zl程序教程

您现在的位置是:首页 >  Javascript

当前栏目

java小技能:JWT(json web token)认证实现

2023-02-18 16:34:23 时间

引言

应用场景:

  • 登录授权,它相比原先的session、cookie来说,更快更安全,跨域也不再是问题。
  • 传递数据

I. 预备知识

1.1 关键字去空格处理

错误代码 (keyword+"").trim(); 会将空转为字符串“null” 正确代码: return StringUtils.isBlank(keyword)?keyword:keyword.trim();

1.2 JWT认证流程

JWT不是一个具体的技术实现,而更像是一种标准。JWT规定了数据传输的结构,一串完整的JWT由三段落组成,每个段落用英文句号连接(.)连接,他们分别是:HeaderPayloadSignature,所以,常规的JWT内容格式是这样的:Header.Payload.Signature。并且这一串内容会进行加密;解码就可以看到实际传输的内容。

  • Header:加密的方式、type
{ 
“typ”: “JWT”, 
“alg”: “HS256” 
} 

  • Payload:实际传递的参数内容,推荐对payload内容进行加密。
  • Signature:签名,用于判断HeaderPayload有没有被人篡改;如果被篡改,那么这条JWT将会被视为无效。

内容前面的“Bearer”是固定的,并且还得多加一个空格做分割。

II token组成

生成jwt:sign(Header+Playload+Signature+expiresAt)

        return create(header, claims, JWT_ISSUER, TOKEN_TIMEOUT);

2.1 头部(Header)

头部存储认证类型和加密算法

{ 
“typ”: “JWT”, 
“alg”: “HS256” 
} 

2.2 有效载荷(Playload)

有效载荷中存放了token的签发者(iss)、签发时间(iat)、过期时间(exp)等以及一些我们需要写进token中的信息

{
 "iss": "ios逆向",
 "exp": 1638841050,
 "iat": 1638840690,
 "userId": "1",
 "account": "admin"
}

2.3 签名(Signature)

将Header和Playload拼接生成一个字符串,使用HS256算法和我们提供的密钥(secret,服务器自己提供的一个字符串)对str进行加密生成最终的JWT,即我们需要的令牌(token)。

2.4 代码实现:生成token

生成jwt:sign(Header+Playload+Signature+expiresAt)

            Algorithm algorithm = Algorithm.HMAC256(TOKENKEY);//使用HS256算法加密密钥
            Date date = new Date(System.currentTimeMillis() + timeout);
            JWTCreator.Builder builder = JWT.create()
                    .withHeader(header)
                    .withIssuer(issuer)
                    .withExpiresAt(date);
            for (String key : claims.keySet()) {//Playload
                builder.withClaim(key, claims.get(key));
            }
            token = builder.sign(algorithm);

builder.sign

        public String sign(Algorithm algorithm) throws IllegalArgumentException, JWTCreationException {
            if (algorithm == null) {
                throw new IllegalArgumentException("The Algorithm cannot be null.");
            } else {
                this.headerClaims.put("alg", algorithm.getName());
                if (!this.headerClaims.containsKey("typ")) {
                    this.headerClaims.put("typ", "JWT");
                }

                String signingKeyId = algorithm.getSigningKeyId();
                if (signingKeyId != null) {
                    this.withKeyId(signingKeyId);
                }

                return (new JWTCreator(algorithm, this.headerClaims, this.payloadClaims)).sign();
            }
        }

完整例子

 public static final long TOKEN_EXPIRE_TIME = 6 * 60 * 1000;

/**
     * 生成token
     *
     * @param userId     用户ID
     * @param account    登录名
     * @param userName   用户名称
     * @param role       角色ID集合
     * @param department 部门ID集合
     * @param jwtSecret  生成jwt的秘钥,由网关传入
     * @return token
     */
    public static String generateToken(Long userId, String account, String userName, List<Long> role, List<Long> department, String jwtSecret) {
        Date now = new Date();
        // 加密算法
        Algorithm algorithm = Algorithm.HMAC256(jwtSecret);

        return JWT.create()
                //签发人
                .withIssuer(ISSUER)
                //签发时间
                .withIssuedAt(now)
//                .withSubject()
                //过期时间
                .withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRE_TIME))
                .withClaim("userId", userId)
                .withClaim("userName", userName)
                .withClaim("account", account)
                .withClaim("role", role)
                .withClaim("department", department)
                .sign(algorithm);
    }

III 验证token

3.1 网关验证token

 /**
     * 签发人
     */
    private static final String ISSUER = "iOS逆向";

    /**
     * 验证token是否合法
     *
     * @param token
     * @return
     * JwtSecret为密钥,随机生成
     */
    public static boolean verify(String token) {
        try {
        // 去掉token前缀
            Algorithm algorithm = Algorithm.HMAC256("JwtSecret1");
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer(ISSUER)
                    .build();
DecodedJWT jwtDecode=  verifier.verify(token);
            // 存储token信息
             Map<String, Claim> claims=jwtDecode.getClaims();
        LoginHelper.set(LoginHelper.USER_ID,claims.get(LoginHelper.USER_ID).asString());
            return true;
        } catch (Exception e) {
            log.error("验证token失败 {}", e.getMessage());
        }
        return false;
    }

3.2 使用拦截器验证token

/**
 * 拦截器
 */
@Component
@Slf4j
public class JwtHandler implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws IOException {
        Map<String,Object> map=new HashMap<>();
        String token = Request.getHeaderParam(request, "token");
        try {
            Algorithm algorithm = Algorithm.HMAC256("JwtSecret");
            JWTVerifier verifier = JWT.require(algorithm)
                    .withIssuer("baidu")
                    .build();
            verifier.verify(token);
            return true;
        }catch (Exception e){
            e.printStackTrace();
            map.put("status",false);
            map.put("msg","认证失败");
        }
        //jackson 将map转换为json
        String json=new ObjectMapper().writeValueAsString(map);
        response.getWriter().println(json);
        return false;
    }
}

拦截器注册

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    
    /**
     * 注册拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserHandlerAdapter())
                .addPathPatterns("/user/verify")     //拦截请求路径
                .excludePathPatterns("/user/login"); //不拦截请求路径
    }

}