十、SpringBoot整合Token
一日一步,小步? 大步?
聚沙成塔,集腋成裘。
JWT
1、什么是JSON Web Token?
JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间作为JSON对象安全地传输信息。此信息可以通过数字签名进行验证和信任。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
虽然JWT可以加密以在各方之间提供保密,但我们将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则隐藏其他方的声明。当使用公钥/私钥对签名令牌时,签名还证明只有持有私钥的一方是签署它的一方。
2、什么时候应该使用JSON Web令牌?
以下是JSON Web令牌有用的一些场景:
授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,允许用户访问该令牌允许的路由,服务和资源。Single Sign On是一种现在广泛使用JWT的功能,因为它的开销很小,并且能够在不同的域中轻松使用。
信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。因为JWT可以签名 - 例如,使用公钥/私钥对 - 您可以确定发件人是他们所说的人。此外,由于使用标头和有效负载计算签名,您还可以验证内容是否未被篡改。
什么是JSON Web令牌结构?
在紧凑的形式中,JSON Web Tokens由dot(.)分隔的三个部分组成,它们是:
Header
Payload
Signature
因此,JWT通常如下所示。
xxxxx.yyyyy.zzzzz
Header
标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。
例如:
{
"alg": "HS256",
"typ": "JWT"
}
然后,这个JSON被编码为Base64Url,形成JWT的第一部分。
Payload
令牌的第二部分是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中。
Signature
要创建签名部分,必须采用 header, payload, secret,标头中指定的算法,并对其进行签名。
例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名用于验证消息在此过程中未被更改,并且,在使用私钥签名的令牌的情况下,它还可以验证JWT的发件人是否是它所声称的人。
全部放在一起
输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递,而与基于XML的标准(如SAML)相比更加紧凑。
下面显示了一个JWT,它具有先前的头和有效负载编码,并使用机密签名。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxODcxMTM0Njc1NiIsImV4cCI6MTU3MDM1OTA0MywiaWF0IjoxNTcwMzU1NDQzfQ.vul-EMRIhkW0l2kZwPmZOTbgfwOjVQJSus_Gwt39CSE
JWT工作原理
1)用户登陆的时候使用用户名和密码发送POST请求。
2)服务器使用私钥创建一个jwt。
3)服务器返回这个jwt到浏览器。
4)浏览器将该jwt串加入请求头中向服务器发送请求。
5)服务器验证jwt。
6)返回相应的资源给客户端。
SpringBoot集成Token
1、引入依赖
com.auth0
java-jwt
3.4.0
2、定义注解
PassToken:跳过验证。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
3、UserLoginToken:需要验证。
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
4、定义实体类
public class UserVO implements Serializable {
private int userId;
private String userAccount;
private String userPassword;
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public String getUserAccount() {
return userAccount;
}
public void setUserAccount(String userAccount) {
this.userAccount = userAccount;
}
public String getUserPassword() {
return userPassword;
}
public void setUserPassword(String userPassword) {
this.userPassword = userPassword;
}
}
5、编写TokenService
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.hu.libraryManagement.VO.UserVO;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.HashMap;
/***
* token 下发
*/
@Service
public class TokenService {
public String getToken(UserVO userVo) {
Date start = new Date();
long currentTime = System.currentTimeMillis() + 60* 60 * 1000;//一小时有效时间
Date end = new Date(currentTime);
//设置头信息
HashMap header = new HashMap<>(2);
header.put("typ", "JWT");
header.put("alg", "HS256");
String token = "";
/**
* withHeader http头部信息
* withAudience 用户登录(user)信息 在Token拦截器(AuthenticationInterceptor)中通过 JWT.decode(token).getAudience().get(0)提取
* withIssuedAt(start) withExpiresAt(end) 有效时间
* sign Algorithm.私钥及加密算法
* */
token = JWT.create().withHeader(header).withAudience(userVo.getUserAccount()).withIssuedAt(start).withExpiresAt(end)
.sign(Algorithm.HMAC256(userVo.getUserPassword())); // Algorithm. 私钥及加密算法
return token;
}
}
6、编写拦截器
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.hu.libraryManagement.VO.UserVO;
import com.hu.libraryManagement.annotation.PassToken;
import com.hu.libraryManagement.annotation.UserLoginToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.annotation.Reference;
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;
import java.util.logging.Logger;
// 编写Token拦截器
public class AuthenticationInterceptor implements HandlerInterceptor {
Logger logger = Logger.getLogger("com.hu.libraryManagement.service.AuthenticationInterceptor");
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("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 中的 userAccount
String userAccount;
try {
// 获得在Token中的userAccount (TokenService 中withAudience(userVo.getUserAccount()) 添加的userAccount)
userAccount = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
logger.info("userAccount: "+ userAccount );
UserVO userVo;
userVo = userService.selcet(userAccount);
if (null == userVo) {
throw new RuntimeException("用户不存在,请重新登录");
}
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(userVo.getUserPassword())).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 {
}
}
7、配置拦截器
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}
8、在controller中使用
@RestController
@CrossOrigin
@RequestMapping("/user")
@Api(tags = "用户登录")
public class LoginController extends BaseController {
@Reference
UserService userService;
@Reference
TokenService tokenService;
//登录
@PostMapping("/login")
@ResponseBody
public Object login(UserVO user) {
JSONObject jsonObject = new JSONObject();
UserVO userVo = userService.select(user.getUserAccount());
if (userVo == null) {
jsonObject.put("message", "登录失败,用户不存在");
return jsonObject;
} else {
if (!userVo.getUserPasswd().equals(user.getUserPasswd())) {
jsonObject.put("message", "登录失败,密码错误");
return jsonObject;
} else {
String token = tokenService.getToken(userVo);
jsonObject.put("token", token);
jsonObject.put("user", userVo);
return jsonObject;
}
}
}
@UserLoginToken
@GetMapping("/getMessage")
public String getMessage() {
return "你已通过验证";
}
}
即传入token才可以调用getMessage()方法。
9、测试
接口调试
1、调用getMessage()
使用postmen调用接口:http://localhost:8080/user/getMessage
输出错误信息如下:
2、调用login()
使用postmen调用接口:http://localhost:8080/user/login
传入id,用户名和密码三个参数,输出结果如下:
3、再次调用getMessage()
在Headers中加入token参数,输出结果如下:
————————————————
版权声明:本文为CSDN博主「lamarsan」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_16410733/article/details/96625801