SpringBoot集成JWT详细步骤

2023-10-21 12:38:11 浏览数 (3)

1、JWT 简介

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以进行验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对对JWT进行签名

2、应用场景

  • 授权:这是使用JWT最常见的方案。当用户登录后,每个后续请求将会在header带上JWT,允许用户访问允许使用该令牌的路由、服务和资源。单点登录是当今广泛使用JWT的一个特性,因为它具有较小的开销和易于跨不同域使用的能力。
  • 信息交换:JWT是保证各方之间安全地传输信息的一种好方法。因为JWT可以被签名,例如使用公钥/私钥对,可以确保发件人是他们所说的人。另外,由于使用header和playload计算签名,还可以验证内容是否被篡改。

3、Jwt结构

JSON Web令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔,分别是:

  • 标头
  • 有效载荷
  • 签名 因此,JWT通常如下所示:

xxxxx.yyyyy.zzzzz

4、Jwt工作流程

  1. 用户使用账号登录发出post请求;
  2. 服务器使用私钥创建一个jwt;
  3. 服务器返回这个jwt给浏览器;
  4. 浏览器将该jwt串放在请求头中,向服务器发送请求;
  5. 服务器验证该jwt;
  6. 返回响应的资源给浏览器。

5、SpringBoot 集成 Jwt

  • 工作目录介绍
  • pom配置文件
代码语言:javascript复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>loginintercept</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>loginintercept</name>
  <description>Demo project for Spring Boot</description>

  <properties>
    <java.version>1.8</java.version>
  </properties>

  <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.7.0</version>
    </dependency>

    <!-- lombok   -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.12</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>
  • JwtUtils 工具类
代码语言:javascript复制
package com.example.loginintercept.config;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import org.springframework.stereotype.Component;

/**
 * token 工具类
 */
@Component
public class JwtUtils {

  // 过期时间
  private static long expire = 604800;
  // 秘钥
  private static String secret = "HSyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9";

  /**
   * 创建一个token
   *
   * @param userId
   * @return
   */
  public String generateToken(String userId) {
    Date now = new Date();
    Date expireDate = new Date(now.getTime()   expire);
    return Jwts.builder().setHeaderParam("type", "JWT").setSubject(userId).setIssuedAt(now)
        .setExpiration(expireDate).signWith(
            SignatureAlgorithm.HS512, secret).compact();
  }

  /**
   * 解析token
   */
  public Claims getClaimsByToken(String token) {
    try {
      return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    } catch (Exception e) {
      System.out.println("validate is token error");
      return null;
    }
  }

  /**
   * 判断 token 是否过期
   */
  public boolean isTokenExpired(Date expiration){
    return expiration.before(new Date());
  }

}
  • ResultT 统一返回格式
代码语言:javascript复制
package com.example.loginintercept.config;

import java.util.HashMap;
import java.util.Map;

public class ResultT extends HashMap<String, Object> {

  public ResultT() {
    put("code", 0);
    put("msg", "success");
  }

  public static ResultT ok() {
    ResultT t = new ResultT();
    t.put("msg", "操作成功");
    return t;
  }

  public static ResultT ok(String msg) {
    ResultT t = new ResultT();
    t.put("msg", msg);
    return t;
  }

  public static ResultT ok(Map<String, Object> map) {
    ResultT t = new ResultT();
    t.putAll(map);
    return t;
  }

  public static ResultT error(int code, String msg) {

    ResultT t = new ResultT();
    t.put("code", code);
    t.put("msg", msg);
    return t;
  }

  public static ResultT error() {
    return error(500, "未知异常");
  }

  public ResultT put(String key, Object value){
    super.put(key, value);
    return this;
  }
}
  • TokenInterceptor 创建一个token拦截器
代码语言:javascript复制
package com.example.loginintercept.config;

