一、JWT是什么?
1、先看利用token登录的实现流程:
1、客户端使用账号密码登录
2、服务端接受到请求验证账号和密码
3、服务端验证请求后,会生成一个token,并把token返回给客户端
4、客户端收到token,并存储起来,在每次请求的时候带上这个token,可以在head中携带
5、服务端接受到请求后,验证token,如果验证成功,则返回请求数据
2、JWT介绍:
JWT代表JSON Web Token,是一种用于在网络上安全地传输信息的开放标准(RFC 7519)。它以紧凑和自包含的方式表示信息,通常用于在用户和服务之间传递身份验证和授权信息。JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。
3、JWT的验证流程:
1、解析Token: 首先,将收到的JWT分割成头部、载荷和签名三个部分。这个过程通常会使用编程语言中相应的库或函数来完成。
2、验证头部和签名: 获取JWT中的头部和载荷信息后,根据头部中指定的加密算法和使用的密钥(通常是一个密钥或公钥)对头部和载荷进行签名验证。验证过程会使用相同的算法和密钥来生成签名,并将生成的签名与JWT中的签名进行比较,以确认JWT的完整性和真实性。
3、检查有效期和其他声明: JWT中的载荷部分可能包含了诸如令牌的过期时间(Expiration Time)等声明。在验证过程中,需要检查这些声明以确保令牌在有效期内,并可能进行其他额外的声明验证,如角色、权限等。
4、可选的其他验证步骤: 根据需求,还可以进行其他的验证步骤,比如验证令牌是否在黑名单中,是否与存储的用户信息匹配等。
JWT的验证流程主要依赖于对JWT规范和实现的理解,以及使用的编程语言或框架提供的相应功能。在验证JWT时,确保密钥的安全性是非常重要的,因为泄露密钥可能会导致令牌被篡改或伪造。
二、使用步骤
1.user数据库(代码演示是配置在yml文件上)
2.项目结构
3.相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
4.代码
User:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@TableField(value = "user_name")
private String userName;
@TableField(value = "password")
private String password;
}
UserDao:
@Mapper
public interface UserDao extends BaseMapper<User> {
}
UserService:
public interface UserService {
User loing(String userName, String userPassword);
List<User> getUserList();
}
UserServiceImpl:
@DS("master")
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Value("${login.userName:admin}")
private String name;
@Value("${login.password:123456}")
private String password;
@Override
public User loing(String userName, String userPassword) {
// //查表方式
// Map<String, Object> map = new HashMap<>();
// map.put("user_name", userName);
// map.put("password", userPassword);
// List<User> users = userDao.selectByMap(map);
// if (CollectionUtils.isEmpty(users)){
// return null;
// }
// return users.get(0);
if (Objects.equals(userName,name) && Objects.equals(userPassword,password)){
User user = new User();
user.setUserName(name);
user.setPassword(password);
return user;
}
return null;
}
@Override
public List<User> getUserList() {
return userDao.selectList(null);
}
}
TokenUtil:
public class TokenUtil {
/**
* 设置延期的时间 x分钟
* x*1000*60
*/
private static final long EXPIR_DATE = 60000;
// private static final long EXPIR_DATE = 3600000;
/**
* 设置token的秘钥
*/
private static final String TOKEN_SECRET = "kb_resource_qwe12300czx";
/**
* 获取token
*
* @param userName
* @param passWord
* @return
*/
public static String getTokenSecret(String userName, String passWord) {
String token = "";
try {
// 过期时间
Date date = new Date(System.currentTimeMillis() + EXPIR_DATE);
// 设置头部信息
Map<String, Object> head = new HashMap<>();
head.put("typ", "JWT");
head.put("alg", "HS256");
// 设置秘钥
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
// JWT.create().setHeader()
// 生成签名
token = JWT.create()
.withHeader(head)
.withClaim("userName", userName)
.withClaim("passWord", passWord)
.withExpiresAt(date)
.sign(algorithm);
} catch (Exception e) {
throw new RuntimeException(e);
}
return token;
}
/**
* 验证token
*
* @param token
* @return
*/
public static DecodedJWT verify(String token) {
Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
JWTVerifier verifier = JWT.require(algorithm).build();
return verifier.verify(token);
}
}
全局异常&自定义异常
@Setter
@Getter
public class MeException extends Exception {
private String msg;
private int code = 500;
private Map<String, Object> map;
public MeException(String msg) {
super(msg);
this.msg = msg;
}
public MeException(String msg, Throwable cause) {
super(msg, cause);
this.msg = msg;
}
public MeException(String msg, int code) {
super(msg);
this.msg = msg;
this.code = code;
}
public MeException(String msg, int code, Map<String, Object> map) {
super(msg);
this.msg = msg;
this.code = code;
this.map = map;
}
}
@RestControllerAdvice
public class GlobalExceptionHandler
{
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 业务异常
*/
@ExceptionHandler(MeException.class)
public Object handleServiceException(MeException e, HttpServletRequest request)
{
log.error(e.getMessage(), e);
return ApiResponse.error(e.getMessage());
}
}
ApiResponse:
@Data
public class ApiResponse {
private int status;
private String msg;
private Object data;
public ApiResponse(int status, String msg, Object data) {
this.status = status;
this.msg = msg;
this.data = data;
}
// Getters and Setters
// Optional: You can also add static methods to create common response objects.
public static ApiResponse success(Object data) {
return new ApiResponse(200, "Success", data);
}
public static ApiResponse error(String message) {
return new ApiResponse(500, message, null);
}
}
LoginController:
@Controller
@RequestMapping("/login")
public class LoginController {
@Resource
private UserService userService;
@RequestMapping("/toLogin")
@ResponseBody
public ApiResponse toLogin(String userName, String userPassword, HttpServletResponse response){
User user = userService.loing(userName, userPassword);
JSONObject jsonObject = new JSONObject();
if(user == null){
return ApiResponse.error("账号或者密码错误,请重试!");
}
String token = TokenUtil.getTokenSecret(userName, userPassword);
jsonObject.put("token", token);
response.setHeader("token", token);
Cookie cookie = new Cookie("token", token);
cookie.setPath("/");
response.addCookie(cookie);
return ApiResponse.success(jsonObject);
}
@UserLoginToken
@RequestMapping("/list")
@ResponseBody
public ApiResponse list(){
List<User> userList = userService.getUserList();
return ApiResponse.success(userList);
}
// @UserLoginToken
@RequestMapping("/test")
@ResponseBody
public ApiResponse test(){
return ApiResponse.success("请求成功");
}
}
UserLoginToken(自定义注解)
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}
LoginrequiredInterceptor
@Component
public class LoginrequiredInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if(handler instanceof HandlerMethod){
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if(userLoginToken.required()){
if(token == null){
throw new RuntimeException("token为空,请重新登录");
}else{
//查询用户是否存在
// 验证token
try {
TokenUtil.verify(token);
} catch (Exception e) {
if (e instanceof TokenExpiredException || e instanceof InvalidClaimException) {
// 触发刷新AccessToken的操作
throw new MeException("登录令牌鉴权失败,令牌超时");
} else {
// 签名失效的场景
throw new MeException("登录令牌鉴权失败," + e.getMessage());
}
}
return true;
}
}
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
}
WebMvcConfig
@Component
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private LoginrequiredInterceptor loginrequiredInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginrequiredInterceptor)
.excludePathPatterns("/login/toLogin")
.addPathPatterns("/**");
}
}
三、测试
1.token过期时间校验,自定义异常