• 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. "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-demoartifactId>
    7. <groupId>com.etgroupId>
    8. <version>1.0-SNAPSHOTversion>
    9. parent>
    10. <modelVersion>4.0.0modelVersion>
    11. <artifactId>jwtartifactId>
    12. <properties>
    13. <maven.compiler.source>8maven.compiler.source>
    14. <maven.compiler.target>8maven.compiler.target>
    15. properties>
    16. <dependencies>
    17. <dependency>
    18. <groupId>org.springframework.bootgroupId>
    19. <artifactId>spring-boot-starter-webartifactId>
    20. dependency>
    21. <dependency>
    22. <groupId>org.springframework.bootgroupId>
    23. <artifactId>spring-boot-autoconfigureartifactId>
    24. dependency>
    25. <dependency>
    26. <groupId>mysqlgroupId>
    27. <artifactId>mysql-connector-javaartifactId>
    28. <version>8.0.29version>
    29. dependency>
    30. <dependency>
    31. <groupId>org.springframework.bootgroupId>
    32. <artifactId>spring-boot-starter-jdbcartifactId>
    33. dependency>
    34. <dependency>
    35. <groupId>com.baomidougroupId>
    36. <artifactId>mybatis-plus-boot-starterartifactId>
    37. <version>3.5.1version>
    38. dependency>
    39. <dependency>
    40. <groupId>org.projectlombokgroupId>
    41. <artifactId>lombokartifactId>
    42. dependency>
    43. <dependency>
    44. <groupId>org.springframework.bootgroupId>
    45. <artifactId>spring-boot-starter-testartifactId>
    46. <scope>testscope>
    47. dependency>
    48. <dependency>
    49. <groupId>com.alibabagroupId>
    50. <artifactId>fastjsonartifactId>
    51. <version>1.2.47version>
    52. dependency>
    53. <dependency>
    54. <groupId>io.jsonwebtokengroupId>
    55. <artifactId>jjwtartifactId>
    56. <version>0.9.1version>
    57. dependency>
    58. <dependency>
    59. <groupId>com.auth0groupId>
    60. <artifactId>java-jwtartifactId>
    61. <version>3.4.0version>
    62. dependency>
    63. dependencies>
    64. 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. 如果要实现强制下线用户功能,该如何实现呢?

  • 相关阅读:
    SQL 获取每个部门中当前员工薪水最高的相关信息
    Verilog:【6】PWM调制器(pwm_modulator.sv)
    Mybatis动态SQL(DynamicSQL)
    FPGA——WS2812B彩灯点亮
    FL Studio21.0中文版本FL水果娘发布更新
    嵌入式常用计算神器EXCEL,欢迎各位推荐技巧,以保持文档持续更新,为其他人提供便利
    MySQL 自动补全工具
    用Python爬取网易云歌单
    STM32标准库通用定时器输入捕获
    Java的泛型
  • 原文地址:https://blog.csdn.net/dot_life/article/details/137252850