JWT

JWT本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码得到一个JWT token,并且这个JWT token带有签名信息,接收后可以验证是否被篡改。所以可以用于在各方之间安全地将信息作为Json对象传输。

JWT认证流程如下:

  • 前端将用户名和密码发送给后端
  • 后端核对用户名和密码成功后,将用户信息的数据作为JWT的payload,将其与JWT Header分别进行Base64编码拼接后签名。格式如:xxx.xxx.xxx
  • 后端将JWT token 作为登陆成功的结果返回给前端。前端可以将返回的结果保存在浏览器中,退出登录时删除保持的JWT token即可
  • 前端在每次请求时将JWT Token放入HTTP请求头中的Authorization属性中(解决XSS和XSRF问题)
  • 后端检查前端传过来的JWT Token,验证其有效性,比如检查签名是否正确、是否过期、token的接收方是否是自己等等
  • 验证通过后,后端解析出JWT Token中包含的用户信息,进行其他逻辑操作(一般是根据用户信息得到权限等),返回结果

导入依赖

       <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
       <!--swagger3 生成接口注释-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
        <!-- 解决jdk11缺失jar包引起的报错-->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>
        <!-- jwt token核心依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
        <!-- jwt token核心依赖-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.18.3</version>
        </dependency>

token生成及校验

  • 封装用户

    存储用户的基本信息

    import lombok.Data;
    import lombok.experimental.Accessors;
    
    /**
     * @author l
     */
    @Data
    @Accessors(chain = true)
    public class JwtUser {
    
        private boolean valid;
        private String userId;
        private String role;
    
        public JwtUser() {
            this.valid = false;
        }
    }
    
    
  • 编写JWT提供者

    主要关注 createToken 和 checkToken 两个方法

    import io.jsonwebtoken.*;
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.extern.slf4j.Slf4j;
    import java.nio.charset.StandardCharsets;
    import java.util.Base64;
    import java.util.Date;
    
    
    /**
    * date: 2021-01-05 08:48
    * description token管理
    *
    * @author qiDing
    */
    @Slf4j
    @ApiModel("token提供者")
    public class TokenProvider {
    
      @ApiModelProperty("盐")
      private static final String SALT_KEY = "links";
    
      @ApiModelProperty("令牌有效期毫秒")
      private static final long TOKEN_VALIDITY = 86400000;
    
      @ApiModelProperty("权限密钥")
      private static final String AUTHORITIES_KEY = "auth";
    
      @ApiModelProperty("Base64 密钥")
      private final static String SECRET_KEY =  Base64.getEncoder().encodeToString(SALT_KEY.getBytes(StandardCharsets.UTF_8));
    
    
      /**
       * 生成token
       * @param userId 用户id
       * @param clientId 用于区别客户端,如移动端,网页端,此处可根据自己业务自定义
       * @param role 角色权限
       */
      public static String createToken(String userId, String clientId, String role) {
          Date validity = new Date((new Date()).getTime() + TOKEN_VALIDITY);
          return Jwts.builder()
                  // 代表这个JWT的主体,即它的所有人
                  .setSubject(String.valueOf(userId))
                  // 代表这个JWT的签发主体
                  .setIssuer("")
                  // 是一个时间戳,代表这个JWT的签发时间;
                  .setIssuedAt(new Date())
                  // 代表这个JWT的接收对象
                  .setAudience(clientId)
                  .claim("role", role)
                  .claim("userId", userId)
                  .signWith(SignatureAlgorithm.HS512, SECRET_KEY)
                  .setExpiration(validity)
                  .compact();
      }
    
        /**
         * 校验token
         */
        public static JwtUser checkToken(String token) {
            if (validateToken(token)) {
                Claims claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
                String audience = claims.getAudience();
                String userId = claims.get("userId", String.class);
                String role = claims.get("role", String.class);
                JwtUser jwtUser = new JwtUser().setUserId(userId).setRole(role).setValid(true);
                log.info("===token有效{},客户端{}", jwtUser, audience);
                return jwtUser;
            }
            log.error("***token无效***");
            return new JwtUser();
        }
    
    
        private static boolean validateToken(String authToken) {
            try {
                Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(authToken);
                return true;
            } catch (Exception e) {
                log.error("无效的token:" + authToken);
            }
            return false;
        }
    }
    
    

