敞开成长之旅!这是我参加「日新方案 12 月更文应战」的第26天,点击检查活动概况

引言

应用场景:

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

I. 准备常识

1.1 关键字去空格处理

java小技能:JWT(json web token)认证实现
错误代码 (keyword+"").trim(); 会将空转为字符串“null” 正确代码: return StringUtils.isBlank(keyword)?keyword:keyword.trim();

1.2 JWT认证流程

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

JWT不是一个详细的技能完成,而更像是一种规范。JWT规则了数据传输的结构,一串完整的JWT由三阶段组成,每个阶段用英文句号连接(.)连接,他们分别是:HeaderPayloadSignature,所以,常规的JWT内容格局是这样的:Header.Payload.Signature。而且这一串内容会进行加密;解码就可以看到实践传输的内容。

  • Header: 加密的方法、type
{
“typ”: “JWT”, 
“alg”: “HS256” 
} 
  • Payload: 实践传递的参数内容,推荐对payload内容进行加密。

  • Signature: 签名,用于判别HeaderPayload有没有被人篡改;假如被篡改,那么这条JWT将会被视为无效。

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

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

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"); //不阻拦恳求路径
    }
}