• JWT(JSON WEB TOKEN)


    传统session认证

    传统session,cookie的认证方式,第一次请求是会把用户信息存到服务器内存中,并返回一个JSESSIONId给请求端,之后每次请求都需要携带jsessionId来做认证

    传统session,cookie的不足:

    JWT官网

    JSON Web Token Libraries - jwt.io

    官方案例:

    pom坐标

    
        com.auth0
        java-jwt
        3.18.3
    

    JWT的结构:

    payload中信息可能被拦截到且可以用base64解码,所以中仅仅放非敏感信息,例如用户名 、appId。而不是用户密码等敏感信息

     另外加密过程中的秘钥不能暴露

    JWTCreator生成器核心源代码

    1. public final class JWTCreator {
    2. private final Algorithm algorithm;
    3. // 头json
    4. private final String headerJson;
    5. // 负载json
    6. private final String payloadJson;
    7. public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException {
    8. if (algorithm == null) {
    9. throw new IllegalArgumentException("The Algorithm cannot be null.");
    10. }
    11. // 给header中赋值
    12. headerClaims.put(PublicClaims.ALGORITHM, algorithm.getName());
    13. if (!headerClaims.containsKey(PublicClaims.TYPE)) {
    14. headerClaims.put(PublicClaims.TYPE, "JWT");
    15. }
    16. String signingKeyId = algorithm.getSigningKeyId();
    17. if (signingKeyId != null) {
    18. withKeyId(signingKeyId);
    19. }
    20. // 先调用创建jwtCreator对象方法,在调用sign生成token
    21. return new JWTCreator(algorithm, headerClaims, payloadClaims).sign();
    22. }
    23. // 创建jwtCreator对象方法
    24. private JWTCreator(Algorithm algorithm, Map headerClaims, Map payloadClaims) throws JWTCreationException {
    25. this.algorithm = algorithm;
    26. try {
    27. // map转String 默认值:"{"typ":"JWT","alg":"HS256"}"
    28. headerJson = mapper.writeValueAsString(headerClaims);
    29. payloadJson = mapper.writeValueAsString(new ClaimsHolder(payloadClaims));
    30. } catch (JsonProcessingException e) {
    31. throw new JWTCreationException("Some of the Claims couldn't be converted to a valid JSON format.", e);
    32. }
    33. }
    34. // 签名获取token
    35. private String sign() throws SignatureGenerationException {
    36. String header = Base64.getUrlEncoder().withoutPadding().encodeToString(headerJson.getBytes(StandardCharsets.UTF_8));
    37. String payload = Base64.getUrlEncoder().withoutPadding().encodeToString(payloadJson.getBytes(StandardCharsets.UTF_8));
    38. byte[] signatureBytes = algorithm.sign(header.getBytes(StandardCharsets.UTF_8), payload.getBytes(StandardCharsets.UTF_8));
    39. String signature = Base64.getUrlEncoder().withoutPadding().encodeToString((signatureBytes));
    40. // 格式 header.payload.signature
    41. return String.format("%s.%s.%s", header, payload, signature);
    42. }
    43. }

    调用JWTCreator生成token工具类:

    1. public class JWTUtils {
    2. private static final String PAYLOAD_ISSUER = "xiaozhen";
    3. private static final String PAYLOAD_COMPANY_ID = "company_id";
    4. private static final String PAYLOAD_APP_ID = "app_id";
    5. /**
    6. * 生成token timestamp代表sign的生成时间
    7. * token格式:由三部分组成header.payload.signature,中间用点分割
    8. * 案例:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5ZGwiLCJjb21wYW55X3VpZCI6IjEyIiwiZXhwIjoxNjY5MDI0ODYxLCJhcHBfaWQiOiJzZGYiLCJpYXQiOjE2NjkwMTc2NjF9.cy3KN5bOMzzYh4pcTR0pbfFeMD7cl5jqeFXYgxz4JeY
    9. */
    10. public static TokenDTO generateToken(String appId, String sign, Long timestamp) {
    11. // 查业务数据库 根据appId查询appSecret等秘钥信息 (秘钥不能暴露否则校验就没用了)
    12. String appSecret = "数据库存储的秘钥";
    13. private static final Algorithm TOKEN_ALGORITHM = Algorithm.HMAC256(appSecret);
    14. // 检查签名
    15. String assertSign = DigestUtils.md5Hex(appId + appSecret);
    16. if (!sign.equals(assertSign)) {
    17. throw new XiaoZhenException("code", "签名错误");
    18. }
    19. // 是否过期
    20. boolean expired = (System.currentTimeMillis() - timestamp) > 7200000;
    21. if (expired) {
    22. throw new XiaoZhenException("code", "签名过期");
    23. }
    24. // 设置Token过期时间
    25. long issuedAt = System.currentTimeMillis();
    26. long expiresAt = issuedAt + 7200000;
    27. // 生成Token, with开头的方法均是在设置payload
    28. String token = JWT.create()
    29. .withIssuer(PAYLOAD_ISSUER)
    30. .withClaim(PAYLOAD_COMPANY_ID, "主体id")
    31. .withClaim(PAYLOAD_APP_ID, "appId")
    32. .withIssuedAt(new Date(issuedAt))
    33. .withExpiresAt(new Date(expiresAt))
    34. .sign(TOKEN_ALGORITHM);
    35. log.info("generateToken token: {}", token);
    36. return new TokenDTO()
    37. .setAccessToken(token)
    38. .setExpiresAt(expiresAt);
    39. }
    40. /**
    41. * 返回sign,timestamp生成时间
    42. */
    43. public Map getSign (String appId) {
    44. // 查业务数据库 根据appId查询appSecret等秘钥信息 (秘钥不能暴露否则校验就没用了)
    45. String appSecret = "数据库存储的秘钥";
    46. // 根据appSecret,appId 生产sign
    47. map.put("sign",sign);
    48. map.put(Constants.TIMESTAMP,String.valueOf(timeStamp));
    49. return map;
    50. }
    51. }

    拦截器验证token

    1. @Component
    2. public class OpenInterceptor extends HandlerInterceptorAdapter {
    3. private static final Logger log = LoggerFactory.getLogger(OpenInterceptor.class);
    4. @Override
    5. public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
    6. // 获取token
    7. String token = req.getHeader("Authorization");
    8. if (StringUtils.isEmpty(token)) {
    9. resp.setStatus(HttpStatus.UNAUTHORIZED.value());
    10. return false;
    11. }
    12. Boolean result = false;
    13. try {
    14. result = bgTokenFacade.parseToken(token);
    15. // jwt验证器
    16. JWTVerifier verifier = JWT.require(TOKEN_ALGORITHM)
    17. .withIssuer(TOKEN_ISSUER)
    18. .build();
    19. /**
    20. * 验证token
    21. * 源码原理:把token按hearder.payload.signature格式拆分,
    22. * 重新用header和payload生成签名, 和入参中token的signature部分比较,
    23. * 如果一致说明请求合法,否则为非法请求
    24. * (这样不论header、payload、signature哪个被篡改都不会验证通过)
    25. */
    26. verifier.verify(accessToken);
    27. } catch (Exception e) {
    28. log.error("解析token异常", e);
    29. resp.setStatus(HttpStatus.UNAUTHORIZED.value());
    30. return false;
    31. }
    32. return result;
    33. }
    34. }

    生成token的api层:

    1. @RestController
    2. @RequestMapping("/v1/open/oauth")
    3. @Slf4j
    4. public class BgAuthController {
    5. @RequestMapping(value = "/getSign", method = RequestMethod.GET)
    6. @ApiOperation(value = "获取验签")
    7. public Map getSign(@RequestParam(value = "appKey",required = true)String appId) {
    8. return JWTUtils.getSign(appId);
    9. }
    10. /**
    11. * 获取 accessToken
    12. */
    13. @ApiOperation(value = "获取 accessToken")
    14. @PostMapping("/token")
    15. public TokenDTO token(@RequestBody @Valid TokenReq req) {
    16. return JWTUtils.generateToken(req.getAppId(),req.getSign, req.getTimestamp());
    17. }
    18. }

    生成的token案例:

    格式 header.payload.signature

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5ZGwiLCJjb21wYW55X3VpZCI6IjEyIiwiZXhwIjoxNjY5MDI0ODYxLCJhcHBfaWQiOiJzZGYiLCJpYXQiOjE2NjkwMTc2NjF9.cy3KN5bOMzzYh4pcTR0pbfFeMD7cl5jqeFXYgxz4JeY

    springBoot设置拦截器配置需要token验证的api

    1. @Configuration
    2. public class CustomWebMvcConfigurer implements WebMvcConfigurer {
    3. @Override
    4. public void addInterceptors(InterceptorRegistry registry) {
    5. WebMvcConfigurer.super.addInterceptors(registry);
    6. //token验证api拦截器
    7. registry.addInterceptor(openInterceptor)
    8. .addPathPatterns("/v1/open/**") // 此contorller业务api均拦截
    9. .excludePathPatterns("/v1/open/oauth/**"); //生成token的api不需拦截
    10. }
    11. }
    12. // 自定义实现拦截器,拦截器中校验token
    13. @Component
    14. public class OpenInterceptor extends HandlerInterceptorAdapter {
    15. @Override
    16. public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
    17. // 获取token
    18. String token = req.getHeader("Authorization");
    19. // 解析token
    20. Boolean result = JWTUtils.verifierToken();
    21. // 可定义ThreadLocal 把请求头中的用户信息保存起来,供本线程全局使用
    22. // todo private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>();
    23. return result;
    24. }
    25. }

  • 相关阅读:
    04.使用 github actions+docker 自动部署前后端分离项目 zhontai (.net core+vue)
    【前端实例代码】使用HTML CSS 和 JavaScript制作一个五星评价的功能!可动态好评+差评+留言功能!
    PTA 7-2 简单计算器
    线索二叉树与Morris遍历
    避免按钮重复点击的小工具bimianchongfu.queren()
    面试考频最高(没有之一)——“谈谈进程和线程的区别?”我来教你如何回答~
    vs2010 webapi开发http请求以及website中如何实现http请求
    十一、组合API(3)
    短视频平台如何保证内容安全问题?
    如何设计数据可视化平台
  • 原文地址:https://blog.csdn.net/qq_36042938/article/details/127964239