Threadlocal+拦截器+JWT实现登录

很多数据库表都会有创建时间和修改时间,这个可以用mp的自动填充来实现。

也有修改人和更新人的字段,用户登录进来后,修改数据如何拿到修改人呢?每次操作不能把操作人的信息都携带者,那么如何拿到修改人的数据,主要是需要拿到id。

JWT

http协议本身是一种无状态的协议,如果用户向服务器提供了用户名和密码来进行用户认证,下次请求时,用户还要再一次进行用户认证才行。因为根据http协议,服务器并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储─份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样应用就能识别请求来自哪个用户

JWT JSON Web Token(JSON Web令牌)
是一个开放标准(rfc7519),它定义了一种紧凑的、自包含的方式,用于在各方之间以JSON对象安全地传输信息。此信息可以验证和信任,因为它是数字签名的。jwt可以使用秘密〈使用HNAC算法)或使用RSA或ECDSA的公钥/私钥对进行签名。

JWT作用:
授权:一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。它的开销很小并且可以在不同的域中使用。如:单点登录。
信息交换:在各方之间安全地传输信息。JWT可进行签名(如使用公钥/私钥对),因此可确保发件人。由于签名是使用标头和有效负载计算的,因此还可验证内容是否被篡改。

image.png
image.png

后端将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。
image.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈阿星

您的支持是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值