• Springboot+JWT


    1.什么是JWT

    JWT(JSON WEB TOKEN)是一种标准,用来在前后端或者系统间以JSON对象安全的传输信息,该信息是可以被验证和信任的,因为它是数字签名的。可以使用HMAC或RSA或ECDSA的公钥/私钥进行签名。

    2.JWT能做什么

    1. 授权
      一旦登录,每个后续请求都包括JWT,用户就可以访问该令牌允许的路由、服务和资源
    2. 信息交换
      可以对JWT签名,确保收件人是对的人,还可以验证内容是否篡改

    3.为什么用JWT

    3.1 传统登录基于session认证

    在这里插入图片描述

    1. 如何实现的
      • 客户端发送账号密码到后端
      • 账号密码验证通过,用户登录成功以后,返回一个sessionId给客户端,客户端每次请求时候,都携带这个sessionId,就表示它已经登陆过了,不需要再输入用户名密码。
    2. 存在问题
      • 每个用户都要在服务端保存一个sessionId来保存它的登录状态,一般session都是保存在内存中,用户越多,服务器压力越大。
      • 用户认证之后,如果session存在对应服务器的内存中,下次用户访问必须还是这台服务器,但是现在分布式的应用,负载均衡后没法确定是访问哪台服务器。
      • 基于cookie来识别用户,如果被篡改,容易收到跨站请求伪造攻击

    3.2 基于JWT认证

    1. 如何实现的
      • 客户端发送账号密码到后端,
      • 账号密码验证通过后,将用户的id等其他信息作为Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT(token),格式如xxx.xxx.xxx,由.分割,三部分组成head,payload,singurater
      • 客户端接收到JWT后保存在本地,退出登录时,就删除本地的JWT
      • 前端每次请求将JWT放在HTTP Header的Atuthorization中,解决XSS和XSRF。
      • 后端收到请求,就验证JWT的有效性,是否过期,签名是否正确等。
      • 验证通过后,后端使用JWT中包含的用户信息进行对应操作。
    2. 优势
      • 可以通过URL、post参数或header发送,数据量小,传输速度快。
      • 负载中包含了用户信息,避免多次查询数据库
      • token以JSON加密的形式保存在客户端,所以可以跨语言,原则上任何web形式都支持。
      • 不保存在服务端,适合分布式微服务。

    4.JWT的结构

    令牌的组成

    • 标头(Header)
    • 有效载荷(Payload)
    • 签名(Signature)

    格式如xxx.xxx.xxx,由.分割,三部分组成

    4.1 Header

    标头,标头通常由两部分组成,令牌的类型(JWT)和所使用的签名算法,例如HMAC、SHA256或RAS。

    明文信息,json字符串

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

    使用Base64编译为 xxx 这种格式,作为JWT的第一部分

    4.2 Payload

    有效负载,其中包含声明,声明是有关信息,也会使用Base64转为xxx,作为JWT的第二部分

    {
    	"id":"sad854dsfewr1",
    	"name":"zhangsan"
    }
    
    • 1
    • 2
    • 3
    • 4

    需要注意的是,不要在这里放入敏感信息,例如密码,因为Base64是可逆的

    4.3 Signature

    签名,对头部及负载内容进行签名,保证JWT没有被篡改

    5.JWT的使用

    • 引入依赖

          <dependency>
              <groupId>com.auth0</groupId>
              <artifactId>java-jwt</artifactId>
              <version>3.4.0</version>
          </dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • 创建令牌

          @Test
          void contextLoads() {
              String secretKey="asdf1213$1f.sad";     //密钥
      
              //设置时间20s
              Calendar instance = Calendar.getInstance();
              instance.add(Calendar.SECOND,20);
      
              String token = JWT.create()
                      //.withHeader(map)      不设置头的话,默认就是alg:HMAC256  typ:JWT
                      .withClaim("id", "asd123456")   //设置id
                      .withClaim("name", "zhangsan")  //设置用户名
                      .withExpiresAt(instance.getTime())  //设置过期时间
                      .sign(Algorithm.HMAC256(secretKey));//使用密钥设置签名
      
              System.out.println(token);
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17

      输出结果eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJpZCI6ImFzZDEyMzQ1NiIsImV4cCI6MTY5Nzc2OTMxNn0.MwxzzpvvCmCXB6JYWWVsMnRffo4RkpW413aDRndkbCc

    • 验证令牌
      这里密钥要和加密的密钥一致,否则签名验证不通过

      @Test
      void test(){
          String secretKey="asdf1213$1f.sad";     //密钥
      
          //创建验证对象
          JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secretKey)).build();
          DecodedJWT verify = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJleHAiOjE2OTc3NzEyMTB9.Ro9YSdTmuqdrisQhXRVDxPT-pv4FZLb5nnQkNRUrMjc");
          //获取负载中name的值,存什么类型的值,就要使用对应的asInt或者asString
          System.out.println(verify.getClaim("name").asString());
          //获取过期时间
          System.out.println(verify.getExpiresAt());
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 常见异常
      在这里插入图片描述

      • SignatureVerificationException 签名不一致
      • TokenExpiredException 令牌过期
      • AlgorithmMismatchException 算法不匹配
      • InvalidClaimException 失效的payload

    6.封装JWT工具类

    import com.auth0.jwt.JWT;
    import com.auth0.jwt.JWTCreator;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.interfaces.DecodedJWT;
    
    import java.util.Calendar;
    import java.util.Map;
    
    public class JWTUtils {
    
        //密钥
        private static final String SECRET_KEY = "asdf1213$1f.sad";
    
        /**
         * 生成token
         * @param map   负载中存储的值
         * @return  生成的token值
         */
        public static String getToken(Map<String, String> map) {
    
            //设置过期时间
            Calendar instance = Calendar.getInstance();
            instance.add(Calendar.DATE, 7);  //默认7天
    
            //创建JWT builder
            JWTCreator.Builder builder = JWT.create();
            //存储payload
            map.forEach((key, value) -> builder.withClaim(key, value));
            //sign
            String token = builder.withExpiresAt(instance.getTime())  //设置过期时间
                    .sign(Algorithm.HMAC256(SECRET_KEY));//使用密钥设置签名
    
            return token;
        }
    
        /**
         * 验证token
         * @param token
         * @return
         */
        public static DecodedJWT verify(String token){
            //创建验证对象
            return JWT.require(Algorithm.HMAC256(SECRET_KEY)).build().verify(token);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    7.SpringBoot整合JWT

    目录结构
    在这里插入图片描述

    7.1 搭建环境

    springboot+jwt+mybatis

    • 引入依赖

          <dependencies>
      
              <dependency>
                  <groupId>org.springframework.bootgroupId>
                  <artifactId>spring-boot-starter-webartifactId>
              dependency>
      
              
              <dependency>
                  <groupId>com.auth0groupId>
                  <artifactId>java-jwtartifactId>
                  <version>3.4.0version>
              dependency>
              
              <dependency>
                  <groupId>org.mybatis.spring.bootgroupId>
                  <artifactId>mybatis-spring-boot-starterartifactId>
                  <version>3.0.0version>
              dependency>
              
              <dependency>
                  <groupId>org.projectlombokgroupId>
                  <artifactId>lombokartifactId>
                  <version>1.18.12version>
              dependency>
              
              <dependency>
                  <groupId>com.alibabagroupId>
                  <artifactId>druidartifactId>
                  <version>1.1.19version>
              dependency>
              
              <dependency>
                  <groupId>mysqlgroupId>
                  <artifactId>mysql-connector-javaartifactId>
                  <version>8.0.11version>
              dependency>
      
              <dependency>
                  <groupId>org.springframework.bootgroupId>
                  <artifactId>spring-boot-starter-testartifactId>
                  <scope>testscope>
              dependency>
          dependencies>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
    • 配置文件

      server.port=8999
      
      spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
      spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
      spring.datasource.url=jdbc:mysql://localhost:3306/aaa?characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
      spring.datasource.username=root
      spring.datasource.password=root
      
      mybatis.type-aliases-package=com.jwt.entity
      mybatis.mapper-locations=classpath:com/jwt/mapper/*.xml
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 实体

      package com.jwt.entity;
      
      import lombok.Data;
      import lombok.experimental.Accessors;
      
      @Data
      @Accessors(chain = true)
      public class User {
      
          private String username;
      
          private String password;
          
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • Dao和Mapper

      • UserDao
        package com.jwt.dao;
        
        import com.jwt.entity.User;
        import org.apache.ibatis.annotations.Mapper;
        
        @Mapper
        public interface UserDao {
            User login(User user);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
      • UserDaoMapper.xml
        
        DOCTYPE mapper
                PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
        <mapper namespace="com.jwt.dao.UserDao">
            <select id="login" parameterType="User" resultType="User">
                select * from user where username=#{username} and password=#{password}
            select>
        mapper>
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
    • Service

      package com.jwt.service;
      
      import com.jwt.dao.UserDao;
      import com.jwt.entity.User;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      import org.springframework.util.ObjectUtils;
      
      @Service
      public class UserServiceImpl {
      
          @Autowired
          private UsersDao usersDao;
      
          public Users login(User user){
      
              User user = userDao.login(user);
      
              if(ObjectUtils.isEmpty(user)){
                  throw new RuntimeException("登录失败");
              }
              return user;
          }
      
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
    • controller

      package com.jwt.controller;
      
      import com.auth0.jwt.exceptions.AlgorithmMismatchException;
      import com.auth0.jwt.exceptions.SignatureVerificationException;
      import com.auth0.jwt.exceptions.TokenExpiredException;
      import com.auth0.jwt.interfaces.DecodedJWT;
      import com.jwt.entity.Users;
      import com.jwt.service.UserServiceImpl;
      import com.jwt.utils.JWTUtils;
      import lombok.extern.slf4j.Slf4j;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RestController;
      
      import java.util.HashMap;
      import java.util.Map;
      
      @RestController
      @Slf4j
      public class UserController {
      
          @Autowired
          private UserServiceImpl userService;
      
          @RequestMapping("/user/login")
          public Map<String,Object> login(Users user){
              System.out.println(user.getUsername());
              log.info("username:[{}]",user.getUsername());
              log.info("password:[{}]",user.getPassword());
      
              Map<String,Object> map=new HashMap<>();
      
      
              try {
                  Users userDb = userService.login(user);
                  Map<String,String> payload = new HashMap<>();
                  payload.put("username",userDb.getUsername());
                  payload.put("password",userDb.getPassword());
                  String token = JWTUtils.getToken(payload);
      
                  map.put("state",true);
                  map.put("msg","登录成功");
                  map.put("token",token);
              } catch (Exception e) {
                  map.put("state",false);
                  map.put("msg",e.getMessage());
              }
      
              return map;
          }
      
          @RequestMapping("/user/verify")
          public Map<String,Object> verify(String token){
              Map<String,Object> map = new HashMap<String,Object>();
              log.info("token:{}",token);
              try {
                  DecodedJWT decodedJWT = JWTUtils.verify(token); //验证令牌
                  map.put("state",true);
                  map.put("msg","验证成功");
                  return map;
              } catch (SignatureVerificationException e) {
                  e.printStackTrace();
                  map.put("msg","无效签名");
              }catch (TokenExpiredException e){
                  e.printStackTrace();
                  map.put("msg","token过期");
              }catch (AlgorithmMismatchException e) {
                  e.printStackTrace();
                  map.put("msg","token算法不一致");
              }catch (Exception e) {
                  e.printStackTrace();
                  map.put("msg","token无效");
              }
              map.put("state",false);
              return map;
          }
      
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79

    访问/user/login,首先从数据库查询,user对象是否存在,如果存在,就生成token,返回给前端,
    在这里插入图片描述
    然后再访问/user/verify,验证这个token是否有效
    在这里插入图片描述

    7.2 拦截器验证token

    如果对每个接口都做token认证,代码冗余太高,单体应用的话,可以使用拦截器验证,分布式应用可以使用网关验证

    • 创建拦截器
      token放在请求头header中

      package com.jwt.interceptors;
      
      import com.auth0.jwt.exceptions.AlgorithmMismatchException;
      import com.auth0.jwt.exceptions.SignatureVerificationException;
      import com.auth0.jwt.exceptions.TokenExpiredException;
      import com.auth0.jwt.interfaces.DecodedJWT;
      import com.fasterxml.jackson.databind.ObjectMapper;
      import com.jwt.utils.JWTUtils;
      import org.springframework.web.servlet.HandlerInterceptor;
      
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.util.HashMap;
      import java.util.Map;
      
      public class JWTinterceptors implements HandlerInterceptor {
      
          @Override
          public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
              // 从 http 请求头中取出 token
              String token = request.getHeader("token");
      
              Map<String,Object> map = new HashMap<String,Object>();
              try {
                  DecodedJWT decodedJWT = JWTUtils.verify(token); //验证令牌
                  map.put("state",true);
                  map.put("msg","验证成功");
                  return true;    //验证通过,放行请求
              } catch (SignatureVerificationException e) {
                  e.printStackTrace();
                  map.put("msg","无效签名");
              }catch (TokenExpiredException e){
                  e.printStackTrace();
                  map.put("msg","token过期");
              }catch (AlgorithmMismatchException e) {
                  e.printStackTrace();
                  map.put("msg","token算法不一致");
              }catch (Exception e) {
                  e.printStackTrace();
                  map.put("msg","token无效");
              }
              map.put("state",false);
              //map转为json
              String json = new ObjectMapper().writeValueAsString(map);
      
              response.setContentType("application/json; charset=utf-8");
              response.getWriter().print(json);
      
              return false;
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
    • 配置拦截器

      package com.jwt.config;
      
      import com.jwt.interceptors.JWTinterceptors;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.stereotype.Component;
      import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
      
      @Configuration
      public class InterceptorConfig implements WebMvcConfigurer {
      
          @Override
          public void addInterceptors(InterceptorRegistry registry) {
              registry.addInterceptor(new JWTinterceptors())
                  .addPathPatterns("/user/verify")     //拦截测试验证接口
                  .excludePathPatterns("/user/login");   //放行登录接口
          }
      }
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 修改7.1中的代码,

      • 修改验证接口/user/verify,去除手动验证的方法,因为拦截器已经实现了
            @RequestMapping("/user/verify")
            public Map<String, Object> verify(String token) {
                Map<String, Object> map = new HashMap<String, Object>();
        
                map.put("state", true);
                map.put("msg", "请求成功");
        
                return map;
            }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
    • 验证
      先访问登录接口,登录成功
      在这里插入图片描述
      再访问验证接口,现在token就不是以参数的形式传进去了,需要放在请求头header中,
      先验证个没带token的,因为controller的代码已经改了,这个token无效提示不是controller的了,是拦截器中的提示
      在这里插入图片描述
      再验证个正确的token
      在这里插入图片描述

  • 相关阅读:
    C语言——九九乘法表
    centos7 防火墙设置 多网卡 内网访问
    java 正则表达式
    红队隧道应用篇之ICMP协议传输(八)
    AI从入门到精通,什么是LLMs大型语言模型?
    华为安全HCIP-Security H12-721、H12-722、H12-723题库,含三套vce软件
    青少年python系列 25.turtle库绘制一个田字方格的方框
    压电换能器的工作原理和应用(功率放大器)
    Nacos安装指南以及集群搭建
    氮化镓(GaN)功率半导体之预测
  • 原文地址:https://blog.csdn.net/a3562323/article/details/133923265