使用JWT身份认证

一、简介

JWT (Json Web Token)是一种基于JSON的开放标准。JWT本身没有定义任何技术实现,它只是定义了一种基于Token的会话管理的规则,涵盖Token需要包含的标准内容和Token的生成过程,广泛用于用户认证方面。

二、构成与特点

  1. 构成
    一个完整JWT token 如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxMjM0NSIsImlhdCI6MTY2NjYzNzYzM30._wyhiKoiOOmhz_2UaSskkepHx0wlIm3ug8HjJzp2J08

由 . 分割的三部分组成,依次分别是头部(Header)、负载(Payload)、签名(Signature),都分别经过Base64编码组成
在这里插入图片描述

  • Header
    Header是一个json,它存储了所使用的加密算法和Token类型

  • Payload
    Payload也是一个json,有7个官方字段可选用,也可自定义字段,但要注意不能放隐私信息,因为这里是经过Base64编码的,别人可以通过解码读取到

    • iss (issuer) : 签发人
    • exp (expiration time) : 过期时间
    • sub (subject) : 主题
    • aud (audience) : 受众
    • nbf (Not Before) : 生效时间
    • iat (Issued At) : 签发时间
    • jti (JWT ID) : 编号
  • Signature
    这部分是对前面两部分的签名,签名公式

base64url(
    HMACSHA256(
      base64UrlEncode(header) + "." + base64UrlEncode(payload),
      your-256-bit-secret (秘钥加盐)
    )
)
  1. 特点
  • 安全性
    JWT的payload使用Base64进行编码,所以是jwt中不能存储敏感数据,也不建议使用http协议传输,而是采用加密的https传输

  • 无状态
    JWT是无状态的,生成的token无法修改,想要修改只能重新签发新token

  • 无法废弃
    签发的token在到期之前都是一直有效的,无法中途废弃,可以考虑结合redis实现废弃token功能

  • 性能
    JWT性能开销比传统大,因为编码后的JWT比较长,通常JWT存放在header里,请求时有可能出现header比body还大,而如果是传统token就是很短的一个字符串

JWT旨在验证时不用和数据库交互,但实际很多人都结合redis使用,以补齐token废弃、续签等短板,而JWT就成了一个token操作工具,说白了怎么方便怎么来。常见应用选型,例如管理后台这些使用传统token,而像有需要兼备ios android h5多端验证时,使用jwt明显会方便很多,还不用考虑跨域问题。

三、认证流程

在这里插入图片描述

四、实战

  1. 依赖
<dependency>
   <groupId>com.auth0</groupId>
   <artifactId>java-jwt</artifactId>
   <version>4.1.0</version>
</dependency>
  1. Token类
@Service
public class TokenService {
    @Value("{jwt.secret}")
    private String secret;
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 生成Token
     * @param userId
     * @return
     */
    public String buildToken(String userId) {
        if (StringUtils.isEmpty(userId)) {
            throw new LoginException(StateCode.USER_NOT_FOUND);
        }
        String key = RedisEnum.TOKEN_VERSION.buildKey(userId);
        Long value = redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, RedisEnum.TOKEN_VERSION.getExpiredTime(), TimeUnit.SECONDS);
        Date now = new Date();
        return JWT.create()
                .withAudience(userId)
                .withJWTId(String.valueOf(value))
                .withIssuedAt(now)
//                .withExpiresAt()
                .sign(Algorithm.HMAC256(userId + secret));
    }
    /**
     * 校验Token
     * @param token
     * @return
     */
    public String verifyToken(String token) {
        DecodedJWT decoded = JWT.decode(token);
        String userId = decoded.getAudience().get(0);
        String tokenVersion = decoded.getClaim("jti").asString();
        JWTVerifier verifier = JWT.require(Algorithm.HMAC256(userId + secret)).build();
        verifier.verify(token);
        // 从redis校验token version
        String key = RedisEnum.TOKEN_VERSION.buildKey(userId);
        String value = redisTemplate.opsForValue().get(key);
        // 登录已过期
        if (StringUtils.isEmpty(value) || !tokenVersion.equals(value)) {
            throw new LoginException(StateCode.TOKEN_INVALID);
        }
        return userId;
    }
    /**
     * 清除token
     * @param userId
     */
    public void clearToken(String userId) {
        String key = RedisEnum.TOKEN_VERSION.buildKey(userId);
        redisTemplate.opsForValue().increment(key);
        redisTemplate.expire(key, RedisEnum.TOKEN_VERSION.getExpiredTime(), TimeUnit.SECONDS);
    }
}
  1. 拦截器
/**
 * 拦截器
 */
public class AuthenticationInterceptor implements HandlerInterceptor {
    @Autowired
    private TokenService tokenService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("token");
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        Method method = handlerMethod.getMethod();
        //检查是否有PassToken注释,有则跳过认证
        Annotation[] annotations = method.getAnnotations();
        if (method.isAnnotationPresent(PassToken.class)) {
            PassToken passToken = method.getAnnotation(PassToken.class);
            if (passToken.required()) {
                if (!StringUtils.isEmpty(token)) {
                    try {
                        String userId = JWT.decode(token).getAudience().get(0);
                        request.setAttribute(UserConstant.USER_ID, userId);
                    } catch (JWTDecodeException j) {
                        //允许无token
                    }
                }
                return true;
            }
        }
        if (StringUtils.isEmpty(token)) {
            throw new LoginException(StateCode.LOGIN_NOT_TOKEN);
        }
        try {
            //校验token
            String userId = tokenService.verifyToken(token);
            // 校验黑名单...
            request.setAttribute(UserConstant.USER_ID, userId);
        } catch (JWTDecodeException d) {
            throw new LoginException(StateCode.LOGIN_PARAM_ERROR);
        } catch (JWTVerificationException v) {
            throw new LoginException(StateCode.TOKEN_INVALID);
        }
        return true;
    }
}
  1. 拦截器配置类
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticateInterceptor())
                .excludePathPatterns("/user/accountLogin")
                .addPathPatterns("/**");
    }
    @Bean
    public AuthenticationInterceptor authenticateInterceptor() {
        return new AuthenticationInterceptor();
    }
}
  1. PassToken注解
/**
 *注解绕过token认证
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
    boolean required() default true;
}
  1. 登录业务
/**
 * 账号登录
 * @param apiRequest
 * @return
 */
@PostMapping("/accountLogin")
public ApiResponse<String> accountLogin(@RequestBody ApiRequest<AccountLoginReq> apiRequest) {
    AccountLoginReq data = apiRequest.getData();
    UserDTO userDTO = userService.getUserByName(data.getUserName());
    //用户登录验证...
    //验证通过生成token
    String userId = "zyz";//临时写死
    String token = tokenService.buildToken(userId);
    return ApiResponse.ok(token);
}
  1. 登出业务
/**
 * 退出登录
 * @param apiRequest
 * @return
 */
@PostMapping("/logout")
public ApiResponse<String> logout(@RequestBody ApiRequest<Void> apiRequest, HttpServletRequest request) {
    String userId = (String) request.getAttribute(UserConstant.USER_ID);
    tokenService.clearToken(userId);
    return ApiResponse.ok("登出成功!");
}

Github项目地址:https://github.com/zyz1130083243/JwtDemo.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值