1. 首页
  2. >
  3. 技术专题
  4. >
  5. SpringBoot

你们要的SpringBoot+JWT来了

什么是JWT

Json Web Token (JWT)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)
该token被设计为紧凑且安全的 特别适用于分布式站点的单点登录(SSO)场景

随着JWT的出现 使得校验方式更加简单便捷化
JWT实际上就是一个字符串 它由三部分组成:头部 载荷签名
用[.]分隔这三个部分 最终的格式类似于:xxxx.xxxx.xxxx

该服务器直接根据token取出保存的用户信息 即可对token的可用性进行校验 使得单点登录更为简单

JWT校验的过程

  • 1、浏览器发送用户名和密码 发起登录请求
  • 2、服务端验证身份 根据算法将用户标识符打包生成token字符串 并且返回给浏览器
  • 3、当浏览器需要发起请求时 将token一起发送给服务器
  • 4、服务器发现数据中携带有token 随即进行解密和鉴权
  • 5、校验成功 服务器返回请求的数据
  • JWT的使用

    项目使用:springboot+mysql+mybatisplus+lombok

    不懂的可以先去学习一下,或者可以使用其他方法大同小异


    第一步:创建SpringBoot项目我这里用的Maven构建

    第二步:导包修改pom.xml下面的dependencies节点加入如下

    <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency>         <!--jwt依赖-->         <dependency>             <groupId>io.jsonwebtoken</groupId>             <artifactId>jjwt</artifactId>             <version>0.9.1</version>         </dependency>         <dependency>             <groupId>com.auth0</groupId>             <artifactId>java-jwt</artifactId>             <version>3.4.0</version>         </dependency>          <!--mysql-->         <dependency>             <groupId>mysql</groupId>             <artifactId>mysql-connector-java</artifactId>             <scope>runtime</scope>             <version>5.1.47</version>         </dependency>          <!--lombok-->         <dependency>             <groupId>org.projectlombok</groupId>             <artifactId>lombok</artifactId>         </dependency>          <!--mybatis-plus-->         <dependency>             <groupId>com.baomidou</groupId>             <artifactId>mybatis-plus-boot-starter</artifactId>             <version>3.2.0</version>         </dependency>          <!--数据源-->         <dependency>             <groupId>com.alibaba</groupId>             <artifactId>druid</artifactId>             <version>1.1.12</version>         </dependency>

    第三步:修改application.yml

    server:   port: 8089 spring:   datasource:     driver-class-name: com.mysql.jdbc.Driver     url: jdbc:mysql://127.0.0.1:3306/jwt?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false     type: com.alibaba.druid.pool.DruidDataSource     username: root     password: root mybatis-plus:   mapper-locations: classpath:mapper/*.xml   configuration:     map-underscore-to-camel-case: true   type-aliases-package: com.kexun.springbootjwt.entity

    第四步:创建数据库(jwt)和表user结构如下

    CREATE DATABASE `jwt`   CREATE TABLE `user` (   `id` varchar(32) DEFAULT NULL,   `username` varchar(20) DEFAULT NULL,   `password` varchar(20) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8 

    第五步:配置数据源Druid配置类

    @Configuration public class DruidConfig {     @ConfigurationProperties(prefix = "spring.datasource")     @Bean     public DruidDataSource druidDataSource() {         return new DruidDataSource();     } }

    第六步:创建实体

    @Data public class User {     @TableId(value = "id", type = IdType.UUID)     private String id;     private String username;     private String password; }

    第七步:创建mapper

    public interface UserMapper extends BaseMapper<User> { }

    第八步:创建自定义注解标记的这个注解的

    @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CheckToken {     boolean required() default true; }  

    第九步:创建拦截器

    public class LoginInterceptor implements HandlerInterceptor {      private UserService userService;      public LoginInterceptor(UserService userService) {         this.userService = userService;     }      @Override     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {         // 从 http 请求头中取出 token         String token = request.getHeader("token");         if (StringUtils.isEmpty(token)) {             Cookie[] cookies = request.getCookies();             if (cookies != null) {                 for (Cookie cookie : cookies) {                     String name = cookie.getName();                     if ("x-token".equals(name)) {                         token = cookie.getValue();                     }                 }             }         }           // 如果不是映射到方法直接通过         if (!(handler instanceof HandlerMethod)) {             return true;         }          HandlerMethod handlerMethod = (HandlerMethod) handler;         Method method = handlerMethod.getMethod();          //检查有没有需要用户权限的注解         if (method.isAnnotationPresent(CheckToken.class)) {             CheckToken checkToken = method.getAnnotation(CheckToken.class);             if (checkToken.required()) {                 // 执行认证                 if (StringUtils.isEmpty(token)) {                     throw new RuntimeException("无token,请重新登录");                 }                 // 获取 token 中的 user id                 String userId;                 try {                     userId = JWT.decode(token).getClaim("id").asString();                 } catch (JWTDecodeException j) {                     throw new RuntimeException("访问异常!");                 }                 User user = userService.getById(userId);                 if (user == null) {                     throw new RuntimeException("用户不存在,请重新登录");                 }                 Boolean verify = JwtUtils.isVerify(token, user);                 if (!verify) {                     throw new RuntimeException("非法访问!");                 }                 return true;             }         }         return true;     } }

    第十步:注册拦截器:

    @Configuration public class WebConfig implements WebMvcConfigurer {      @Autowired     private UserService userService;      @Override     public void addInterceptors(InterceptorRegistry registry) {         registry.addInterceptor(new LoginInterceptor(userService)).addPathPatterns("/**");     } } 

    异常处理类

    @RestControllerAdvice public class GlobalExceptionHandler {     @ExceptionHandler(Exception.class)     @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)     public Result onRuntime(Exception e) {         e.printStackTrace();         return Result.error(e.getMessage());     } }

    返回包装类

    package com.kexun.springbootjwt.utils;  public class Result {     private static final long serialVersionUID = 1L;      private int code;      private String msg;      private Object data;      public static long getSerialVersionUID() {         return serialVersionUID;     }      public int getCode() {         return code;     }      public void setCode(int code) {         this.code = code;     }      public String getMsg() {         return msg;     }      public void setMsg(String msg) {         this.msg = msg;     }      public Object getData() {         return data;     }      public void setData(Object data) {         this.data = data;     }      public Result(int code, String msg, Object data) {         this.code = code;         this.msg = msg;         this.data = data;     }      public Result(int code, String msg) {         this.code = code;         this.msg = msg;     }      public Result() {     }      public static Result success() {         return new Result(0, "ok", null);     }      public static Result success(String msg) {         return new Result(0, msg, null);     }      public static Result success(String msg, Object data) {         return new Result(0, msg, data);     }       public static Result error() {         return new Result(500, "error", null);     }      public static Result error(String msg) {         return new Result(500, msg, null);     }      public static Result error(String msg, Object data) {         return new Result(500, msg, data);     }   } 

    JwtUtils

    public class JwtUtils {      /**      * 用户登录成功后生成Jwt      * 使用Hs256算法  私匙使用用户密码      *      * @param ttlMillis jwt过期时间      * @param user      登录成功的user对象      * @return      */     public static String createJWT(long ttlMillis, User user) {         //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。         SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;          long nowMillis = System.currentTimeMillis();         Date now = new Date(nowMillis);          //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)         Map<String, Object> claims = new HashMap<String, Object>();         claims.put("id", user.getId());         claims.put("username", user.getUsername());         claims.put("password", user.getPassword());          //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。         String key = user.getPassword();          //生成签发人         String subject = user.getUsername();           //下面就是在为payload添加各种标准声明和私有声明了         //这里其实就是new一个JwtBuilder,设置jwt的body         JwtBuilder builder = Jwts.builder()                 //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的                 .setClaims(claims)                 //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。                 .setId(UUID.randomUUID().toString())                 //iat: jwt的签发时间                 .setIssuedAt(now)                 //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。                 .setSubject(subject)                 //设置签名使用的签名算法和签名使用的秘钥                 .signWith(signatureAlgorithm, key);         if (ttlMillis >= 0) {             long expMillis = nowMillis + ttlMillis;             Date exp = new Date(expMillis);             //设置过期时间             builder.setExpiration(exp);         }         return builder.compact();     }      /**      * Token的解密      *      * @param token 加密后的token      * @param user  用户的对象      * @return      */     public static Claims parseJWT(String token, User user) {         //签名秘钥,和生成的签名的秘钥一模一样         String key = user.getPassword();         //得到DefaultJwtParser         Claims claims = Jwts.parser()                 //设置签名的秘钥                 .setSigningKey(key)                 //设置需要解析的jwt                 .parseClaimsJws(token).getBody();         return claims;     }       /**      * 校验token      * 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过      *      * @param token      * @param user      * @return      */     public static Boolean isVerify(String token, User user) {         //签名秘钥,和生成的签名的秘钥一模一样         String key = user.getPassword();          //得到DefaultJwtParser         Claims claims = Jwts.parser()                 //设置签名的秘钥                 .setSigningKey(key)                 //设置需要解析的jwt                 .parseClaimsJws(token).getBody();          if (claims.get("password").equals(user.getPassword())) {             return true;         }          return false;     }   }

    启动类:

    @SpringBootApplication @MapperScan("com.kexun.springbootjwt.mapper") public class SpringbootJwtApplication {      public static void main(String[] args) {         SpringApplication.run(SpringbootJwtApplication.class, args);     }  }

    具体演示:

    登录:http://127.0.0.1:8089/login?username=lidong&password=123456

    你们要的SpringBoot+JWT来了

    鉴权:http://127.0.0.1:8089/checkToken

    你们要的SpringBoot+JWT来了

    清空cookie在鉴权

    你们要的SpringBoot+JWT来了

    你们要的SpringBoot+JWT来了

    授权失败

    都写在注释里面了不明白的可以在评论区留言我看到会及时回复的

    附gitee源码地址:https://gitee.com/gdianqimeng/springboot-jwt.git