一、创建JWT
1.导入依赖
maven
<!--引入JWT依赖,由于是基于Java,所以需要的是java-jwt-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
gradle
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'com.auth0:java-jwt:3.4.0'
2.创建jwt工具类
package com.zx.framework.util;
import com.zx.cargo.pojo.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* jwt工具类 生成、解析、校验token
*/
public class JwtUtil {
/**
* 用户登录成功后生成jwt
* 使用Hs256算法
* 三部分组成 头部+荷载+签证信息
* @param ttlMillis jwt过期时间
* @param user 登录成功的user对象
* @return
*/
public static String createJwt(long ttlMillis, User user){
// header部分,jwt已经封装好了
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// jwt生成时间 当前时间
long nowMillis = System.currentTimeMillis();
Date date = new Date(nowMillis);
// payload 荷载部分(存放有效信息的地方,包含标准中注册的声明、公共声明、私有声明)
// 创建私有声明
Map<String,Object> claims = new HashMap<>();
claims.put("id", user.getUserId());
claims.put("username",user.getUserName());
claims.put("password",user.getUserPassword());
// 生成秘钥secret用
// String key = user.getUserPassword();
byte[] bytes = user.getUserPassword().getBytes();
// 生成签发人
String subject = user.getUserName();
// 为payload添加标准声明和私有声明(new一个JwtBuilder,设置jwt的body)
JwtBuilder jwtBuilder = Jwts.builder()
// 先设置自己创建的私有声明,要是写在标准声明后面,会覆盖掉标准声明
.setClaims(claims)
// 设置jti(jwt id),主要用来作为一次性token,从而回避重放攻击
.setId(UUID.randomUUID().toString())
// 设置iat jwt签发时间
.setIssuedAt(date)
// 设置jwt的所有人
.setSubject(subject)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, bytes);
// 设置jwt的过期时间
if(ttlMillis>= 0){
long expMillis = ttlMillis+nowMillis;
Date expDate = new Date(expMillis);
jwtBuilder.setExpiration(expDate);
}
System.out.println("生成jwt");
return jwtBuilder.compact();
}
/**
* 解密jwt
* @param token 需要被解密的token
* @param user 用户的对象
* @return
*/
public static Claims parseJWT(String token,User user){
// 签名秘钥(与生成签名的秘钥一样)
String key = user.getUserPassword();
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(key)
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
/**
* 校验jwt
* 判断token携带的密码跟数据库里的是否一致(也可用官方的校验方法)
* @param token
* @param user
* @return
*/
public static Boolean isVerify(String token,User user){
// 秘钥
byte[] bytes = user.getUserPassword().getBytes();
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(bytes)
// 设置需要解析的jwt
.parseClaimsJws(token)
.getBody();
System.out.println("claims-----》"+claims);
// 判断密码是否一致
if(claims.get("password").equals(user.getUserPassword())){
return true;
}
return false;
}
}
二、配置拦截器
1.配置拦截器文件
package com.zx.framework.config;
import com.zx.framework.interceptor.AuthenticationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* jwt 后台访问拦截
* 拦截器配置文件 config
*/
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
// 这个方法用来注册拦截器,我们自己写好的拦截器需要通过这里添加注册才能生效
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册TestInterceptor拦截器
InterceptorRegistration registration = registry.addInterceptor(authenticationInterceptor());
registration.addPathPatterns("/**");//所有路径都被拦截
registration.excludePathPatterns("" +
"/assets/**", // assets文件夹里文件不拦截
"/**/*.js", //js静态资源不拦截
"/**/*.css" //css静态资源不拦截
);
}
}
2.创建拦截器类
package com.zx.framework.interceptor;
import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.zx.cargo.pojo.User;
import com.zx.cargo.stock.service.UserLoginService;
import com.zx.framework.annotation.PassToken;
import com.zx.framework.annotation.UserLoginToken;
import com.zx.framework.util.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
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
private UserLoginService userLoginService;
/*
controller执行之前
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object){
// 从http请求头中取出token
System.out.println("输出token"+request.getHeader("token"));
String token = request.getHeader("token");
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) object;
Method method = handlerMethod.getMethod();
// 方法是否带有UserLoginToken注释
if(method.isAnnotationPresent(UserLoginToken.class)){
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if(userLoginToken.required()){
return true;
}
}
// 方法是否带有PassToken注释
if(method.isAnnotationPresent(PassToken.class)){
PassToken passToken = method.getDeclaredAnnotation(PassToken.class);
if(passToken.required()){
// 执行认证
if(token == null){
throw new RuntimeException("无token,请重新登录!");
}
// 获取token中的userId
String userId;
try {
userId = JWT.decode(token).getClaim("id").asString();
} catch (JWTDecodeException j){
throw new RuntimeException("访问异常!");
}
User user = userLoginService.findUserById(userId);
if(StringUtils.isEmpty(user)){
throw new RuntimeException("当前用户不存在,请重新登录!");
}
Boolean verify = JwtUtil.isVerify(token, user);
if(!verify){
throw new RuntimeException("非法访问!");
}
return true;
}
}
return true;
}
/*
controller执行后,页面渲染前
*/
@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 {
}
}
三、创建使用注解
创建两个注解,PassToken和UserLoginToken,用于在项目开发中,如果需要权限校验就标注userlogintoken,如果访问的资源不需要权限验证则正常编写不需要任何注解,如果用的的请求时登录操作,在用户登录的方法上增加passtoken注解。
1.除登录外使用的注解
package com.zx.framework.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PassToken {
boolean required() default true;
}
2.登录使用的注解
ackage com.zx.framework.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserLoginToken {
boolean required() default true;
}
注解解析:从上面我们新建的两个类上我们可以看到主要的等学习到的就四点
第一:如何创建一个注解
第二:在我们自定义注解上新增@Target注解(注解解释:这个注解标注我们定义的注解是可以作用在类上还是方法上还是属性上面)
第三:在我们自定义注解上新增@Retention注解(注解解释:作用是定义被它所注解的注解保留多久,一共有三种策略,SOURCE 被编译器忽略,CLASS 注解将会被保留在Class文件中,但在运行时并不会被VM保留。这是默认行为,所有没有用Retention注解的注解,都会采用这种策略。RUNTIME 保留至运行时。所以我们可以通过反射去获取注解信息。
第四:boolean required() default true; 默认required() 属性为true
对应方法加入对应注解即可