Java OpenResty Spring Spring Boot MySQL Redis MongoDB PostgreSQL Linux Android Nginx 面试 小程序 Arthas JVM AQS juc Kubernetes Docker DevOps


Spring Boot 整合 JWT JSON Web Token

Spring Boot JWT 大约 5002 字

添加依赖

<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

阅读 91 · 发布于 2022-09-30

————        END        ————

Give me a Star, Thanks:)

https://github.com/fendoudebb

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

扫描二维码关注我
昵称:
随便看看 换一批