分类:
Java
JWT 的原理
基于Token的身份验证用来替代传统的cookie+session身份验证方法中的session。
token应用流程为:
1、初次登录:用户初次登录,输入用户名密码。
2、密码验证:服务器从数据库取出用户名和密码进行验证。
3、生成JWT:服务器端验证通过,根据从数据库返回的信息,以及预设规则,生成JWT。
4、返还JWT:服务器的HTTP RESPONSE中将JWT返还。
5、带JWT的请求:以后客户端发起请求,HTTP REQUEST HEADER中的Authorization字段都要有值,为JWT,用来验证用户身份以及对路由,服务和资源的访问权限进行验证。请求验证的url可以例如:http://127.0.0.1:8083/change/goodsMenu? token=JWT
JWT 是jsonWebToken 简称,现在流行的前后端分离开发(jwt不止是运用于web应用,app,小程序,H5混合开发都可)基本上都使用jwt来验证用户token,减少了服务器session 的使用,也能减轻服务器的压力,是当下Java开发不二选择
更多的关于jwt的东西都可自行百度,这里就不多赘述
使用JWT
1、添加依赖
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
2、找到一个合适的Jwt加密工具
网上的加密工具都不一样,但达到的目的都是一样的
@Component
public class JwtTokenUtil {
private static final long serialVersionUID = -3301605591108950415L;
private String secret = ParamUtil.getValue("jwt_secret");
private Long expiration = Long.valueOf(ParamUtil.getValue("jwt_expiration"));
private String tokenHeader = ParamUtil.getValue("jwt_tokenHeader");
private Clock clock = DefaultClock.INSTANCE;
public String generateToken(UserInfo userInfo) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userInfo.getName());
}
public String generateToken(UserInfo user, String userName) {
Map<String, Object> claims = new HashMap<>();
// 自定义参数,把需要存入token的参数都可以自定义到这里,比如 角色 部门类的关键参数
claims.put("userId", user.getId());
claims.put("phone", user.getName());
return doGenerateToken(claims, userName);
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
final Date createdDate = clock.now();
final Date expirationDate = calculateExpirationDate(createdDate);
return Jwts.builder()
.setClaims(claims) // 存入自定义参数
.setSubject(subject)
.setIssuedAt(createdDate)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
private Date calculateExpirationDate(Date createdDate) {
return new Date(createdDate.getTime() + expiration);
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
public Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(clock.now());
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
}3、生产加密token,登录时返回
加密字符串可以得到了,接下来就是运用起来,在登录接口利用jwtTokenUtil 生成一个Token 并在登录接口一并返回
4、拦截器拦截并验证token
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
String servletPath = request.getServletPath();
if (methodAnnotation != null) {
// 判断是否存在令牌信息,如果存在,则允许登录
//token_access 这就是前端放在请求头中的jwtToken
String accessToken = request.getHeader("token_access");
if (ToolUtil.isEmpty(accessToken)) {
//这里抛给前端的为一个自定义异常枚举,可自己定义
//RRException 为继承 RuntimeException 的自定义枚举
throw new RRException(RongRunErrorCodeEnum.SYSTEM_LOGIN);
}
try {
Claims claims = jwtTokenUtil.getAllClaimsFromToken(accessToken);
//通过jwt加密工具中自定义参数取出自己想要验证的值,来进行操作
Long userId = Long.valueOf(claims.get("userId").toString());
//这个可要可不要,通过这里自己在请求中加参数,可以让前端少传几个已经在token中自定义好的值
request.setAttribute("userId", userId);
//接下来可处理需要验证并且已经通过后的业务
} catch (ExpiredJwtException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"token已过期");
ServletOutputStream out = response.getOutputStream();
OutputStreamWriter ow = new OutputStreamWriter(out, "utf-8");
ow.write(JSONObject.toJSONString(ApiJson.returnNG("请登录!")));
ow.flush();
ow.close();
return false;
} catch (MalformedJwtException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED,"token有误");
ServletOutputStream out = response.getOutputStream();
OutputStreamWriter ow = new OutputStreamWriter(out, "utf-8");
ow.write(JSONObject.toJSONString(ApiJson.returnNG("请登录!")));
ow.flush();
ow.close();
return false;
}
return true;
}
}处理类已经完成,接下来是添加拦截器 自定义类并且实现WebMvcConfigurer
@Configuration
@Slf4j
public class MyInterceptor implements WebMvcConfigurer {
/*
@Autowired
private LoginInterceptor loginInterceptor;*/
/**
* 重写添加拦截器方法并添加配置拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor());//添加拦截规则
}
@Bean
public LoginInterceptor loginInterceptor (){
return new LoginInterceptor();
}
}到此就可以完成spring boot 对jwt的实现,也可以实现用户单点登录,这里就不细说。原理就是在用户登录时就在服务器中存入token,在拦截器中获取到并对比即可
* 另外说明,以上时全局拦截,如果有的同志只需要进行局部拦截的话,可以使用自定义注解的方式
Spring boot 中使用注解也时非常方便 如果有同志对注解不是很了解的话,可以百度一哈
1、创建注解类

2、使用注解

3、在拦截规则中使用注解
Method method = handlerMethod.getMethod();
// 通过获取注解的方法,判断有注解的方法则需要拦截验证
YourAnnotation yourAnnotation = method.getAnnotation(YourAnnotation.class);
// 有 @methodAnnotation 注解,需要认证
if (yourAnnotation != null) {
// 判断是否存在令牌信息,如果存在,则允许登录
String accessToken = request.getHeader("token_access");
if (ToolUtil.isEmpty(accessToken)) {
throw new RRException(RongRunErrorCodeEnum.SYSTEM_LOGIN);
}
try {
Claims claims = jwtTokenUtil.getAllClaimsFromToken(accessToken);
} catch (ExpiredJwtException e) {
}
}这样就可以只验证加了自定义注解的接口,实现局部拦截
50010702506256