文章目录
前言
app开发定制跳槽去了新公司,app开发定制研究公司的系统架构,app开发定制发现一个很有趣的思路:
GateWay app开发定制解析前端请求携带的token信息,app开发定制并向下游微服务传递。
达到下游微服务不用重复解析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@Componentpublic 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 登出判断 * @author yangs * Company: 北京九恒星科技股份有限公司 * @since 2020/8/10 19:20 */ 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
- 117
- 118
- 119
其中最为核心的部分在于:
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)@Componentpublic 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