多言则背道,多欲则伤生。——林逋

按照文档里集成时发现一个问题:

https://sa-token.cc/doc.html#/micro/gateway-auth

其中在web-flux的网关处调用认证子服务进行鉴权,按照文档里进行配置后

checkPermission函数会调用StpInterface,然后我实现的StpInterface是同步的,本来用open-feign实现后,发现open-feign不支持webflux!虽然有个三方库 feign-reactive 可以支持,但考虑了下,还是采用webclient实现

但由于webclient此处不能阻塞调用,所以就手动实现SaReactorFilter完成封装

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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import cn.dev33.satoken.exception.BackResultException;
import cn.dev33.satoken.exception.StopMatchException;
import cn.dev33.satoken.reactor.context.SaReactorHolder;
import cn.dev33.satoken.reactor.context.SaReactorSyncHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.router.SaRouterStaff;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.namaste.rubengateway.api.webclient.AuthService;
import com.namaste.pojo.dto.RubenResponse;
import org.springframework.cloud.client.discovery.ReactiveDiscoveryClient;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
* SaTokenReactorFilter
*
* @author VampireAchao<achao @ hutool.cn>
* @since 2023/9/27
*/
@Component
public class SaTokenReactorFilter extends SaReactorFilter {

private final AuthService authService;
private final ReactiveDiscoveryClient discoveryClient;
private final LinkedHashMap<Supplier<SaRouterStaff>, Predicate<List<String>>>
routerRolesPreMap = new LinkedHashMap<>();
private final LinkedHashMap<Supplier<SaRouterStaff>, Predicate<List<String>>>
routerPermsPreMap = new LinkedHashMap<>();


public SaTokenReactorFilter(AuthService authService, ReactiveDiscoveryClient discoveryClient) {
this.authService = authService;
this.discoveryClient = discoveryClient;

// 拦截地址
addInclude("/**");
// 开放地址
addExclude(
"/auth-service/login/loginWithPhone",
"/auth-service/login/loginWithPassword",
"/auth-service/sms/send");

setAuth(router -> {
// 登录校验 -- 拦截所有路由 用于开放登录
SaRouter.match("/**").check(r -> StpUtil.checkLogin());
});

// 权限认证 -- 不同模块, 校验不同权限
addRoleChecker(() -> SaRouter.match("/user-service/test"),
roles -> roles.contains("user"));

ruben(() -> SaRouter.match("/user-service/test"),
permissions -> permissions.contains("user:info:list"));

}

private void addRoleChecker(Supplier<SaRouterStaff> matcherSupplier, Predicate<List<String>> rolesPredicate) {
routerRolesPreMap.put(matcherSupplier, rolesPredicate);
}

private void addPermsChrubenSaRouterStaff> matcherSupplier, Predicate<List<String>> permsPredicate) {
routerPermsPreMap.put(matcherSupplier, permsPredicate);
}


public static Mono<Void> writeJsonResponse(ServerWebExchange exchange, Object result) {
if (exchange.getResponse().getHeaders().getFirst(Header.CONTENT_TYPE.getValue()) == null) {
exchange.getResponse().getHeaders().set(Header.CONTENT_TYPE.getValue(),
ContentType.build(ContentType.JSON, StandardCharsets.UTF_8));
}
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory()
.wrap(JacksonUtils.toJson(result).getBytes())));
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// ================ Sa-Token处理(下方有改动请注释注明,升级sa-token版本时用) ==================

// 写入WebFilterChain对象
exchange.getAttributes().put(SaReactorHolder.CHAIN_KEY, chain);

// ---------- 全局认证处理
try {
// 写入全局上下文 (同步)
SaReactorSyncHolder.setContext(exchange);

// 执行全局过滤器
beforeAuth.run(null);
SaRouter.match(includeList).notMatch(excludeList).check(saRouterStaff -> {
auth.run(null);
});

} catch (StopMatchException e) {
// StopMatchException 异常代表:停止匹配,进入Controller
} catch (Throwable e) {
// 1. 获取异常处理策略结果
String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));
return writeJsonResponse(exchange,RubenwResponse.fail(result));
} finally {
// 清除上下文
SaReactorSyncHolder.clearContext();
}

// ---------- 执行

// 写入全局上下文 (同步)
SaReactorSyncHolder.setContext(exchange);

// ================ Sa-Token处理结束(上方有改动请注释注明,升级sa-token版本时用) =================

