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


    前言

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

    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;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

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

    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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样,只要请求携带了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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

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

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    几个工具类

    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() {
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
  • 相关阅读:
    程序员第一次接私活?记住这三点让你事半功倍
    Docker安装pgAdmin4
    应变与几何方程——弹性力学
    mysql笔记
    MySQL 权限变更,何时生效?
    《攻守道》笔记(2)
    百度智能云千帆推出大模型普惠计划,0成本切换
    Dockerfile 语法教程
    HAProxy实现负载均衡
    聊一聊责任链模式
  • 原文地址:https://blog.csdn.net/qq_38322527/article/details/126530849