Spring Boot 整合 JWT JSON Web Token

Spring Boot JWT About 5,002 words

添加依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.19.1</version>
</dependency>

获取 Token

@RestController
public class LoginController {

    @GetMapping("/login")
    public TokenDto login() {
        try {
            Algorithm algorithm = Algorithm.HMAC256("secret");
            String token = JWT.create()
                    .withIssuer("auth0")
                    // 自定义 payload 键值
                    .withClaim("testKey", "testValue")
                    .withExpiresAt(Date.from(LocalDateTime.now().plusDays(3).toInstant(ZoneOffset.UTC)))
                    .sign(algorithm);
            return TokenDto.builder().accessToken(token).build();
        } catch (JWTCreationException exception) {
            //Invalid Signing configuration / Couldn't convert Claims.
        }
        return null;
    }

}

请求资源

curl \
  "http://localhost:8080/login"

返回

{
    "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsInRlc3RLZXkiOiJ0ZXN0VmFsdWUiLCJleHAiOjE2NTE2NjQxMzh9.EW5XMP5Z1YY_3XFEmojUcUh601i-OuBwgMBkRh453Q8"
}

Base64 解密

header

{
  "typ": "JWT",
  "alg": "HS256"
}

payload

{
  "iss": "auth0",
  "testKey": "testValue",
  "exp": 1651664138
}

signature

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  "secret"
)

请求资源

拦截器,异常返回401

@Slf4j
@Component
public class TokenFilter implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            String authorization = request.getHeader("Authorization");
            String token = authorization.split(" ")[1];
            Algorithm algorithm = Algorithm.HMAC256("secret");
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer("auth0")
                    .build(); //Reusable verifier instance
            DecodedJWT jwt = verifier.verify(token);
            log.info("header#{}, payload#{}, sign#{}, jwt#{}", jwt.getHeader(), jwt.getPayload(), jwt.getSignature(), jwt);
        } catch (Exception exception){
            //Invalid signature/claims
            log.error("exception#{}", exception.getMessage(), exception);
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }
}

拦截器配置

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private TokenFilter tokenFilter;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tokenFilter).addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}

模拟资源接口

@RestController
public class ResourceController {

    @GetMapping("/resource")
    public String resource() {
        return "resource";
    }

}

请求资源

curl \
  -H "Authorization: bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsInRlc3RLZXkiOiJ0ZXN0VmFsdWUiLCJleHAiOjE2NTE2NjQxMzh9.EW5XMP5Z1YY_3XFEmojUcUh601i-OuBwgMBkRh453Q8" \
  "http://localhost:8080/resource"

解码 API

String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsInRlc3RLZXkiOiJ0ZXN0VmFsdWUiLCJleHAiOjE2NTE2NjQxMzh9.EW5XMP5Z1YY_3XFEmojUcUh601i-OuBwgMBkRh453Q8";
try {
    DecodedJWT jwt = JWT.decode(token);
    System.out.println("Header#" + jwt.getHeader());
    System.out.println("Payload#" + jwt.getPayload());
    System.out.println("Signature#" + jwt.getSignature());
    System.out.println("Token#" + jwt.getToken());
    System.out.println("Type#" + jwt.getType());
    System.out.println("Algorithm#" + jwt.getAlgorithm());
    System.out.println("Issuer#" + jwt.getIssuer());
    System.out.println("Claims#" + jwt.getClaims());
    System.out.println("ExpiresAt#" + jwt.getExpiresAt());
    System.out.println("Id#" + jwt.getId());
    System.out.println("KeyId#" + jwt.getKeyId());
    System.out.println("Subject#" + jwt.getSubject());
    System.out.println("Audience#" + jwt.getAudience());
    System.out.println("ContentType#" + jwt.getContentType());
} catch (JWTDecodeException exception){
    //Invalid token
}

输出

Header#eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
Payload#eyJpc3MiOiJhdXRoMCIsInRlc3RLZXkiOiJ0ZXN0VmFsdWUiLCJleHAiOjE2NTE2NjQxMzh9
Signature#EW5XMP5Z1YY_3XFEmojUcUh601i-OuBwgMBkRh453Q8
Token#eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJhdXRoMCIsInRlc3RLZXkiOiJ0ZXN0VmFsdWUiLCJleHAiOjE2NTE2NjQxMzh9.EW5XMP5Z1YY_3XFEmojUcUh601i-OuBwgMBkRh453Q8
Type#JWT
Algorithm#HS256
Issuer#auth0
Claims#{iss="auth0", testKey="testValue", exp=1651664138}
ExpiresAt#Wed May 04 19:35:38 CST 2022
Id#null
KeyId#null
Subject#null
Audience#null
ContentType#null

JWT 缺点

无法满足如下场景

  • 退出登录(Token泄露,有效期内Token仍然有效)
  • 修改密码(Token泄露,修改密码后Token仍然有效)
  • Token续签(需通过refresh token再次刷新)

Token 续签流程

  1. 前端请求资源,服务端校验access_token过期,返回401
  2. 前端携带refresh_token请求刷新Token,服务端校验refresh_token,并返回新Token
  3. 前端使用最新Token请求资源。

官网

https://jwt.io

https://github.com/auth0/java-jwt

Views: 1,174 · Posted: 2022-09-30

————        END        ————

Give me a Star, Thanks:)

https://github.com/fendoudebb/LiteNote

扫描下方二维码关注公众号和小程序↓↓↓

扫描下方二维码关注公众号和小程序↓↓↓
Today On History
Browsing Refresh