当前位置:   article > 正文

Spring Boot集成JWT快速入门demo

Spring Boot集成JWT快速入门demo

1.JWT是什么?

JWT,英文全称JSON Web Token:JSON网络令牌。为了在网络应用环境间传递声明而制定的一种基于JSON的开放标准(RFC 7519)。这个规范允许我们使用JWT在客户端和服务端之间传递安全可靠的信息。JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑自包含的方式,用于通信双方之间作为 JSON 对象安全地传递信息。此信息可以通过数字签名进行验证和信任。

  • 紧凑:这个字符串简洁,数据量小,传输速度快,能通过URL参数、HTTP请求提交的数据以及HTTP Header的方式进行传递。

  • 自包含:负载中包含很多信息,比如用户的ID等。别人拿到这个字符串,就能拿到这些关键的业务信息,从而避免再通过数据库查询等方式得到它们。

JWT的结构 JWT由三段信息用.连接构成的字符串。

Header.Payload.Signature 例如:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.ihOZFzg3ZGIbBMneRy-4RMqors1P3nuO-wRJnQtTzWQ
  • Header头部

承载两部分信息:token类型和采用的加密算法

  1. {
  2. "typ": "JWT",
  3. "alg": "HS256"
  4. }

token类型:JWT 加密算法:HS256

  • Payload负载

存放有效信息的地方

iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: 过期时间戳(jwt的过期时间,这个过期时间必须要大于签发时间) nbf: 定义在什么时间之前,该jwt都是不可用的 iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击

  • Signature签名

对头部及负载内容进行签证。采用Header中声明的算法,接收三个参数:base64编码的Header、base64编码的Payload和密钥(secret)进行运算。密钥secret是保存在服务端的,服务端会根据这个密钥进行生成token和进行验证。

2.环境搭建

参考代码仓库里面的mysql模块,这里只贴出docker-compose.yml

  1. version: '3'
  2. services:
  3. mysql:
  4. image: registry.cn-hangzhou.aliyuncs.com/zhengqing/mysql:5.7
  5. container_name: mysql_3306
  6. restart: unless-stopped
  7. volumes:
  8. - "./mysql/my.cnf:/etc/mysql/my.cnf"
  9. - "./mysql/init-file.sql:/etc/mysql/init-file.sql"
  10. - "./mysql/data:/var/lib/mysql"
  11. # - "./mysql/conf.d:/etc/mysql/conf.d"
  12. - "./mysql/log/mysql/error.log:/var/log/mysql/error.log"
  13. - "./mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d" # init sql script directory -- tips: it can be excute when `/var/lib/mysql` is empty
  14. environment: # set environment,equals docker run -e
  15. TZ: Asia/Shanghai
  16. LANG: en_US.UTF-8
  17. MYSQL_ROOT_PASSWORD: root # set root password
  18. MYSQL_DATABASE: demo # init database name
  19. ports: # port mappping
  20. - "3306:3306"

运行

docker-compose -f docker-compose.yml -p mysql5.7 up -d

初始化表

  1. DROP TABLE IF EXISTS `jwt_user`;
  2. CREATE TABLE `jwt_user`(
  3. `id` varchar(32) CHARACTER SET utf8 NOT NULL COMMENT '用户ID',
  4. `username` varchar(100) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '登录账号',
  5. `password` varchar(255) CHARACTER SET utf8 NULL DEFAULT NULL COMMENT '密码'
  6. )ENGINE = InnoDB CHARACTER SET = utf8 COMMENT = '用户表' ROW_FORMAT = Compact;
  7. INSERT INTO jwt_user VALUES('1','admin','123');

3.代码工程

实验目标:实现JWT的签发和验证

pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>springboot-demo</artifactId>
  7. <groupId>com.et</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>jwt</artifactId>
  12. <properties>
  13. <maven.compiler.source>8</maven.compiler.source>
  14. <maven.compiler.target>8</maven.compiler.target>
  15. </properties>
  16. <dependencies>
  17. <dependency>
  18. <groupId>org.springframework.boot</groupId>
  19. <artifactId>spring-boot-starter-web</artifactId>
  20. </dependency>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-autoconfigure</artifactId>
  24. </dependency>
  25. <!--mysql-->
  26. <dependency>
  27. <groupId>mysql</groupId>
  28. <artifactId>mysql-connector-java</artifactId>
  29. <version>8.0.29</version>
  30. </dependency>
  31. <dependency>
  32. <groupId>org.springframework.boot</groupId>
  33. <artifactId>spring-boot-starter-jdbc</artifactId>
  34. </dependency>
  35. <dependency>
  36. <groupId>com.baomidou</groupId>
  37. <artifactId>mybatis-plus-boot-starter</artifactId>
  38. <version>3.5.1</version>
  39. </dependency>
  40. <dependency>
  41. <groupId>org.projectlombok</groupId>
  42. <artifactId>lombok</artifactId>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-test</artifactId>
  47. <scope>test</scope>
  48. </dependency>
  49. <dependency>
  50. <groupId>com.alibaba</groupId>
  51. <artifactId>fastjson</artifactId>
  52. <version>1.2.47</version>
  53. </dependency>
  54. <dependency>
  55. <groupId>io.jsonwebtoken</groupId>
  56. <artifactId>jjwt</artifactId>
  57. <version>0.9.1</version>
  58. </dependency>
  59. <dependency>
  60. <groupId>com.auth0</groupId>
  61. <artifactId>java-jwt</artifactId>
  62. <version>3.4.0</version>
  63. </dependency>
  64. </dependencies>
  65. </project>

