网关层登录拦截器
定义一个登录拦截器类实现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;
}
}