import com.example.loginintercept.exception.TokenRuntimeException;
import io.jsonwebtoken.Claims;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * 创建一个 token 拦截器.
 * 需要继承 HandlerInterceptorAdapter,并且声明为spring的组件
 */
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {

  // 注入jwt工具类
  @Autowired
  private JwtUtils jwtUtils;

  // 重写 前置拦截方法
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    // 1、从请求头中获取token
    String token = request.getHeader("token");

    // 2、判断 token 是否存在
    if (token == null ||"".equals(token)) {
      System.out.println("未登录");
      // 这里可以自定义 抛出 token 异常
      throw new TokenRuntimeException("未登录");
    }

    // 3、解析token
    Claims claim = jwtUtils.getClaimsByToken(token);

    if (null == claim) {
      System.out.println("token 解析错误");
      // 这里可以自定义 抛出 token 异常
      throw  new TokenRuntimeException("token 解析错误");
    }

    // 4、判断 token 是否过期
    Date expiration = claim.getExpiration();
    boolean tokenExpired = jwtUtils.isTokenExpired(expiration);
    if (tokenExpired) {
      System.out.println("token已过期,请重新登录");
      // 这里可以自定义 抛出 token 异常
      throw new TokenRuntimeException("token已过期,请重新登录");
    }

    // 5、 从 token 中获取员工信息
    String subject = claim.getSubject();

    // 6、去数据库中匹配 id 是否存在 (这里直接写死了)
    if (null == subject) {
      System.out.println("员工不存在");
      // 这里可以自定义 抛出 token 异常
      throw new TokenRuntimeException("员工不存在");
    }

    // 7、成功后 设置想设置的属性,比如员工姓名
    request.setAttribute("userId", subject);
    request.setAttribute("userName", "张三");

    return true;
  }
}
  • WebMvcConfig 设置拦截器
代码语言:javascript复制
package com.example.loginintercept.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 设置拦截器.
 * 打上configuration 注解,标注为配置项
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  // 注入 token 拦截器
  @Autowired
  private TokenInterceptor interceptor;

  /**
   * 重写添加拦截器
   */
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 添加自定义拦截器,并拦截对应 url
    registry.addInterceptor(interceptor).addPathPatterns("/gateway/**");
  }
}
  • GatewayController 模仿需要登录后才能访问的资源
代码语言:javascript复制
package com.example.loginintercept.controller;

import com.example.loginintercept.config.ResultT;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * controller
 */
@RestController
@RequestMapping("/gateway")
public class GatewayController {

  @RequestMapping(value = "/find",method = RequestMethod.POST)
  public ResultT find(){
    return ResultT.ok("find one success");
  }

}
  • LoginController 登录
代码语言:javascript复制
package com.example.loginintercept.controller;

import com.example.loginintercept.config.JwtUtils;
import com.example.loginintercept.config.ResultT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * 登录
 */
@RestController
public class LoginController {
  // 注入jwt工具类
  @Autowired
  private JwtUtils jwtUtils;
  @RequestMapping(value = "/login", method = RequestMethod.POST)
  public ResultT login(String name, String psw) {
    String userId = "132";
    String token = jwtUtils.generateToken(userId);
    return ResultT.ok().put("token", token);
  }
}
  • SysRuntimeExceptionHandler 全局异常处理
代码语言:javascript复制
package com.example.loginintercept.exception;

import com.example.loginintercept.config.ResultT;
import com.example.loginintercept.exception.TokenRuntimeException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理
 */
@RestControllerAdvice
public class SysRuntimeExceptionHandler {

  @ExceptionHandler(TokenRuntimeException.class)
  public ResultT tokenRuntimeException(TokenRuntimeException e) {
    e.printStackTrace();
    return ResultT.error(e.getCode(), e.getMsg());
  }

  @ExceptionHandler(Exception.class)
  public ResultT handlerException(Exception e){
    e.printStackTrace();
    return ResultT.error();
  }
}
  • TokenRuntimeException 自定义 token 异常
代码语言:javascript复制
package com.example.loginintercept.exception;

import lombok.Data;

/**
 * 自定义 token 异常
 */
@Data
public class TokenRuntimeException extends RuntimeException{

  private Integer code = 401;
  private String msg;

  public TokenRuntimeException(String msg) {
    this.msg = msg;
  }

}

6、运行测试

  • 首先访问需要 token 的资源
  • 登录 获取 token
  • 请求头中存放 token 再次访问需要 token 的资源

当然,一般在实际开发中请求头会叫Authorizationtoken 内容的前面通常会拼接上 `’Bearer ‘ 这里为了方便,就不这样做了

好,完全!

1 人点赞