# JWT-study **Repository Path**: GetOver/JWT-study ## Basic Information - **Project Name**: JWT-study - **Description**: SpringBoot 集成 JWT 实现 token 鉴权 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2020-03-22 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JWT-study ## 介绍 SpringBoot 集成 JWT 实现 token 鉴权 教学视频地址:https://www.bilibili.com/video/av75572951?p=108 # SpringBoot 集成 JWT 实现 token 鉴权 **完整项目地址:https://gitee.com/aoxiaobao/JWT-study.git** *** ## pom依赖 * 首先当然是引入pom依赖 ```javascript io.jsonwebtoken jjwt 0.9.0 ``` ## 一个简单的小例子 *** * 创建 **token** * 通过使用 **Jwts.builder()** * 在 **token** 添加一些简单的用户信息 * 选择一个加密算法 * 就可以创建出一个 **token** * 解析 **token** * 通过 **Jwts.parser()** * 获取其中加密的用户信息 ```javascript public class TestJwt { public static void main(String[] args) { System.out.println(createJwt()); parseJwt(createJwt()); } public static String createJwt(){ JwtBuilder jwtBuilder = Jwts.builder() .setId("888888") // 添加id .setSubject("藏冰") // 添加用户名 .setIssuedAt(new Date()) // 添加当前时间 .signWith(SignatureAlgorithm.HS256,"aowei"); // 设置加密算法,密钥 String token = jwtBuilder.compact(); // 获取token return token; } public static void parseJwt(String token){ Claims claims = Jwts.parser() .setSigningKey("aowei") .parseClaimsJws(token).getBody(); System.out.println(claims.getId()); System.out.println(claims.getSubject()); System.out.println(claims.getIssuedAt()); } } ``` ## JwtUtils 工具类 *** * 将上面的简单例子进一步完善, * 把 **私钥** 和 **签名**的失效时间,单独提取出来 * 还可以在 **token** 中添加自定义的 **Map** 格式数据 * 最后添加上自定义异常的异常处理 ```javascript public class JwtUtils { // 签名私钥 public static final String AUTH_HEADER_KEY = "Authorization"; // 签名的失效时间 private static final long TTL = 10000 ; private static Logger log = LoggerFactory.getLogger(JwtUtils.class); /** * 设置认证token * id:登录用户id * subject:登录用户名 */ public static String createJwt(String id, String name, Map map) throws CustomException { // 设置失效时间 long now = System.currentTimeMillis(); // 当前毫秒数 long exp = now + TTL; // 创建jwtBuilder String token = null; try { JwtBuilder jwtBuilder = Jwts.builder() .setId(id) // 添加id .setSubject(name) // 添加用户名 .setIssuedAt(new Date()) // 添加当前时间 .signWith(SignatureAlgorithm.HS256,AUTH_HEADER_KEY); // 设置加密算法,密钥 // 根据map设置claims for (Map.Entry entry : map.entrySet()){ jwtBuilder.claim(entry.getKey(),entry.getValue()); } // 指定失效时间 jwtBuilder.setExpiration(new Date(exp)); // 创建token token = jwtBuilder.compact(); } catch (Exception e) { log.error("签名失败", e); throw new CustomException(ResultCode.PERMISSION_SIGNATURE_ERROR); } return token; } /** * 解析token字符串,获取clamis */ public static Claims parseJwt(String token) throws CustomException { try { Claims claims = Jwts.parser() .setSigningKey(AUTH_HEADER_KEY) .parseClaimsJws(token).getBody(); return claims; } catch (ExpiredJwtException e) { log.error("===== Token过期 =====", e); throw new CustomException(ResultCode.PERMISSION_TOKEN_EXPIRED); } catch (Exception e) { log.error("===== token解析异常 =====", e); throw new CustomException(ResultCode.PERMISSION_TOKEN_INVALID); } } } ``` ## 自定义JWT拦截器 *** * 创建一个 **token** 拦截器 * 继承 **HandlerInterceptorAdapter** * 有三个常用的方法,这里只需要重写 **preHandle** 方法即可 * 重写 **preHandle**方法(进入到控制器方法之前执行的内容) * 返回值是 boolean类型 * true: 可以继续执行控制器方法 * false: 拦截请求 * postHandler(执行控制器方法之后执行的内容) * afterHandler(响应结束之前执行的内容) * 这里拦截器要实现的功能有 * 简化获取token数据的代码编写 * 统一的用户权限校验(是否登录) * 判断用户是否具有当前访问接口的权限 ```javascript /** * 自定义拦截器 * 继承HandlerInterceptorAdapter * preHandle: 进入到控制器方法之前执行的内容 * boolean: * true: 可以继续执行控制器方法 * false: 拦截 * postHandler: 执行控制器方法之后执行的内容 * afterHandler: 响应结束之前执行的内容 * * 简化获取token数据的代码编写 * 统一的用户权限校验(是否登录) * 判断用户是否具有当前访问接口的权限 * @author aowei */ @Component @Slf4j public class JwtInterceptor extends HandlerInterceptorAdapter { /** * 通过拦截器获取token数据 * 从token中解析获取claims * 将claims绑定到request域中 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 忽略带JwtIgnore注解的请求, 不做后续token认证校验 if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; JwtIgnore jwtIgnore = handlerMethod.getMethodAnnotation(JwtIgnore.class); if (jwtIgnore != null) { return true; } } // 通过request获取请求token信息 String authorization = request.getHeader("Authorization"); // 判断请求头信息是否为空,或是否以Bearer 开头 if(!StringUtils.isEmpty(authorization) && authorization.startsWith("Bearer")){ // 获取token数据 String token = authorization.replace("Bearer",""); // 解析token获取claims Claims claims = JwtUtils.parseJwt(token); if(claims != null){ // 通过claims获取到当前用户的可访问api权限字符串 String apis = (String) claims.get("roles"); // 通过handler String name = null; if (handler instanceof HandlerMethod) { HandlerMethod h = (HandlerMethod) handler; // 获取接口上的requestmapping注解 RequestMapping annotation = h.getMethodAnnotation(RequestMapping.class); // 获取当前请求接口中的name属性 name = annotation.name(); }else { throw new CustomException(ResultCode.INTERFACE_ADDRESS_INVALID); } if(apis.contains(name)){ request.setAttribute("userClaims",claims); return true; }else{ throw new CustomException(ResultCode.PERMISSION_UNAUTHORISE); } } } throw new CustomException(ResultCode.USER_NOT_LOGGED_IN); } } ``` ## 配置拦截器 *** * 拦截器写好之后,还不能真正起到作用 * 这里需要配置一下 JWT 拦截器 * 实现 **WebMvcConfigurer** 接口 * 用 **addPathPatterns()** 方法拦截请求 * /**表示拦截所有的请求 * 可以在 **excludePathPatterns()** 方法中指明不需要拦截的路径 ```javascript @Configuration public class JwtConfig implements WebMvcConfigurer { /** * 添加拦截器的配置 */ @Override public void addInterceptors(InterceptorRegistry registry){ // 添加自定义拦截器 registry.addInterceptor(new JwtInterceptor()) .addPathPatterns("/**"); } /** * 跨域支持 * * @param registry */ @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowCredentials(true) .allowedMethods("GET", "POST", "DELETE", "PUT", "PATCH", "OPTIONS", "HEAD") .maxAge(3600 * 24); } } ``` ## 自定义注解 *** * 通过自定义注解 **@JwtIgnore** * 实现放行某些接口(不拦截请求) ```javascript @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface JwtIgnore { // String value() default "JWT"; } ``` ## 代码测试 *** * 将用户的角色也添加到 **token** 中 * 在拦截器中解析 **token** 获得用户角色 * 再从 **handler** 中获取请求的 **RequestMapping**注解的**name**属性 * 可以在**name**属性里,写上访问该接口所需的权限名称 * 比较 用户角色和 **name** 属性,就可以判断出用户是否有访问该接口的权限 ```javascript @Slf4j @RestController public class Login { @Autowired UserService userService; @Autowired RoleService roleService; @RequestMapping(value = "/login",method = RequestMethod.POST) @JwtIgnore public Result login(String username, String password) throws CustomException { User user = userService.findByUsername(username); if(user == null || !user.getPassword().equals(password)){ return new Result(ResultCode.FAIL); } else { // 登录成功 // 获取到所有的可访问api权限 // StringBuilder sb = new StringBuilder(); // for(Role role : user.getRole()){ // for(Permission perm : role.getPermissions()){ // sb.append(perm.getCode().append(",")); // } // } Role role = roleService.findByUsername(username); Map map = new HashMap<>(); // 可访问的角色 map.put("roles",role.getRoleName()); String token = JwtUtils.createJwt(username,username,map); // Claims claims = JwtUtils.parseJwt(token); // System.out.println("角色:"+claims.get("roles")); // System.out.println("用户名:"+claims.getSubject()); // System.out.println("用户id:"+claims.getId()); Result result = new Result(ResultCode.SUCCESS); result.setData(token); return result; } } // 测试需要权限的url(需要登录且拥有all-admin的权限) @RequestMapping(value = "/test3",method = RequestMethod.GET,name="all-admin") public Result test3(){ return new Result(ResultCode.SUCCESS); } // 测试需要权限的url(需要登录且拥有test2的权限) @RequestMapping(value = "/test2",method = RequestMethod.GET,name="test2") public Result test1(){ return new Result(ResultCode.SUCCESS); } // 测试被拦截的url(需要登录) @RequestMapping(value = "/test1",method = RequestMethod.GET) public Result test2(){ return new Result(ResultCode.SUCCESS); } // 测试不拦截的url(不需要登录) @RequestMapping("/test") @JwtIgnore public Result test(){ return new Result(ResultCode.SUCCESS); } } ```