登陆

  1. 密码登录

    对密码进行md5加密

    import org.springframework.util.DigestUtils;
    
    /**
     * 密码加密工具类
     *
     * @author liangQiDing
     */
    public class PasswordEncoder {
    
        /**
         * 密码加密
         * @param rawPassword 登录时传入的密码
         */
        public static String encode(CharSequence rawPassword) {
            return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
        }
    
        /**
         * 密码对比
         * @param rawPassword 登录时传入的密码
         * @param encodedPassword 数据库保存的加密过的密码
         */
        public static boolean matches(CharSequence rawPassword, String encodedPassword) {
            return encodedPassword.equals(DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes()));
        }
    }
    
    
  2. 登陆接口编写

    import com.example.jwt_dome.config.PasswordEncoder;
    import com.example.jwt_dome.jwt.AuthStorage;
    import com.example.jwt_dome.jwt.JwtUser;
    import com.example.jwt_dome.jwt.TokenProvider;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.HashMap;
    
    /**
     * @author liangQiDing
     */
    @RestController
    @Api("token测试服务器")
    public class TokenController {
    
        /**
         * 模拟数据库数据 账号 admin  密码 123456
         */
        private final static HashMap<String, String> USER = new HashMap<>() {
            {
                put("admin", "e10adc3949ba59abbe56e057f20f883e");
            }
        };
    
        @GetMapping("/login")
        @ApiOperation("登陆示例(账号admin,密码123456)")
        public String login(String username, String password) {
            if (PasswordEncoder.matches(password, USER.get(username))) {
                // 模拟一个用户的数据 用户id为1  登录端为网页web  角色是admin
                return TokenProvider.createToken("1", "web", "admin");
            }
            return "error";
        }
    
        @GetMapping("/token/validate")
        @ApiOperation("token校验")
        public JwtUser tokenValidate(String token) {
            return TokenProvider.checkToken(token);
        }
    }
    
    

编写拦截器校验token

  1. 存储授权信息

    /**
     * 存储本次请求的授权信息,适用于各种业务场景,包括分布式部署
     *
     * @author lqd
     */
    public class AuthStorage {
    
        @ApiModelProperty("请求头token的下标")
        public static final String TOKEN_KEY = "token";
    
        /**
         * 模拟session
         */
        private static final HashMap<String, JwtUser> JWT_USER = new HashMap<String, JwtUser>();
    
        /**
         * 全局获取用户
         */
        public static JwtUser getUser() {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            return JWT_USER.get(request.getHeader(TOKEN_KEY));
        }
    
        /**
         * 设置用户
         */
        public static void setUser(String token, JwtUser user) {
            JWT_USER.put(token, user);
        }
    
        /**
         * 清除授权
         */
        public static void clearUser() {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            JWT_USER.remove(request.getHeader(TOKEN_KEY));
        }
    }
    
  2. 配置拦截器

    在请求响应前,校验token,校验通过后存储用户信息。

    import com.example.jwt_dome.jwt.AuthStorage;
    import com.example.jwt_dome.jwt.JwtUser;
    import com.example.jwt_dome.jwt.TokenProvider;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.util.StringUtils;
    import org.springframework.web.servlet.HandlerInterceptor;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    /**
     * 拦截器
     *
     * @author lqd
     */
    @Slf4j
    public class AuthInterceptor implements HandlerInterceptor {
    
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String token = request.getHeader(AuthStorage.TOKEN_KEY);
            if (StringUtils.hasLength(token)) {
                JwtUser jwtUser = TokenProvider.checkToken(token);
                // 是否认证通过
                if (jwtUser.isValid()) {
                    // 保存授权信息
                    AuthStorage.setUser(token, jwtUser);
                    return true;
                }
            }
            response.setContentType("text/html;charset=utf-8");
            response.getWriter().write("请先登录!");
            return false;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object      handler, Exception ex) throws Exception {
            // 请求完成清除授权信息
            AuthStorage.clearUser();
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
  3. 配置拦截路径

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    /**
     * 配置拦截器路径
     *
     * @author lqd
     */
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new AuthInterceptor())
                    // 拦截的路径
                    .addPathPatterns("/**")
                    // 开放的路径
                    .excludePathPatterns("/login/**", "/token/validate");
        }
    }
    
  4. 拦截测试

    在controller层添加测试接口

        @GetMapping("/get/Info")
        @ApiOperation("模拟拦截")
        public String getInfo() {
            // 从全局环境中获取用户id
            JwtUser user = AuthStorage.getUser();
            return "用户:"+user.getUserId() + ",请求成功";
        }
    
    
posted @ 2025-05-13 21:35  小郑[努力版]  阅读(21)  评论(0)    收藏  举报