一、导入jwt依赖
自己认为比较重要的知识点,可以提前学习一下:
- jwt的含义Json web token,基于json的开放标准,用于通信双方传递信息,可以实现加密。
- jwt的请求流程,客户端登录的时候会向服务端发送登录请求,服务端会给此用户生成一个专属token字符串。以后用户的请求在Header里边加入token字符串,服务端会检查此token是否正确,并且可以通过此token得到用户的信息,token不光可以确定用户信息是否存在,也可以通过token反向得到当前发送请求的是用户信息。(通过什么信息生成的token,那么就可以通过token反向得到什么信息,本篇文章用的是用户的id,那么也可以得到用户id;如果想用整个用户类来生成token也是可以的)
- jwt的结构,分成三部分,Header头部,Payload负载,Signature签名。这里不做详细解释,可以去查一下相关内容。
- token可以设置过期时间,当时自己一直没想明白为什么假设可以2小时内有效。因为在 Payload 中,存放了token的生成时间,以及自定义的有效时间,那么过期时间 = 生成时间 + 有效时间。将生成时间、过期时间一起去生成这个token,token里就会自动鉴别是否有效。
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
二、实现jwt工具类(生成token,检查token)
jwtToken可以根据自己的需要自行设定,可以理解为盐。写了一个main主函数,可以用于测试,我自己填入的token肯定是不管用了,可以重新用 Long id = 1L 再生成一个。
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class JWTUtils {
private static final String jwtToken = "123456******!@#$$";
public static String createToekn(Long userId) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,密钥为 jwtToken
.setClaims(claims) // body数据,唯一确认,自行设置
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)); // 一天内的有效时间
String token = jwtBuilder.compact();
return token;
}
// TODO
public static Map<String, Object> checkToken(String token) {
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String, Object>) parse.getBody();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
Long id = 1L;
String tk = createToekn(id);
System.out.println(tk);
String token = "eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MzkyOTI2NDksInVzZXJJZCI6MSwiaWF0IjoxNjM5MjA2MjQ5fQ.8dAHN7hWI6iiBT_JHqJqZsyzL7V2OYVesg7kChVuHj8";
Map<String, Object> map = checkToken(token);
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object value = entry.getValue();
System.out.print(key);
System.out.print("----");
System.out.println(value);
}
}
}
三、设置拦截器
这里已经不算是jwt的部分,可以选择性的看。 拦截器可以配置请求路径,哪些路径的请求信息我们必须要验证用户是否登录,或者我们需要这个用户的信息。把所有可能用到的类都放出来了,除了SysUser部分,这个是需要自己项目需要来做。
1、WebConfig
这里我们拦截test请求。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
/**
* 拦截 test 接口,后续实际遇到需要拦截的端口时,再配置为真正的拦截接口
*/
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/test");
}
}
2、LoginInterceptor
import com.alibaba.fastjson.JSON;
import com.***.service.SysUserService;
import com.***.utils.UserThreadLocal;
import com.***.vo.ErrorCode;
import com.***.vo.LoginUserVo;
import com.***.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private SysUserService sysUserService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/**
* 在执行 Controller方法(Handler)之前进行
* 1、需要判断,请求的接口路径是否为 HandlerMethod(Controller方法)
* 2、判断 Token 是否为空,如果为空,即为未登录
* 3、如果 Token 不为空,进行登录验证 checkToken
* 4、如果认证成功,放行即可
*/
if (!(handler instanceof HandlerMethod)) {
/**
* handler 可能是 RequestResourceHandler,Springboot 程序访问静态资源,默认去 classpath 下的 static 目录查询
*/
return true;
}
String token = request.getHeader("Authorization");
log.info("=================request start===========================");
String requestURI = request.getRequestURI();
log.info("request uri:{}", requestURI);
log.info("request method:{}", request.getMethod());
log.info("token:{}", token);
log.info("=================request end===========================");
if (StringUtils.isBlank(token)) {
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
Result res = sysUserService.findUserInfoByToken(token);
if (!res.isSuccess()) {
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
/**
* 登录成功,放行
* 在 Controller 中,直接获取登录信息
*/
LoginUserVo loginUserVo = (LoginUserVo) res.getData();
UserThreadLocal.put(loginUserVo);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
/**
* 如果不删除,ThreadLocal 中用完的信息,有内存泄漏的风险
*/
UserThreadLocal.remove();
}
}
3、UserThreadLocal
public class UserThreadLocal {
private UserThreadLocal() {
}
/**
* 线程变量隔离
*/
private static final ThreadLocal<LoginUserVo> LOCAL = new ThreadLocal<>();
public static void put(LoginUserVo loginUserVo) {
LOCAL.set(loginUserVo);
}
public static LoginUserVo get() {
return LOCAL.get();
}
public static void remove() {
LOCAL.remove();
}
}
4、Result
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private boolean success;
private Integer code;
private String msg;
private Object data;
public static Result success(Object data) {
return new Result(true, 200, "success", data);
}
public static Result fail(Integer code, String msg) {
return new Result(false, code, msg, null);
}
}
5、ErrorCode
public enum ErrorCode {
PARAMS_ERROR(10001, "参数有误"),
ACCOUNT_PWD_NOT_EXIST(10002, "用户名或密码不存在"),
TOKEN_ERROR(10003, "Toekn不合法"),
ACCOUNT_EXIST(10004,"账号已存在"),
NO_PERMISSION(70001, "无访问权限"),
SESSION_TIME_OUT(90001, "会话超时"),
NO_LOGIN(90002, "未登录"),
;
private int code;
private String msg;
ErrorCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}