GateWay——向其他服务传递参数数据(思路)

本文介绍了一种利用Spring Cloud Gateway实现JWT认证的方式。通过在网关层面解析前端请求携带的token信息,并向下游微服务传递,避免了每个服务重复解析token。详细介绍了如何在Gateway中增加过滤器,以及如何在下游服务中解析已验证的用户信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

跳槽去了新公司,研究公司的系统架构,发现一个很有趣的思路:

GateWay 解析前端请求携带的token信息,并向下游微服务传递。

达到下游微服务不用重复解析token,就能获取当前登录账户的基本信息

其实原理很简单,但记录下实现方式。

GateWay 增加 filter

在gateway网关服务中,增加filter 过滤器,主要实现获取请求接口中携带的token信息解析token将解析数据继续存放至当前请求对象中

具体实现方式如下所示:

import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CreateCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.util.Date;

@Slf4j
@Component
public class UASFilter implements GlobalFilter, Ordered {

	@CreateCache(name = "uas:user:login:")
	private Cache<String, String> tokenCache;

	/**
	 * 1.首先网关检查token是否有效,无效直接返回401,不调用签权服务
	 * 2.调用签权服务器看是否对该请求有权限,有权限进入下一个filter,没有权限返回401
	 *
	 * @param exchange
	 * @param chain
	 * @return
	 */
	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		ServerHttpRequest request = exchange.getRequest();
		URI uri = request.getURI();
		// 判断是否属于白名单中
		if(white(uri.getPath())){
			return chain.filter(exchange);
		}

		log.debug("**********UASFilter start: " + new Date());
		try {
//			ServerHttpRequest request = exchange.getRequest();
			// 获取原始token信息
			String authentication = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
			String method = request.getMethodValue();
			String url = request.getPath().value();
			log.debug("url:{},method:{},headers:{}", url, method, request.getHeaders());
			// 根据环境判断是否校验
			if (permissionService.ignoreAuthenticationByModule() || isSwaggerUrl(exchange.getRequest().getPath().value())) {
				return chain.filter(exchange);
			}

			//不需要网关签权的url
			if (permissionService.ignoreAuthentication(url)) {
				return chain.filter(exchange);
			}

			//登出判断
			if (isLogout(authentication)) {
				log.error("已经登出或者在其他设备登录,请重新登录!");
				return unauthorized(exchange, "已经登出或者在其他设备登录,请重新登录!");
			}
			
			// 核心!!!!!
			//调用签权服务看用户是否有权限,若有权限进入下一个filter
			if (permissionService.hasPermission(authentication, url, method)) {
				ServerHttpRequest.Builder builder = request.mutate();
				// 原始jwt token
				builder.header(GatewayConstans.X_CLIENT_TOKEN, authentication);
				//将jwt token中的用户信息传给服务
				builder.header(GatewayConstans.X_CLIENT_TOKEN_USER, permissionService.getUserTokenBase64(authentication));
				return chain.filter(exchange.mutate().request(builder.build()).build());
			}
			return unauthorized(exchange);
		} finally {
			log.debug("**********UASFilter end: " + new Date());
		}
	}

	/**
	 * @param token
	 * @return boolean
	 * @throws
	 * @description 登出判断
	 */
	private boolean isLogout(String token) {
		if (StringUtil.isNotEmpty(token) && token.startsWith("Bearer")) {
			token = token.replace("Bearer", "").trim();
			String loginToken = tokenCache.get(MD5Util.standardMD5(token));
			return StringUtil.isBlank(loginToken);
		}
		return true;
	}
	
	/**
	 * 网关拒绝,返回401
	 *
	 * @param msg
	 */
	protected Mono<Void> unauthorized(ServerWebExchange serverWebExchange, String... msg) {
		DataBuffer buffer = serverWebExchange.getResponse()
				.bufferFactory().wrap(JSON.toJSONBytes(
						CommonResult.error(HttpStatus.UNAUTHORIZED.value(), "未授权!" + (msg.length > 0 ? msg[0] : ""))));
		serverWebExchange.getResponse().getHeaders()
				.add("Content-Type", "json/plain;charset=UTF-8");
		serverWebExchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
		log.error("未授权!", msg);
		return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
	}

	@Override
	public int getOrder() {
		// 调用优先级, 数字小 优先级高
		return 20;
	}
}

其中最为核心的部分在于:

if (permissionService.hasPermission(authentication, url, method)) {
	// 获取当前的请求对象信息
	ServerHttpRequest.Builder builder = request.mutate();
	// 原始jwt token
	builder.header(GatewayConstans.X_CLIENT_TOKEN, authentication);
	// 向header中设置新的key,存储解析好的token对应基本信息
	builder.header(GatewayConstans.X_CLIENT_TOKEN_USER, permissionService.getUserTokenBase64(authentication));
	// exchange.mutate().request(builder.build()).build() 将其继续转化为请求对象
	// 向下游传递
	return chain.filter(exchange.mutate().request(builder.build()).build());
}

这样,只要请求携带了token,并能够成功解析,就会在请求对象的header数据部分,打上x-client-token-user解析后的数据。

其他服务解析

当gateway网关验证完毕后,合法的请求将会继续向内执行,当进入到对应的模块时,此时只需要从请求中获取x-client-token-user对应的登录账户解析数据,并将其保存至ThreadLocal中即可。

一样可以使用filter 过滤器,使用拦截器也可以!

核心代码如下所示:

import org.springframework.web.filter.OncePerRequestFilter;

@Order(-1000)
@Component
public class TokenAuthenticationFilter extends OncePerRequestFilter {
	
	// 重写  doFilterInternal  方法即可
	protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
		// 获取请求中header的 x-client-token-user信息
        String userToken = httpServletRequest.getHeader("x-client-token-user");
        if (userToken != null) {
            String json = EncryptUtil.decodeUTF8StringBase64(userToken);
            JSONObject jsonObject = JSON.parseObject(json);
            HashMap profile = (HashMap)JSON.parseObject(jsonObject.getString("user_name"), HashMap.class);
            // 这里是 ThreadLocal 的封装,将获取到的数据存放其中
            UserContextHolder.getInstance().setContext(profile);
        } else {
            UserContextHolder.getInstance().setContext(this.getParamMap());
        }

        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

在需要使用的地方,采取下列方式获取即可:

public static Profile getProfile() {
    Map<String, String> data = UserContextHolder.getInstance().getContext();
    if (null == data) {
        throw new RuntimeException("当前请求没有通过网关监控,无法加载登录用户信息!");
    } else {
        Profile profile = (Profile)BeanUtil.toBean(data, Profile.class);
        logger.debug("当前登陆用户信息:{}", profile.toString());
        return profile;
    }
}

几个工具类

UserContextHolder .java

import java.util.Map;

public class UserContextHolder {
    private ThreadLocal<Map<String, String>> threadLocal;

    private UserContextHolder() {
        this.threadLocal = new ThreadLocal();
    }

    public static UserContextHolder getInstance() {
        return UserContextHolder.SingletonHolder.sInstance;
    }

    public void setContext(Map<String, String> map) {
        this.threadLocal.set(map);
    }

    public Map<String, String> getContext() {
        return (Map)this.threadLocal.get();
    }

    public void clear() {
        this.threadLocal.remove();
    }

    private static class SingletonHolder {
        private static final UserContextHolder sInstance = new UserContextHolder();

        private SingletonHolder() {
        }
    }
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值