从零开始的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(); } }
以上。
分类:
springboot
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端