JWT简介
Jwt全称是:json web token。它将用户信息加密到token里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证token的正确性,只要正确即通过验证。
优点:
- 简洁: 可以通过URL、POST参数或者在HTTP header发送,因为数据量小,传输速度也很快;
- 自包含:负载中可以包含用户所需要的信息,避免了多次查询数据库;
- 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持;
- 不需要在服务端保存会话信息,特别适用于分布式微服务。
缺点:
- 无法作废已颁布的令牌;
- 不易应对数据过期。
备注:JWT官网
1、Jwt消息构成
1.1组成
一个token分3部分,按顺序为
- 头部(header)
- 载荷(payload)
- 签证(signature)
三部分之间用.号做分隔。例如:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIxYzdiY2IzMS02ODFlLTRlZGYtYmU3Yy0wOTlkODAzM2VkY2UiLCJleHAiOjE1Njk3Mjc4OTF9.wweMzyB3tSQK34Jmez36MmC5xpUh15Ni3vOV_SGCzJ8
|
Jwt的头部承载两部分信息:
- 声明类型,这里是Jwt
- 声明加密的算法 通常直接使用 HMAC SHA256
Jwt里验证和签名使用的算法列表如下:
JWS |
算法名称 |
HS256 |
HMAC256 |
HS384 |
HMAC384 |
HS512 |
HMAC512 |
RS256 |
RSA256 |
RS384 |
RSA384 |
RS512 |
RSA512 |
ES256 |
ECDSA256 |
ES384 |
ECDSA384 |
ES512 |
ECDSA512 |
1.3 playload
载荷就是存放有效信息的地方。基本上填2种类型数据
- 标准中注册的声明的数据;
- 自定义数据。
由这2部分内部做base64加密。
标准中注册的声明 (建议但不强制使用)
iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的. iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
|
- 自定义数据:存放我们想放在token中存放的key-value值
1.4 signature
Jwt的第三部分是一个签证信息,这个签证信息由三部分组成
base64加密后的header和base64加密后的payload连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了Jwt的第三部分。
SpringBoot集成JWT
1、添加依赖
在pom.xml文件中添加如下依赖:
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>
|
2、简单配置使用
2.1创建一个token工具类:
创建TokenUtils.java
import cn.hutool.core.util.StrUtil; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.qiancheng.springboot.entity.User; import com.qiancheng.springboot.service.IUserService; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.PostConstruct; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.Date;
@Component public class TokenUtils {
private static IUserService staticUserService;
@Resource private IUserService userService;
private static final long EXPIRE_TIME = 30 * 60 * 1000;
@PostConstruct public void setUserService(){ staticUserService = userService; }
public static String genToken(String userId,String sign) { Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
return JWT.create().withAudience(userId) .withExpiresAt(date) .sign(Algorithm.HMAC256(sign)); }
public static User getCurrentUser(){ try { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String token = request.getHeader("token"); if (StrUtil.isNotBlank(token)){ String userId = JWT.decode(token).getAudience().get(0); return staticUserService.getById(Integer.valueOf(userId)); } }catch (Exception e){ return null; }
return null; } }
|
关于@PostConstruct注解参考文章
2.2创建拦截器,拦截token
创建JWTInterceptor.java
import cn.hutool.core.util.StrUtil; 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.qiancheng.springboot.common.Constants; import com.qiancheng.springboot.entity.User; import com.qiancheng.springboot.exception.ServiceException; import com.qiancheng.springboot.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
public class JWTInterceptor implements HandlerInterceptor {
@Autowired private IUserService userService;
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); if(!(handler instanceof HandlerMethod)){ return true; } if (StrUtil.isBlank(token)) { throw new ServiceException(Constants.CODE_401,"无Token,请重新登录!"); }
String userId; try { userId = JWT.decode(token).getAudience().get(0); } catch (JWTDecodeException j) { throw new ServiceException(Constants.CODE_401,"Token验证失败,请重新登录!"); }
User user = userService.getById(userId); if (user == null){ throw new ServiceException(Constants.CODE_401,"用户不存在,请重新登录!"); }
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build(); try { jwtVerifier.verify(token); } catch (JWTVerificationException e) { throw new ServiceException(Constants.CODE_401,"Token验证失败,请重新登录!"); }
return true; }
}
|
2.3注册拦截器
创建InterceptorConfig.java
import com.qiancheng.springboot.config.interceptor.JWTInterceptor; 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(jwtInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/user/login","/user/register","/**/export","/**/import");
}
@Bean public JWTInterceptor jwtInterceptor(){ return new JWTInterceptor(); }
}
|
2.4使用
在返回前端的数据封装类里新增token属性,登录成功时给token属性赋值,之后前端的所有请求都在header里携带token,后端会自动拦截到token后再处理请求。
返回前端的封装类UserDTO.java:
import lombok.Data;
@Data public class UserDTO {
private String username; private String password; private String nickname; private String avatarUrl;
private String token; }
|
登录成功,给token赋值:
String token = TokenUtils.genToken(one.getId().toString(),one.getPassword());
userDTO.setToken(token);
|