SpringCloud-Gateway微服务校验登录
登录流程分析
在原来的单体服务中,我们可以定义一个拦截器或者过滤器来拦截用户携带的认证信息,并且每个service可以很方便的获取到登录的用户信息。
但是在微服务的场景之下,需要在每一个微服务中都去配置解析token的拦截器,这并不安全且不现实。既然我们需要做登录校验,但是又不希望在每一个微服务中都重复的做登录校验,那我们就可以将这个工作交给我们的网关,GateWay。
此时我们登录校验的流程则变成了:
- 前端携带jwt令牌发起请求到网关
- 网关在请求路由之前做jwt令牌校验
- 后续的微服务需要使用到用户信息,则需要将用户信息传递到具体的微服务
- 微服务之间可能会发生调用,也需要将用户信息传递到其他微服务

代码实现
网关GlobalFilter
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
| import com.xiaoyun.config.AuthProperties; import com.xiaoyun.utils.JwtTool; import lombok.RequiredArgsConstructor; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.util.AntPathMatcher; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono;
import javax.annotation.Resource; import java.util.List;
@Component @RequiredArgsConstructor public class LoginGlobalFilter implements GlobalFilter, Ordered {
@Resource private final AuthProperties authProperties;
private final AntPathMatcher pathMatcher = new AntPathMatcher();
@Resource private final JwtTool jwtTool;
@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); if(isAllowPath(request)){ return chain.filter(exchange); } String token = null; List<String> headers = request.getHeaders().get("authorization"); if(headers != null) { token = headers.get(0); } Long userId = null; try { userId = jwtTool.parseToken(token); System.out.println("userId = " + userId); } catch (Exception e) { ServerHttpResponse response = exchange.getResponse(); response.setRawStatusCode(401); return response.setComplete(); } String userInfo = userId.toString(); ServerWebExchange exc = exchange.mutate() .request(builder -> builder.header("user-info", userInfo)) .build(); return chain.filter(exc); }
private boolean isAllowPath(ServerHttpRequest request) { boolean flag = false; String path = request.getPath().toString(); for (String excludePath : authProperties.getExcludePaths()) { boolean isMatch = pathMatcher.match(excludePath, path); if(isMatch){ flag = true; break; } } return flag; }
@Override public int getOrder() { return 0; } }
|
网关拦截器将用户信息放到了请求体中,具体的微服务需要获取这个用户信息,存储到threadLocal中
用户信息过滤器及ThreadLocal工具类
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
| import cn.hutool.core.util.StrUtil; import com.xiaoyun.common.utils.UserContext; import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse;
public class UserInfoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String userInfo = request.getHeader("user-info"); if (StrUtil.isNotBlank(userInfo)) { UserContext.setUser(Long.valueOf(userInfo)); } return true; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { UserContext.removeUser(); } }
|
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
| public class UserContext { private static final ThreadLocal<Long> tl = new ThreadLocal<>();
/** * 保存当前登录用户信息到ThreadLocal * @param userId 用户id */ public static void setUser(Long userId) { tl.set(userId); }
/** * 获取当前登录用户信息 * @return 用户id */ public static Long getUser() { return tl.get(); }
/** * 移除当前登录用户信息 */ public static void removeUser(){ tl.remove(); } }
|
OpenFeign传递用户信息

定义拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13
| import com.xiaoyun.common.utils.UserContext; import feign.RequestInterceptor; import feign.RequestTemplate;
public class UserInfoInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { Long userId = UserContext.getUser(); if (userId != null) { template.header("user-info", userId.toString()); } } }
|
配置拦截器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import feign.Logger; import feign.RequestInterceptor; import org.springframework.context.annotation.Bean;
public class FeignLogLevelConfig {
@Bean public Logger.Level feignLogLevel(){ return Logger.Level.FULL; }
@Bean public RequestInterceptor userInfoInterceptor(){ return new UserInfoInterceptor(); } }
|
配置配置类
1
| @EnableFeignClients(defaultConfiguration = FeignLogLevelConfig.class)
|