微服务架构里的登录拦截器

网关层登录拦截器

定义一个登录拦截器类实现GlobalFilter全局过滤器,重写它的filter方法,形参是当前http服务器上下文,包含请求和响应的信息。还有一个形参是过滤链。

首先通过请求获取request对象,然后使用mutate方法创建请求修改器。

然后获取请求路径,如果请求路径是我们的登录接口,才会通过过滤链处理。

然后使用Sa-token框架提供的StpUtil工具类获取请求token信息,然后从token信息里面拿到登录id,再把这个登录id添加到我之前创建的请求修改器里面,其中key是loginId和后续每个微服务的登录拦截器做统一,value是用户具体的登录id。然后再把这个请求修改器继续处理过滤链,这样就实现了网关层的登录过滤器。

package com.jingdianjichi.club.gateway.filter;

import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.jingdianjichi.club.gateway.entity.Result;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 登录拦截器
 *
 */
@Component
@Slf4j
public class LoginFilter implements GlobalFilter {

    /**
     * 登录过滤器,用于在请求通过网关时进行登录验证。
     *
     * @param exchange 服务器web交换对象,包含请求和响应信息。
     * @param chain 网关过滤器链,用于继续处理过滤链中的下一个过滤器。
     * @return Mono<Void>,表示异步处理结果。
     * @throws Exception 如果验证失败或出现其他异常。
     */
    @Override
    @SneakyThrows
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取请求和响应对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        // 创建请求修改器
        ServerHttpRequest.Builder mutate = request.mutate();

        // 获取当前请求的URL路径
        String url = request.getURI().getPath();
        // 记录请求URL,用于日志记录
        log.info("LoginFilter.filter.url:{}", url);

        // 如果当前请求是登录请求,则直接通过过滤器链处理
        if (url.equals("/user/doLogin")) {
            return chain.filter(exchange);
        }

        // 获取令牌信息,用于登录验证
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
        // 记录令牌信息,用于日志记录
        log.info("LoginFilter.filter.url:{}", new Gson().toJson(tokenInfo));

        // 从令牌信息中获取登录ID
        String loginId = (String) tokenInfo.getLoginId();

        // 将登录ID添加到请求头中
        mutate.header("loginId", loginId);

        // 使用修改后的请求继续处理过滤链
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }


}

刷题微服务登录拦截器

首先定义一个登录拦截器,实现HandlerInterceptor接口的preHandle方法用于获取request的Header,拿到header里面的loginId,然后使用上下文登录类的set方法,创建key为loginId值为header里面获取的具体id值。这个上下文登录类用InheritableThreadLocal和map集合定义一个静态变量,存储线程局部的登录上下文信息。它的泛型是map结构,记录登录上下文映射。key是上下文信息的键,value是上下文信息的值。它里面有set,get操作map集合的方法,还有一个getloginid方法,用于获取key为loginid的value值。然后我单独把这个getloginID方法抽取处理作为有个登录工具类。
登录拦截器:

package com.jingdianjichi.subject.application.interceptor;

import com.jingdianjichi.subject.common.context.LoginContextHolder;
import org.apache.commons.lang3.StringUtils;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 登录拦截器
 *
 */
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String loginId = request.getHeader("loginId");
        if (StringUtils.isNotBlank(loginId)) {
            LoginContextHolder.set("loginId", loginId);
        }
        return true;
    }
//功能:此方法在控制器方法及其视图渲染完成之后调用。
//它调用LoginContextHolder.remove()来清除在preHandle中设置的loginId。这是为了确保每个请求的上下文独立,避免不同用户请求之间的数据污染。
//总之,LoginInterceptor主要负责在请求开始时从请求头中读取loginId并将其保存到一个线程局部的上下文中,在请求结束时清除该上下文,以保证线程安全和请求间的数据隔离。
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        LoginContextHolder.remove();
    }

}

登录上下文:

/**
 * 登录上下文持有类,用于在多线程环境下存储和访问与当前登录用户相关的上下文信息。
 * 采用线程局部变量(InheritableThreadLocal)来保证每个线程都有自己的上下文副本,且这些副本可以被子线程继承。
 */
package com.jingdianjichi.subject.common.context;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 登录上下文对象
 *
 */
public class LoginContextHolder {

    /**
     * 使用InheritableThreadLocal来存储线程局部的登录上下文信息。
     * InheritableThreadLocal比普通的ThreadLocal多了一个特性,就是其值会在创建新线程时被复制到新线程。
     */
    private static final InheritableThreadLocal<Map<String, Object>> THREAD_LOCAL
            = new InheritableThreadLocal<>();

    /**
     * 设置登录上下文信息。
     *
     * @param key   上下文信息的键。
     *
     */
    public static void set(String key, Object val) {
        Map<String, Object> map = getThreadLocalMap();
        map.put(key, val);
    }

    /**
     * 获取登录上下文信息。
     *
     * @param key 上下文信息的键。
     * @return 与键相关联的值,如果不存在则返回null。
     */
    public static Object get(String key){
        Map<String, Object> threadLocalMap = getThreadLocalMap();
        return threadLocalMap.get(key);
    }

    /**
     * 获取登录ID。
     *
     * @return 当前登录用户的ID,如果未登录则返回null。
     */
    public static String getLoginId(){
        return (String) getThreadLocalMap().get("loginId");
    }

    /**
     * 移除登录上下文信息。
     */
    public static void remove(){
        THREAD_LOCAL.remove();
    }

    /**
     * 获取当前线程的登录上下文映射。
     * 如果当前线程尚未设置登录上下文映射,则创建一个新的并发HashMap并设置为当前线程的值。
     *
     * @return 当前线程的登录上下文映射。
     */
    public static Map<String, Object> getThreadLocalMap() {
        Map<String, Object> map = THREAD_LOCAL.get();
        if (Objects.isNull(map)) {
            map = new ConcurrentHashMap<>();
            THREAD_LOCAL.set(map);
        }
        return map;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值