Supplier<Mono<Void>> successSupplier = () -> chain.filter(exchange)
// 写入全局上下文 (异步)
.contextWrite(ctx -> ctx.put(SaReactorHolder.CONTEXT_KEY, exchange))
// 清除上下文
.doFinally(r -> SaReactorSyncHolder.clearContext());

List<Predicate<List<String>>> rolePredicates = routerRolesPreMap.entrySet().stream()
.filter(s -> s.getKey().get().match(includeList).notMatch(excludeList).isHit())
.map(Map.Entry::getValue).toList();
List<Predicate<List<String>>> permPredicates = routerPermsPreMap.entrySet().stream()
.filter(s -> s.getKey().get().match(includeList).notMatch(excludeList).isHit())
.map(Map.Entry::getValue).toList();

if (rolePredicates.isEmpty() && permPredicates.isEmpty()) {
return successSupplier.get();
}

Mono<Boolean> roleMono = rolePredicates.isEmpty() ? Mono.just(true) :
authService.getRoleList(StpUtil.getLoginId(), StpUtil.getLoginType())
.map(roles -> rolePredicates.stream()
.reduce(Predicate::and)
.orElseGet(() -> o -> true).test(roles));
Mono<Boolean> permissionMono = permPredicates.isEmpty() ? Mono.just(true) :
authService.getPermissionList(StpUtil.getLoginId(), StpUtil.getLoginType())
.map(permissions -> permPredicates.stream()
.reduce(Predicate::and)
.orElseGet(() -> o -> true).test(permissions));

return Mono.zip(roleMono, permissionMono).flatMap(tuple -> {
if (tuple.getT1() && tuple.getT2()) {
return successSupplier.get();
}
return writeJsonResponse(exchangeRubenswResponse.fail("权限不足"));
});
}


}

主要就是这块代码:

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
Supplier<Mono<Void>> successSupplier = () -> chain.filter(exchange)
// 写入全局上下文 (异步)
.contextWrite(ctx -> ctx.put(SaReactorHolder.CONTEXT_KEY, exchange))
// 清除上下文
.doFinally(r -> SaReactorSyncHolder.clearContext());

List<Predicate<List<String>>> rolePredicates = routerRolesPreMap.entrySet().stream()
.filter(s -> s.getKey().get().match(includeList).notMatch(excludeList).isHit())
.map(Map.Entry::getValue).toList();
List<Predicate<List<String>>> permPredicates = routerPermsPreMap.entrySet().stream()
.filter(s -> s.getKey().get().match(includeList).notMatch(excludeList).isHit())
.map(Map.Entry::getValue).toList();

if (rolePredicates.isEmpty() && permPredicates.isEmpty()) {
return successSupplier.get();
}

Mono<Boolean> roleMono = rolePredicates.isEmpty() ? Mono.just(true) :
authService.getRoleList(StpUtil.getLoginId(), StpUtil.getLoginType())
.map(roles -> rolePredicates.stream()
.reduce(Predicate::and)
.orElseGet(() -> o -> true).test(roles));
Mono<Boolean> permissionMono = permPredicates.isEmpty() ? Mono.just(true) :
authService.getPermissionList(StpUtil.getLoginId(), StpUtil.getLoginType())
.map(permissions -> permPredicates.stream()
.reduce(Predicate::and)
.orElseGet(() -> o -> true).test(permissions));

return Mono.zip(roleMono, permissionMono).flatMap(tuple -> {
if (tuple.getT1() && tuple.getT2()) {
return successSupplier.get();
}
return writeJsonResponse(exchangeRubenswResponse.fail("权限不足"));
});

使用的话,setAuth里可以进行登陆认证,角色和权限认证就使用addRoleCheckerruben即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 拦截地址
addInclude("/**");
// 开放地址
addExclude(
"/auth-service/login/loginWithPhone",
"/auth-service/login/loginWithPassword",
"/auth-service/sms/send");

setAuth(router -> {
// 登录校验 -- 拦截所有路由 用于开放登录
SaRouter.match("/**").check(r -> StpUtil.checkLogin());
});

// 权限认证 -- 不同模块, 校验不同权限
addRoleChecker(() -> SaRouter.match("/user-service/test"),
roles -> roles.contains("user"));

ruben(() -> SaRouter.match("/user-service/test"),
permissions -> permissions.contains("user:info:list"));