文章目录
一、JWT是什么?
JWT全称是:json web token。它将用户信息加密到 token 里,服务器不保存任何用户信息。服务器通过使用保存的密钥验证 token 的正确性,只要正确即通过验证。
优点
1.简介:可以通过 URL POST 参数或者在 HTTP header 发送,因为数据量小,传输速度也很快;
2.自包含:负载中可以包含用户所需要的信息,避免了多次查询数据库;
3.因为 Token 是以 JSON 加密的形式保存在客户端的,所以 JWT 是跨语言的,原则上任何 web 形式都支持;
4.不需要再服务端保存会话信息,特别适用于分布式微服务;
缺点
1.无法作废已经发布的令牌;
2.不易应对数据过期;
二、集成JWT实例
1.引入依赖
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- alibaba durid 数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<!-- JWT 鉴权依赖 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>5.4.6</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
</dependencies>
2.编写认证工具类jwtUtil
代码如下:
package com.demo.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.sbm.productionmodelmap.Exception.JwtException;
import java.util.Date;
/**
* <p>Description:
* Jwt 认证授权方式用于生成签名校验和通过签名获取用户信息
*
* </p>
*
* @author Editor MartinZac
* @date 2021年08月03日 9:27
*/
public class JwtUtils {
/**
* 设置过期时间24小时
*/
private static final long EXPIRE_TIME =24 * 60 * 60 * 1000;
/**
* jwt 秘钥
*/
private static final String SECRET = "jwt_secret";
/**
* 生成token签名设置过期时间
*
* @param userId 用户id
* @return
*/
public static String sign(String userId) {
try {
Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME);
Algorithm algorithm = Algorithm.HMAC256(SECRET);
return JWT.create()
//将userid 保存到token
.withAudience(userId)
//设置token过期时间
.withExpiresAt(date)
//token秘钥
.sign(algorithm);
} catch (Exception e) {
return null;
}
}
/**
* 根据token 秘钥解密取出userID
* @param token
* @return
*/
public static String getUserId(String token) {
try {
//从JWT获取userID
String userId = JWT.decode(token).getAudience().get(0);
return userId;
} catch (Exception e) {
return null;
}
}
/**
* 校验token
* @param token
* @return
*/
public static boolean checkSign(String token){
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.build();
DecodedJWT jwt = verifier.verify(token);
return true;
}catch (JWTVerificationException exception){
throw new JwtException(401,"token 无效,请重新获取");
}
}
}
3.自定义注解@JwtAuth
代码如下:
package com.demo.target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 定义需要登录才能访问的接口
* jwt
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface JwtAuth{
boolean required() default true;
}
4.编写拦截器拦截声明的@JwtAuth注解接口
代码如下:
package com.demo.interceptor;
import com.sbm.productionmodelmap.Exception.JwtException;
import com.sbm.productionmodelmap.entity.SysUser;
import com.sbm.productionmodelmap.service.UserService;
import com.sbm.productionmodelmap.target.AppPermissions;
import com.sbm.productionmodelmap.target.JwtToken;
import com.sbm.productionmodelmap.utils.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* <p>Description:
* jwt 拦截器类拦截所有带注解请求
* </p>
*
* @author Editor MartinZac
* @date 2021年08月03日 10:41
*/
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
@Resource
private UserService userService;
/**
* 在请求前执行验证
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//解决跨域
if("OPTIONS".equals(request.getMethod().toUpperCase())) {
return true;
}
//从http 请求头取出token
String token = request.getHeader("token");
//校验不是映射方法通过请求
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
//定义userId
String userId = "";
if (method.isAnnotationPresent(JwtToken.class)) {
JwtToken jwtToken = method.getAnnotation(JwtToken.class);
if (jwtToken.required()) {
//执行认证
if (token == null) {
throw new JwtException(401, "您还没有授权请先登录");
}
//获取token中的 userId
userId = JwtUtils.getUserId(token);
log.info("当前token:-->:" + token + "<----->userID为:" + userId);
//验证token
JwtUtils.checkSign(token);
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
5.注册拦截器
代码如下:
package com.demo.config;
import com.sbm.productionmodelmap.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;
/**
* <p>Description:
* 注册拦截器类将jwt 拦截器注入
* </p>
*
* @author Editor MartinZac
* @date 2021年08月03日 11:15
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
/**
* 注册拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor())
.addPathPatterns("/**");
}
@Bean
public JwtInterceptor jwtInterceptor() {
return new JwtInterceptor();
}
}
6.全局异常处理
代码如下:
@RestControllerAdvice
public class GlobalExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public Object handleException(Exception e) {
String msg = e.getMessage();
if (msg == null || msg.equals("")) {
msg = "服务器出错";
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("message", msg);
return jsonObject;
}
}
7.编写接口
代码如下:
/**
* 登录并获取token
* @param userName
* @param passWord
* @return
*/
@PostMapping("/login")
public Object login( String userName, String passWord){
JSONObject jsonObject=new JSONObject();
// 检验用户是否存在(为了简单,这里假设用户存在,并制造一个uuid假设为用户id)
String userId = UUID.randomUUID().toString();
// 生成签名
String token= JwtUtil.sign(userId);
Map<String, String> userInfo = new HashMap<>();
userInfo.put("userId", userId);
userInfo.put("userName", userName);
userInfo.put("passWord", passWord);
jsonObject.put("token", token);
jsonObject.put("user", userInfo);
return jsonObject;
}
/**
* 该接口需要带签名才能访问
* @return
*/
@JwtToken
@GetMapping("/getMessage")
public String getMessage(){
return "你已通过验证";
}
}
8.测试,先访问登录接口,获取返回中的token,在将token加到jwt/getMessage接口请求头即可正常访问
原理即使用拦截器拦截请求获取请求头进行校验
总结
当前为springboot整合JWT的实例,还可以根据项目复杂度通过自定义注解的方式进行权限校验,拦截请求校验权限参数