164105-15249048658fdeSpringCloud-Gateway微服务校验登录

登录流程分析

在原来的单体服务中,我们可以定义一个拦截器或者过滤器来拦截用户携带的认证信息,并且每个service可以很方便的获取到登录的用户信息。

但是在微服务的场景之下,需要在每一个微服务中都去配置解析token的拦截器,这并不安全且不现实。既然我们需要做登录校验,但是又不希望在每一个微服务中都重复的做登录校验,那我们就可以将这个工作交给我们的网关,GateWay。

此时我们登录校验的流程则变成了:

  1. 前端携带jwt令牌发起请求到网关
  2. 网关在请求路由之前做jwt令牌校验
  3. 后续的微服务需要使用到用户信息,则需要将用户信息传递到具体的微服务
  4. 微服务之间可能会发生调用,也需要将用户信息传递到其他微服务

1731819099648

代码实现

网关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) {
// 1.获取Request
ServerHttpRequest request = exchange.getRequest();
// 2.判断当前请求是否需要被拦截
if(isAllowPath(request)){
// 无需拦截,放行
return chain.filter(exchange);
}
// 3.获取token
String token = null;
List<String> headers = request.getHeaders().get("authorization");
if(headers != null) {
token = headers.get(0);
}
// 4.解析token
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();
}
// 5.传递用户信息到下游服务
String userInfo = userId.toString();
ServerWebExchange exc = exchange.mutate()
.request(builder -> builder.header("user-info", userInfo))
.build();
// 6.放行
return chain.filter(exc);
}

private boolean isAllowPath(ServerHttpRequest request) {
boolean flag = false;
// 1.获取当前路径
// String method = request.getMethodValue();
String path = request.getPath().toString();
// 2.要放行的路径
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 {
// 1.获取请求头中的用户
String userInfo = request.getHeader("user-info");
// 2.判断是否为空,不为空,存入UserContext
if (StrUtil.isNotBlank(userInfo)) {
UserContext.setUser(Long.valueOf(userInfo));
}
// 3.放行
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传递用户信息

image-20241124115850839

定义拦截器

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)