SpringBoot项目引入token设置
一. 先了解熟悉JWT(JSON Web Token)
看这些介绍、结构之类的,确实挺无聊的;想直接进入主题的话,就跳过第一大步。望各位同仁给出相关意见,以备我来更加深入的学习。
1. JSON Web Token是什么鬼?
这个东西,反正理解成一个标准就行了,啥标准我也不知道。反正就是用于各种信息的安全性传输。
1. JSON Web令牌应用的场景
1.授权,在用户登录后会给用户一个token,在用户后续的所有请求后台资源的操作都将携带这个token,只有被token允许的操作才能执行。
2.信息交换,应用于各种数据信息交换的场景;目前这种场景我也没有涉及到过,嘿嘿!
2. JSON Web令牌结构
JSON Web Tokens由dot(.)分隔的三个部分组成:Header、Payload、Signature;
因此token的形式是:
xxxxx.yyyyy.zzzzz
2.1 Header
标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。
{
"alg": "HS256",
"typ": "JWT"
}
然后,这个JSON被编码为Base64Url,形成JWT的第一部分
2.2 Payload
这一部分是声明,有三种类型:注册,公开和私有声明;
- 注册
这些是一组预定义声明,不是强制性的,但建议使用,以提供一组有用的,可互操作的声明。其中一些是:iss (issuer), exp (expiration time), sub (subject), aud(audience)等
注:请注意,声明名称只有三个字符,因为JWT意味着紧凑。 - 公开
这些可以由使用JWT的人随意定义。但为避免冲突,应在 IANA JSON Web令牌注册表中定义它们,或者将其定义为包含防冲突命名空间的URI - 私有
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息
payload示例:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
然后,payload经过Base64Url编码,形成JSON Web令牌的第二部分
请注意:对于签名令牌,此信息虽然可以防止被篡改,但任何人都可以读取。除非加密,否则不要将秘密信息放在JWT的payload或header中。
2.3 Signature
要创建签名部分,必须采用 header, payload, secret,标头中指定的算法,并对其进行签名。
例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
2.4 token
最后输出是三个由点分隔的Base64-URL字符串
3. JSON Web令牌工作原理
- 用户登陆的时候使用用户名和密码发送POST请求
- 服务器使用私钥创建一个jwt
- 服务器返回这个jwt到浏览器
- 浏览器将该jwt串加入请求头中向服务器发送请求
- 服务器验证jwt
- 返回相应的资源给客户端
二. 动手开始搞
1.引入依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2. 定义注解
package com.example.fighting.config;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class UserToken {
// 跳过验证
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
// 需要验证
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
}
3.新增TokenService接口类和TokenServiceImpl实现类
package com.example.fighting.serviceImpl;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.fighting.bean.UserTest;
import com.example.fighting.service.TokenService;
import org.springframework.stereotype.Service;
@Service
public class TokenServiceImpl implements TokenService {
@Override
public String getToken(UserTest userTest) {
String token="";
token= JWT.create().withAudience(userTest.getId().toString())
.sign(Algorithm.HMAC256(userTest.getPassword()));
return token;
}
}
4.新增拦截器
package com.example.fighting.config;
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.example.fighting.bean.UserTest;
import com.example.fighting.service.UserTestService;
import jdk.nashorn.internal.ir.annotations.Reference;
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;
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
// @Reference
UserTestService userTestService;
@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(UserToken.PassToken.class)) {
UserToken.PassToken passToken = method.getAnnotation(UserToken.PassToken.class);
if (passToken.required()) {
return true;
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(UserToken.UserLoginToken.class)) {
UserToken.UserLoginToken userLoginToken = method.getAnnotation(UserToken.UserLoginToken.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 获取 token 中的 user id
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
UserTest user = userTestService.findUserTestById(Long.parseLong(userId));
if (user == null) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.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 {
}
}
5.添加拦截器配置
package com.example.fighting.interceptor;
import com.example.fighting.config.AuthenticationInterceptor;
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("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
6. 在controller中添加登录接口
//登录
@ApiOperation(value="登录接口",notes ="验证登录后获取一个token")
@ApiImplicitParams({
@ApiImplicitParam(paramType="query",name="username",value="账号",required = true),
@ApiImplicitParam(paramType="query",name="password",value="密码",required = true)
})
@PostMapping("/login")
@ResponseBody
public Map login(UserTest user) {
Map<Object, Object> map = new HashMap<>();
UserTest userForBase = userTestService.findUserTestByUserName(user.getUsername());
if (userForBase == null) {
map.put("message", "登录失败,用户不存在");
return map;
} else {
if (!userForBase.getPassword().equals(user.getPassword())) {
map.put("message", "登录失败,密码错误");
return map;
} else {
String token = tokenService.getToken(userForBase);
map.put("token", token);
map.put("user", userForBase);
return map;
}
}
}
@ApiOperation(value="测试token",notes ="测试token是否通过")
@UserToken.UserLoginToken
@GetMapping("/getMessage")
public String getMessage() {
return "你已通过验证";
}
7.接口测试
1.用postman不加token访问
调接口:http://localhost:8014/userTest/getMessage
后台打印结果:
2.用postman添加错误的token访问
打印结果:
3.正常登录,使用登录后返回的token
登录接口:http://localhost:8014/userTest/login
调接口测试token是否生效: