从零开始的SpringBoot项目 ( 八 ) 实现基于Token的用户身份验证
1.首先了解一下Token
- uid: 用户唯一身份标识
- time: 当前时间的时间戳
- sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
- 固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查数据库
2.token 验证的机制(流程)
- 用户登录校验,校验成功后就返回Token给客户端。
- 客户端收到数据后保存在客户端
- 客户端每次访问API是携带Token到服务器端。
- 服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码
3.使用SpringBoot搭建基于token验证
3.1 引入 POM 依赖
<!--Json web token--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
3.2 新建一个拦截器配置 用于拦截前端请求 实现 WebMvcConfigurer
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor()) .addPathPatterns("/**");//拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录 } @Bean public AuthenticationInterceptor authenticationInterceptor() { return new AuthenticationInterceptor(); } }
3.3 新建一个 AuthenticationInterceptor 实现HandlerInterceptor接口 实现拦截还是放通的逻辑
import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.my_springboot.rbac.pojo.Admin; import com.my_springboot.rbac.service.IAdminService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * 拦截器去获取token并验证token*/ public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired private IAdminService adminService; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) { 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 id String adminId; try { adminId = JWT.decode (token).getAudience ().get (0); } catch (JWTDecodeException j) { throw new RuntimeException ("401"); } Admin admin = adminService.getById (adminId); if (admin == null) { throw new RuntimeException ("用户不存在"); } // 验证 token JWTVerifier jwtVerifier = JWT.require (Algorithm.HMAC256 (admin.getPassword ())).build (); try { jwtVerifier.verify (token); } catch (JWTVerificationException e) { throw new RuntimeException ("401"); } 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 { } }
3.4 新建两个注解 用于标识请求是否需要进行Token 验证
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 需要登录才能进行操作的注解UserLoginToken*/ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface UserLoginToken { boolean required() default true; }
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 用来跳过验证的PassToken*/ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PassToken { boolean required() default true; }
3.5 新建一个Service用于下发Token
import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.my_springboot.rbac.pojo.Admin; import org.springframework.stereotype.Service; import java.util.Date; /** * 下发token*/ @Service public class TokenService { public String getToken(Admin admin) { Date start = new Date (); long currentTime = System.currentTimeMillis () + 60 * 60 * 1000;//一小时有效时间 Date end = new Date (currentTime); return JWT.create ().withAudience (admin.getId ()).withIssuedAt (start) .withExpiresAt (end) .sign (Algorithm.HMAC256 (admin.getPassword ())); } }
3.6 新建一个工具类 用户从token中取出用户Id
import com.auth0.jwt.JWT; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * token工具类*/ public class TokenUtil { public static String getTokenUserId() { String token = getRequest().getHeader("token");// 从 http 请求头中取出 token String userId = JWT.decode(token).getAudience().get(0); return userId; } /** * 获取request * * @return */ public static HttpServletRequest getRequest() { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); return requestAttributes == null ? null : requestAttributes.getRequest(); } }
以上。