很多数据库表都会有创建时间和修改时间,这个可以用mp的自动填充来实现。
也有修改人和更新人的字段,用户登录进来后,修改数据如何拿到修改人呢?每次操作不能把操作人的信息都携带者,那么如何拿到修改人的数据,主要是需要拿到id。
JWT
http协议本身是一种无状态的协议,如果用户向服务器提供了用户名和密码来进行用户认证,下次请求时,用户还要再一次进行用户认证才行。因为根据http协议,服务器并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储─份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样应用就能识别请求来自哪个用户。
JWT JSON Web Token(JSON Web令牌)
是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密〈使用HNAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。
JWT作用:
授权:一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。它的开销很小并且可以在不同的域中使用。如:单点登录。
信息交换:在各方之间安全地传输信息。JWT可进行签名(如使用公钥/私钥对),因此可确保发件人。由于签名是使用标头和有效负载计算的,因此还可验证内容是否被篡改。
后端将token传给前端的时候,放入了id进去。前端再次访问的时候可以拿到用户的id。后端通过拦截器拦截。
JWT封装成工具类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
//将密钥 和数据加密生产token
//创建token 三个参数 密钥 过期时间 需要保存的数据
public static String createJWT(String secretKey, long ttlMills, Map<String, Object> claims) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
long expMills = System.currentTimeMillis() + ttlMills; //存储时间
Date exp = new Date(expMills); //时间转为date
JwtBuilder builder = Jwts.builder()
.setClaims(claims) //载荷
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
.setExpiration(exp); //过期时间
return builder.compact(); // 变为字符串
}
/*
根据token解析得到里面的数据
*/
public static Claims parseJWT(String secretKey, String token) {
try {
Claims claims = Jwts.parser().setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(token).getBody();
return claims;
}catch (Exception e){
throw new RuntimeException("token失效");
}
}
}
密钥和时间配置存在application.yml文件中
@Data
@Component
@ConfigurationProperties(prefix = "shop.jwt")
public class JwtProperties {
private String secret; // 密钥
private int expire; // 过期时间(分钟)
}
shop:
jwt:
secret: njitzx
expire: 60480000
拦截器
拦截器是SpringMVC下面的。
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//添加拦截器
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**").excludePathPatterns("/captchaLogin",
"/captchaImage", "/login","/alipay/notify");
}
}
通过实现 WebMvcConfigurer 接口配置 Spring MVC。
将 LoginInterceptor 添加到拦截器注册表中,使其拦截所有进入的请求,
除了那些在 excludePathPatterns 中指定的路径(如登录接口、验证码接口等)。
package com.njitzx.interceptor;
import com.njitzx.exception.LoginErrorException;
import com.njitzx.properties.JwtProperties;
import com.njitzx.util.BaseContext;
import com.njitzx.util.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@RestController //实现拦截器的接口
public class LoginInterceptor implements HandlerInterceptor {
//请求之前拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("进行登录拦截器");
//
if (!(handler instanceof HandlerMethod)){
return true;
}
简单来说,这个条件检查确保了只有那些由控制器方法(HandlerMethod)处理的请求才会被
拦截器进一步处理。对于静态资源、错误页面、自定义处理器等非控制器方法处理的请求,
拦截器会自动放行,不进行额外的拦截操作。
String token = request.getHeader("Authorization"); //从请求头中拿到token
try {
Claims claims = JwtUtil.parseJWT("njitzx", token);
Integer id = claims.get("id", Integer.class);
BaseContext.setCurrentId(id);
System.out.println("用户的id" + id);
return true;
}catch (Exception ex){
//如果返回 直接返回401
response.setStatus(401);
return false;
}
}
}
- 实现了
HandlerInterceptor
接口,重点实现了preHandle
方法。 preHandle
方法会检查请求头中是否有一个有效的 JWT。- 如果 token 有效,会提取用户的 ID 并存储到
BaseContext
中以供后续使用。 - 如果 token 无效或缺失,则将响应状态设置为
401 Unauthorized
,并阻止请求继续处理。
Threadlocal
ThreadLocal叫做_线程变量,意思是ThreadLocal中填充的变量_属于**当前线程**,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
每个用户进来之后的threadLocal存储的值都是不一样的,具有隔离的特性。
public class BaseContext {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Integer id) {
threadLocal.set(id);
}
public static Integer getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}
在拦截器那边通过token拿到id,并存入到threadlocal
Integer id = claims.get("id", Integer.class);
BaseContext.setCurrentId(id);
登进系统之后需要根据根据不同的人显示不同的菜单,如何表示当前的用户呢,请求的时候走拦截器可以拿到id。