1.首先了解一下Token
1、token也称作令牌,由uid+time+sign[+固定参数]组成:
- uid: 用户唯一身份标识
- time: 当前时间的时间戳
- sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
- 固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查数据库
2.token 验证的机制(流程)
- 用户登录校验,校验成功后就返回Token给客户端。
- 客户端收到数据后保存在客户端
- 客户端每次访问API是携带Token到服务器端。
- 服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码
3.使用SpringBoot搭建基于token验证
3.1 引入 POM 依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
3.2 新建一个拦截器配置 用于拦截前端请求 实现 WebMvcConfigurer
1 /***
2 * 新建Token拦截器
3 * @Title: InterceptorConfig.java
4 * @author MRC
5 * @date 2019年5月27日 下午5:33:28
6 * @version V1.0
7 */
8 @Configuration
9 public class InterceptorConfig implements WebMvcConfigurer {
10 @Override
11 public void addInterceptors(InterceptorRegistry registry) {
12 registry.addInterceptor(authenticationInterceptor())
13 .addPathPatterns("/**"); // 拦截所有请求,通过判断是否有 @LoginRequired 注解 决定是否需要登录
14 }
15 @Bean
16 public AuthenticationInterceptor authenticationInterceptor() {
17 return new AuthenticationInterceptor();// 自己写的拦截器
18 } //省略其他重写方法
19
20 }
3.3 新建一个 AuthenticationInterceptor 实现HandlerInterceptor接口 实现拦截还是放通的逻辑
1 public class AuthenticationInterceptor implements HandlerInterceptor {
2 @Autowired
3 UserService userService;
4 @Override
5 public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
6 String token = httpServletRequest.getHeader("token");// 从 http 请求头中取出 token
7 // 如果不是映射到方法直接通过
8 if(!(object instanceof HandlerMethod)){
9 return true;
10 }
11 HandlerMethod handlerMethod=(HandlerMethod)object;
12 Method method=handlerMethod.getMethod();
13 //检查是否有passtoken注释,有则跳过认证
14 if (method.isAnnotationPresent(PassToken.class)) {
15 PassToken passToken = method.getAnnotation(PassToken.class);
16 if (passToken.required()) {
17 return true;
18 }
19 }
20 //检查有没有需要用户权限的注解
21 if (method.isAnnotationPresent(UserLoginToken.class)) {
22 UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
23 if (userLoginToken.required()) {
24 // 执行认证
25 if (token == null) {
26 throw new RuntimeException("无token,请重新登录");
27 }
28 // 获取 token 中的 user id
29 String userId;
30 try {
31 userId = JWT.decode(token).getAudience().get(0);
32 } catch (JWTDecodeException j) {
33 throw new RuntimeException("401");
34 }
35 User user = userService.findUserById(userId);
36 if (user == null) {
37 throw new RuntimeException("用户不存在,请重新登录");
38 }
39 // 验证 token
40 JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
41 try {
42 jwtVerifier.verify(token);
43 } catch (JWTVerificationException e) {
44 throw new RuntimeException("401");
45 }
46 return true;
47 }
48 }
49 return true;
50 }
51
52 @Override
53 public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
54
55 }
56 @Override
57 public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
58
59 }
60 }
3.4 新建两个注解 用于标识请求是否需要进行Token 验证
/***
* 用来跳过验证的 PassToken
* @author MRC
* @date 2019年4月4日 下午7:01:25
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
/**
* 用于登录后才能操作
* @author MRC
* @date 2019年4月4日 下午7:02:00
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
3.5 新建一个Server 用于下发Token
/***
* token 下发
* @Title: TokenService.java
* @author MRC
* @date 2019年5月27日 下午5:40:25
* @version V1.0
*/
@Service("TokenService")
public class TokenService {
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.getId()).withIssuedAt(start).withExpiresAt(end)
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}
}
3.6 新建一个工具类 用户从token中取出用户Id
1 /*
2 * @author MRC
3 * @date 2019年4月5日 下午1:14:53
4 * @version 1.0
5 */
6 public class TokenUtil {
7
8 public static String getTokenUserId() {
9 String token = getRequest().getHeader("token");// 从 http 请求头中取出 token
10 String userId = JWT.decode(token).getAudience().get(0);
11 return userId;
12 }
13
14 /**
15 * 获取request
16 *
17 * @return
18 */
19 public static HttpServletRequest getRequest() {
20 ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
21 .getRequestAttributes();
22 return requestAttributes == null ? null : requestAttributes.getRequest();
23 }
24 }
3.7 新建一个简单的控制器 用于验证
@RestController
public class UserApi {
@Autowired
UserService userService;
@Autowired
TokenService tokenService;
// 登录
@GetMapping("/login")
public Object login(User user, HttpServletResponse response) {
JSONObject jsonObject = new JSONObject();
User userForBase = new User();
userForBase.setId("1");
userForBase.setPassword("123");
userForBase.setUsername("mrc");
if (!userForBase.getPassword().equals(user.getPassword())) {
jsonObject.put("message", "登录失败,密码错误");
return jsonObject;
} else {
String token = tokenService.getToken(userForBase);
jsonObject.put("token", token);
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
response.addCookie(cookie);
return jsonObject;
}
}
/***
* 这个请求需要验证token才能访问
*
* @author: MRC
* @date 2019年5月27日 下午5:45:19
* @return String 返回类型
*/
@UserLoginToken
@GetMapping("/getMessage")
public String getMessage() {
// 取出token中带的用户id 进行操作
System.out.println(TokenUtil.getTokenUserId());
return "你已通过验证";
}
}
3.8 开始测试
## 成功登陆后保存token到前端cookie 以后的请求带上token即可区别是哪个用户的请求!
我们下一个请求在请求的时候带上这个token试试
成功通过验证! 我们看一下后端控制台打印的结果!
打印出带这个token的用户
|