Spring boot使用JWT来做接口登录验证
一.什么是JWT
JWT(JSON Web Token)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519)。该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可以直接被用于认证,也可以被加密。
JWT官网:https://jwt.io
JWT的构成分三部分,头部(header)、载荷(payload)、签证(signature)。
header是声明类型和声明加密的算法,一般用HMAC。
playload是存放有效信息。
signature是对header和playload分别用base64加密后,将这两个结果拼接字符串,再用secret密钥来进行HMAC加密。
JWT为这个三部分拼接起来的字符串,算法如下伪方法。
JWT = base64(header) + '.' + base64(payload) + '.' + HMAC(base64(header) + '.' + base64(payload) ,secret)。
二.应用
1.准备工作
创建一个spring boot项目,安装相关包。pom.xml添加如下。
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.3</version> </dependency>
创建User实体。
public class User { private String name; private String password; //get.set... }
2.登录获取token
创建登录接口。登录完成后,后端生成token返回给前端。
@RequestMapping("/User") @RestController public class UserController { @Autowired ITokenService tokenService; // 登录 @RequestMapping(value = "/login", method = RequestMethod.GET) public String login(User user) { String mes = ""; if (!user.getName().equals("test") && !user.getPassword().equals("123")) { mes = "登录失败,密码错误"; } else { String token = tokenService.getToken(user); mes = token; } return mes; } }
生成token,设置有效时间、信息、加密方式。
@Service public class TokenService implements ITokenService { public String getToken(User user) { Date start = new Date(); long currentTime = System.currentTimeMillis() + 60* 60 * 1000;//一小时有效时间 Date end = new Date(currentTime); String token = ""; token = JWT.create().withAudience(user.getName()).withIssuedAt(start).withExpiresAt(end) .sign(Algorithm.HMAC256(user.getPassword())); return token; } }
调用登录接口,获得返回的token。
3.做其它接口的token校验
创建注解,用于接口上的。
//用来跳过验证的 PassToken @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PassToken { boolean required() default true; } //用于登录后才能操作的token @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface UserLoginToken { boolean required() default true; }
定义一个拦截器。
public class AuthenticationInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token // 如果不是映射到方法直接通过 if (!(object instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); //检查是否有passtoken注解,有则跳过认证 if (method.isAnnotationPresent(PassToken.class)) { PassToken passToken = method.getAnnotation(PassToken.class); if (passToken.required()) { return true; } } //检查有没有需要用户权限的注解 if (method.isAnnotationPresent(UserLoginToken.class)) { UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class); if (userLoginToken.required()) { // 执行认证 if (token == null) { throw new RuntimeException("无token,请重新登录"); } // 获取 token 中的 user name String userName; try { userName = JWT.decode(token).getAudience().get(0); } catch (JWTDecodeException j) { throw new RuntimeException("401"); } if (!userName.equals("test")) { throw new RuntimeException("用户不存在,请重新登录"); } // 验证 token JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("123")).build(); try { jwtVerifier.verify(token); } catch (JWTVerificationException e) { throw new RuntimeException("401"); } return true; } } return true; } }
配置拦截config。
@Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor()) .addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @UserLoginToken 注解 决定是否需要登录 } @Bean public AuthenticationInterceptor authenticationInterceptor() { return new AuthenticationInterceptor(); } }
解析token信息的工具方法。
public class TokenUtil { public static String getTokenUserName() { String token = getRequest().getHeader("token");// 从 http 请求头中取出 token String name = JWT.decode(token).getAudience().get(0); return name; } /** * 获取request * * @return */ public static HttpServletRequest getRequest() { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); return requestAttributes == null ? null : requestAttributes.getRequest(); } }
创建一个获取信息的接口,用注解来声明这是一个需要校验的接口。
// 这个请求需要验证token才能访问 @UserLoginToken @RequestMapping(value = "/getMessage" ,method = RequestMethod.GET) public String getMessage() { // 取出token中带的用户name 进行操作 System.out.println(TokenUtil.getTokenUserName()); return "您已通过验证"; }
接口调用,在接口的header中添加token,值为之前获取到的token。token值正确才能获取到返回的信息。