搭建過程可以分為以下幾步
- 構建簡單的Spring Security + OAuth2.0 認證服務
- 優化認證服務(使用JWT技術加強token,自定義auth接口以及返回結果)
- 配置gateway服務完成簡單鑒權功能
- 優化gateway配置(添加復雜鑒權邏輯等等)
(四)優化網關鑒權,添加復雜鑒權邏輯
接上篇,對簡單的網關鑒權邏輯進行優化
一. 構建 AuthorizationManager 自定義鑒權邏輯
@Service
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
// 預檢請求直接放行
if (request.getMethod() == HttpMethod.OPTIONS) {
return Mono.just(new AuthorizationDecision(true));
}
// 如果token以"Bearer "為前綴,到此方法里說明JWT有效即已認證,其他前綴的token則攔截
String token = request.getHeaders().getFirst("Authorization");
if (StringUtils.isBlank(token) || !token.startsWith("Bearer ")) {
return Mono.just(new AuthorizationDecision(false));
}
return Mono.just(new AuthorizationDecision(true));
}
}
在之前創建的 springSecurityFilterChain 中進行配置
@Configuration
@EnableWebFluxSecurity
public class ResourceServerConfig {
@Autowired
private AuthorizationManager authenticationManager;
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.oauth2ResourceServer().jwt();
http.authorizeExchange()
// 配置鑒權
.anyExchange().access(authenticationManager)
.and().csrf().disable();
return http.build();
}
}
此時請求header中Authorization字段前綴非"Bearer "時就會被直接攔截
二. 配置鑒權白名單
如獲取Token這類接口,在發送請求的時候是沒法攜帶有效token的,如果請求走網關則會被攔截。此時就需要配置白名單,跳過鑒權。
在yaml中配置一些白名單路徑
secure:
ignore:
urls:
- "/user-service/login"
- "/pgcp-auth/oauth/token"
構建配置類,服務啟動時讀取yaml配置
@Component
@Data
@ConfigurationProperties(prefix="secure.ignore")
public class WhiteListUrlsConfig {
private List<String> urls;
}
在之前的 AuthorizationManager 中加入白名單邏輯
@Service
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
@Autowired
private WhiteListUrlsConfig whiteListUrlsConfig;
@Override
public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
ServerHttpRequest request = authorizationContext.getExchange().getRequest();
URI uri = request.getURI();
PathMatcher pathMatcher = new AntPathMatcher();
if (request.getMethod() == HttpMethod.OPTIONS) {
return Mono.just(new AuthorizationDecision(true));
}
// 鑒權白名單邏輯
List<String> ignoreUrls = whiteListUrlsConfig.getUrls();
for (String ignoreUrl : ignoreUrls) {
if (pathMatcher.match(ignoreUrl, uri.getPath())) {
return Mono.just(new AuthorizationDecision(true));
}
}
// 如果token以"Bearer "為前綴,到此方法里說明JWT有效即已認證,其他前綴的token則攔截
String token = request.getHeaders().getFirst(Auth.JWT_TOKEN_HEADER);
if (StringUtils.isBlank(token) || !token.startsWith(Auth.JWT_TOKEN_PREFIX)) {
return Mono.just(new AuthorizationDecision(false));
}
return Mono.just(new AuthorizationDecision(true));
}
}
驗證:通過網關路由生成token請求,可以看到在未攜帶token的情況下請求成功,返回了想要的結果。
三. 配置自定義gateway filter
可能在后續的請求需要用到userId這個字段,但是這個字段是被編碼在JWT中的。如果把JWT帶到后續的各種接口中,需要的時候進行解析,會產生大量重復代碼。
干脆就在gateway這里解析,並把需要的userId字段放置入header中,后續需要自取即可
自定義filter如下完成解析的工作
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst(Auth.JWT_TOKEN_HEADER);
if (StringUtils.isBlank(token)) {
return chain.filter(exchange);
}
try {
String accessToken = token.replace(Auth.JWT_TOKEN_PREFIX, "");
JWSObject object = JWSObject.parse(accessToken);
String userId = (String) object.getPayload().toJSONObject().get("userId");
// 從token中解析出userId字段設置到header中
ServerHttpRequest request = exchange.getRequest().mutate().header("userId", userId).build();
exchange = exchange.mutate().request(request).build();
} catch (ParseException e) {
Asserts.fail(ResultCode.UNAUTHORIZED);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
驗證如下,在filesystem-service中寫一個簡單的測試接口
@RestController
public class TestController {
@Autowired
private HttpServletRequest httpServletRequest;
@GetMapping("/t")
public String get(@RequestParam String str) {
return str + " " + httpServletRequest.getHeader("userId");
}
}
postman測試,成功!
四. 更加復雜的鑒權邏輯(Todo)
可以設置url對應的用戶角色訪問權限(用redis預先設置),配置鑒權邏輯進 AuthorizationManager 中。
Spring Security + OAuth2.0 構建微服務統一認證解決方案(一)
Spring Security + OAuth2.0 構建微服務統一認證解決方案(二)
Spring Security + OAuth2.0 構建微服務統一認證解決方案(三)
github 倉庫