SpringBoot项目引入token设置方式
作者:陈厚伯
一. 先了解熟悉JWT(JSON Web Token)
看这些介绍、结构之类的,确实挺无聊的;想直接进入主题的话,就跳过第一大步。
望各位同仁给出相关意见,以备我来更加深入的学习。
1. JSON Web Token是什么鬼?
这个东西,反正理解成一个标准就行了,啥标准我也不知道。反正就是用于各种信息的安全性传输。
2. JSON Web令牌应用的场景
1.授权,在用户登录后会给用户一个token,在用户后续的所有请求后台资源的操作都将携带这个token,只有被token允许的操作才能执行。
2.信息交换,应用于各种数据信息交换的场景;目前这种场景我也没有涉及到过,嘿嘿!
3. JSON Web令牌结构
JSON Web Tokens由dot(.)分隔的三个部分组成:Header、Payload、Signature;
因此token的形式是:
xxxxx.yyyyy.zzzzz
3.1 Header
标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法,例如HMAC SHA256或RSA。
{ "alg": "HS256", "typ": "JWT" }
然后,这个JSON被编码为Base64Url,形成JWT的第一部分
3.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中。
3.3 Signature
要创建签名部分,必须采用 header, payload, secret,标头中指定的算法,并对其进行签名。
例如,如果要使用HMAC SHA256算法,将按以下方式创建签名:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
3.4 token
最后输出是三个由点分隔的Base64-URL字符串
4. 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是否生效:
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。