JWT(JSON Web Token)简而言之,JWT是一个加密的字符串,JWT传输的信息经过了数字签名,因此传输的信息可以被验证和信任。一般被用来在身份提供者和服务提供者间传递被认证用户的身份信息,以便于从资源服务器获取资源,也可以增加一些额外的业务逻辑所需的声明信息。
使用基于Token的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:
JWT 标准的 Token 由三个部分组成:header.payload.signature:
header(头部)
payload(数据)
signature(签名)
头部数据,里面包含了使用的算法。
声明类型,这里是jwt
声明加密的算法 通常直接使用 HS256
{
"alg": "HS256",
"typ": "JWT"
}
在base64url编码之后变成
eyJhbGciOiJIUzI1NiJ9
载荷是存放有效信息的地方。
iss: Issuer,发行者
sub: Subject,主题
aud: Audience,观众
exp: Expiration time 过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
可以自定义字段, 一个是 id ,还有一个是 admin 。
{
"iss": "ninghao.net",
"exp": "1438955445",
"id": "aaa",
"admin": true
}
使用 base64url 编码以后就变成了这个样子:
eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ
一个签证信息,这个签证信息由三部分组成:
header (base64后的)
payload (base64后的)
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密。
const encodedString = base64UrlEncode(header) + "." + base64UrlEncode(payload);
// 这里的 HMACSHA256() 就是我们在第一部分定义的加密算法。
HS256(encodedString, 'secret');
处理完像这样
SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
最后这个在服务端生成并且要发送给客户端的 Token
eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJuaW5naGFvLm5ldCIsImV4cCI6IjE0Mzg5NTU0NDUiLCJuYW1lIjoid2FuZ2hhbyIsImFkbWluIjp0cnVlfQ.SwyHTEx_RQppr97g4J5lKXtabJecpejuef8AqKYMAJc
客户端收到这个 Token 以后把它存储下来,下回向服务端发送请求的时候就带着这个 Token。服务端收到这个 Token ,然后进行验证,通过以后就会返回给客户端想要的资源。
com.auth0
java-jwt
3.4.0
public class TokenUtil {
/**
* token过期时间
*/
private static final long EXPIRE_TIME = 30 * 60 * 1000;
/**
* token秘钥
*/
private static final String SECRET = "demo_secret";
/**
* 生成签名,30分钟过期
* @param username 用户名
* @param loginTime 登录时间
* @return 生成的token
*/
public static String sign(String username, String loginTime) {
try {
// 设置过期时间
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
// 私钥和加密算法
Algorithm algorithm = Algorithm.HMAC256(SECRET);
// 设置头部信息
Map header = new HashMap<>(2);
header.put("Type", "Jwt");
header.put("alg", "HS256");
// 返回token字符串
return JWT.create()
.withHeader(header)
.withClaim("loginName", username)
.withClaim("loginTime", loginTime)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 检验token是否正确
* @param token 需要校验的token
* @return 校验是否成功
*/
public static boolean verify(String token){
try {
//设置签名的加密算法:HMAC256
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT jwt = verifier.verify(token);
return true;
} catch (Exception e){
return false;
}
}
}
/**
* 登录
* @param loginVo 登录类
* @return 返回类
*/
@Override
public R login(LoginVo loginVo) {
User user = new User();
user.setUserName(loginVo.getUserName());
user.setPassword(loginVo.getPassword());
List users = userService.queryByUser(user);
if(users.isEmpty()){
return R.fail();
}else{
if(loginVo.getUserName() != null && loginVo.getLoginTime() != null) {
String token = TokenUtil.sign(loginVo.getUserName(), loginVo.getLoginTime());
loginVo.setToken(token);
//断言token不为空,并以用户名作为key,存入redis(看需求是否要放到redis)
assert token != null;
redisTemplate.opsForValue().set(loginVo.getUserName(),token);
return R.ok(loginVo);
}else{
return R.fail();
}
}
}
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
return true;
}
response.setCharacterEncoding("utf-8");
// header里面的字段名取决于前端放置的字段名
String token = request.getHeader("Authorization");
if (token != null) {
boolean result = TokenUtil.verify(token);
if (result) {
System.out.println("通过拦截器");
return true;
}
}
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
PrintWriter out = null;
try {
JSONObject json = new JSONObject();
json.put("success", "false");
json.put("msg", "认证失败,未通过拦截器");
json.put("code", "500");
response.getWriter().append(json.toJSONString());
System.out.println("认证失败,未通过拦截器");
} catch (Exception e) {
e.printStackTrace();
response.sendError(500);
return false;
}
return false;
}
}
@Component
public class IntercepterConfig implements WebMvcConfigurer {
private TokenInterceptor tokenInterceptor;
//构造方法
public IntercepterConfig(TokenInterceptor tokenInterceptor){
this.tokenInterceptor = tokenInterceptor;
}
@Override
public void addInterceptors(InterceptorRegistry registry){
List excludePath = new ArrayList<>();
//登录
excludePath.add("/login/acount");
registry.addInterceptor(tokenInterceptor)
.addPathPatterns("/**")
.excludePathPatterns(excludePath);
//除了登陆接口其他所有接口都需要token验证
WebMvcConfigurer.super.addInterceptors(registry);
}
}
参考博客:
https://www.cnblogs.com/yuanchangliang/p/16373162.html