application.yaml

  1. spring:
  2. datasource:
  3. driver-class-name: com.mysql.cj.jdbc.Driver
  4. url: jdbc:mysql://localhost:3306/demo?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT
  5. username: root
  6. password: root
  7. server:
  8. port: 8088

Jwt生成

  1. package com.et.jwt.service;
  2. import com.auth0.jwt.JWT;
  3. import com.auth0.jwt.algorithms.Algorithm;
  4. import com.et.jwt.entity.User;
  5. import org.springframework.stereotype.Service;
  6. import java.time.Instant;
  7. import java.time.LocalDateTime;
  8. import java.time.ZoneId;
  9. import java.util.Date;
  10. @Service("TokenService")
  11. public class TokenService {
  12. long outHours=1;
  13. public String getToken(User user) {
  14. Instant instant = LocalDateTime.now().plusHours(outHours).atZone(ZoneId.systemDefault()).toInstant();
  15. Date expire = Date.from(instant);
  16. String token="";
  17. // save information to token ,such as: user id and Expires time
  18. token= JWT.create()
  19. .withAudience(user.getId())
  20. .withExpiresAt(expire)
  21. .sign(Algorithm.HMAC256(user.getPassword()));
  22. // use HMAC256 to generate token,key is user's password
  23. return token;
  24. }
  25. }

jwt验证

  1. package com.et.jwt.interceptor;
  2. import com.auth0.jwt.JWT;
  3. import com.auth0.jwt.JWTVerifier;
  4. import com.auth0.jwt.algorithms.Algorithm;
  5. import com.auth0.jwt.exceptions.JWTDecodeException;
  6. import com.auth0.jwt.exceptions.JWTVerificationException;
  7. import com.et.jwt.annotation.PassToken;
  8. import com.et.jwt.annotation.UserLoginToken;
  9. import com.et.jwt.entity.User;
  10. import com.et.jwt.service.UserService;
  11. import org.springframework.beans.factory.annotation.Autowired;
  12. import org.springframework.web.method.HandlerMethod;
  13. import org.springframework.web.servlet.HandlerInterceptor;
  14. import org.springframework.web.servlet.ModelAndView;
  15. import javax.servlet.http.HttpServletRequest;
  16. import javax.servlet.http.HttpServletResponse;
  17. import java.lang.reflect.Method;
  18. import java.util.Date;
  19. public class AuthenticationInterceptor implements HandlerInterceptor {
  20. @Autowired
  21. UserService userService;
  22. @Override
  23. public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
  24. // get token from http header
  25. String token = httpServletRequest.getHeader("token");
  26. if(!(object instanceof HandlerMethod)){
  27. return true;
  28. }
  29. HandlerMethod handlerMethod=(HandlerMethod)object;
  30. Method method=handlerMethod.getMethod();
  31. //check is included @passtoken annotation? jump if have
  32. if (method.isAnnotationPresent(PassToken.class)) {
  33. PassToken passToken = method.getAnnotation(PassToken.class);
  34. if (passToken.required()) {
  35. return true;
  36. }
  37. }
  38. //check is included @UserLoginToken ?
  39. if (method.isAnnotationPresent(UserLoginToken.class)) {
  40. UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
  41. if (userLoginToken.required()) {
  42. // excute verify
  43. if (token == null) {
  44. throw new RuntimeException("token invalid,please login again");
  45. }
  46. // get user id from token
  47. String userId;
  48. try {
  49. userId = JWT.decode(token).getAudience().get(0);
  50. } catch (JWTDecodeException j) {
  51. throw new RuntimeException("401");
  52. }
  53. User user = userService.findUserById(userId);
  54. if (user == null) {
  55. throw new RuntimeException("user is not exist,please login again");
  56. }
  57. // verify token
  58. JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
  59. try {
  60. jwtVerifier.verify(token);
  61. if (JWT.decode(token).getExpiresAt().before(new Date())) {
  62. throw new RuntimeException("token Expires");
  63. }
  64. } catch (JWTVerificationException e) {
  65. throw new RuntimeException("401");
  66. }
  67. return true;
  68. }
  69. }
  70. return true;
  71. }
  72. @Override
  73. public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
  74. }
  75. @Override
  76. public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
  77. }
  78. }

以上只是一些关键代码,所有代码请参见下面代码仓库

代码仓库

  • https://github.com/Harries/springboot-demo

4.测试

  • 启动Spring Boot应用

  • 登录系统

    http://localhost:8088/api/login?username=admin&password=123

  1. {
  2. "user": {
  3. "username": "admin",
  4. "password": "123",
  5. "id": "1"
  6. },
  7. "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxIn0.ihOZFzg3ZGIbBMneRy-4RMqors1P3nuO-wRJnQtTzWQ"
  8. }
  • 在header里面设置 token,访问

    http://localhost:8088/api/getMessage

24c66ffc012d08e554225b4dbc16e092.png

5.引用

  • https://www.v2ex.com/t/817906

  • http://www.liuhaihua.cn/archives/710374.html

  • https://github.com/ChuaWi/SpringBoot-JWT/tree/master

6.Questions

这里留2个问题给读者

  1. 过期了如何刷新token呢?保证过期时候重新登录,而不是后台自动刷新,一直不过期?

  2. 如果要实现强制下线用户功能,该如何实现呢?

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号