JWT在项目中的简单应用

    科技2025-08-02  5

    JWT在项目中的简单应用

    JWT介绍

    JWT(JSON WEB TOKEN):JSON网络令牌,JWT是一个轻便的安全跨平台传输格式,定义了一个紧凑的自包含的方式在不同实体之间安全传输信息(JSON格式)。它是在Web环境下两个实体之间传输数据的一项标准。实际上传输的就是一个字符串。广义上讲JWT是一个标准的名称;狭义上JWT指的就是用来传递的那个token字符串。

    JWT的项目应用

    引入依赖:

    <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>

    定义两个自定义注解,LoginToken表示登陆,可以跳过token验证。CheckToken需要做token验证。

    @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LoginToken { boolean required() default true; } @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CheckToken { boolean required() default true; }

    定义生成token的JWT工具类,包含了token的生成、校验、刷新等。

    /** * @ClassName: JwtUtil */ public class JwtUtil { //有效时间,如果有效时间小于20分钟,刷新token public final static Integer checkDate = 20*60*1000; public final static Integer validDate = 30*60*1000; public final static String TOKEN = "token"; /** * 用户登录成功后生成Jwt * 使用Hs256算法 私匙使用用户密码 * * @param ttlMillis jwt过期时间 * @param user 登录成功的user对象 * @return */ public static String createJWT(long ttlMillis, User user) { //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //生成JWT的时间 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); //创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的) Map<String, Object> claims = new HashMap<String, Object>(); claims.put("id", user.getId()); claims.put("username", user.getUsername()); claims.put("password", user.getPassword()); //生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。 String key = user.getPassword(); //生成签发人 String subject = user.getUsername(); //下面就是在为payload添加各种标准声明和私有声明了 //这里其实就是new一个JwtBuilder,设置jwt的body JwtBuilder builder = Jwts.builder() //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setClaims(claims) //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setId(UUID.randomUUID().toString()) //iat: jwt的签发时间 .setIssuedAt(now) //代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 .setSubject(subject) //设置签名使用的签名算法和签名使用的秘钥 .signWith(signatureAlgorithm, key); if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); //设置过期时间 builder.setExpiration(exp); } return builder.compact(); } /** * Token的解密 * @param token 加密后的token * @param user 用户的对象 * @return */ public static Claims parseJWT(String token, User user) { //签名秘钥,和生成的签名的秘钥一模一样 String key = user.getPassword(); //得到DefaultJwtParser Claims claims = Jwts.parser() //设置签名的秘钥 .setSigningKey(key) //设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; } /** * 校验token * 在这里可以使用官方的校验,我这里校验的是token中携带的密码于数据库一致的话就校验通过 * @param token * @param user * @return */ public static String isVerify(String token, User user) { try { //签名秘钥,和生成的签名的秘钥一模一样 String key = user.getPassword(); //得到DefaultJwtParser Claims claims = Jwts.parser() //设置签名的秘钥 .setSigningKey(key) //设置需要解析的jwt .parseClaimsJws(token).getBody(); if (claims == null) { return null; } if (claims.get("password").equals(user.getPassword())) { return refreshJwt(token,claims,user); } return null; } catch (ExpiredJwtException e) { e.printStackTrace(); } catch (UnsupportedJwtException e) { e.printStackTrace(); } catch (MalformedJwtException e) { e.printStackTrace(); } catch (SignatureException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } return null; } /*** * 刷新token,区分不同功能的配置 * @param claims * @return */ private static String refreshJwt(String jwt, Claims claims,User user) { Date exp = claims.getExpiration(); // 当 过期时间-当前时间(分)<配置时间 时刷新 if ( exp.getTime()-System.currentTimeMillis() <= checkDate) { String newjwt = createJWT(validDate, user); if (newjwt != null) { return newjwt; } } return jwt; } }

    定义拦截器:

    /** * @ClassName: AuthenticationInterceptor * @Description: 拦截器 */ public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired UserService userService; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { // 从 http 请求头中取出 token String token = httpServletRequest.getHeader("token"); // 如果不是映射到方法直接通过 if (!(object instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); //检查是否有LoginToken注释,有则跳过认证 if (method.isAnnotationPresent(LoginToken.class)) { LoginToken loginToken = method.getAnnotation(LoginToken.class); if (loginToken.required()) { return true; } } //检查有没有需要用户权限的注解 if (method.isAnnotationPresent(CheckToken.class)) { CheckToken checkToken = method.getAnnotation(CheckToken.class); if (checkToken.required()) { // 执行认证 if (token == null) { throw new RuntimeException("无token,请重新登录"); } // 获取 token 中的 user id String userId; try { userId = JWT.decode(token).getClaim("id").asString(); } catch (JWTDecodeException j) { throw new RuntimeException("访问异常!"); } User user = userService.findUserById(userId); if (user == null) { throw new RuntimeException("用户不存在,请重新登录"); } String jwt = JwtUtil.isVerify(token, user); if (StringUtils.isEmpty(jwt)) { throw new RuntimeException("非法访问!"); } httpServletResponse.setHeader(JwtUtil.TOKEN,jwt); return true; } } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }

    拦截器的配置:

    @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor()) .addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录 } @Bean public AuthenticationInterceptor authenticationInterceptor() { return new AuthenticationInterceptor(); } }

    测试方法

    @RestController @RequestMapping("/api") public class UserController { @Autowired private UserService userService; //登录 @PostMapping("/login") @LoginToken public Object login(@RequestBody @Valid User user) { JSONObject jsonObject = new JSONObject(); User userForBase = userService.findByUsername(user); if (userForBase == null) { jsonObject.put("message", "登录失败,用户不存在"); return jsonObject; } else { if (!userForBase.getPassword().equals(user.getPassword())) { jsonObject.put("message", "登录失败,密码错误"); return jsonObject; } else { String token = JwtUtil.createJWT(JwtUtil.validDate, userForBase); jsonObject.put("token", token); jsonObject.put("user", userForBase); return jsonObject; } } } //查看个人信息 @CheckToken @GetMapping("/getMessage") public String getMessage() { return "你已通过验证"; } }

    测试结果 登陆:

    校验:

    关注公众号免费领取学习资料~

    Processed: 0.010, SQL: 8