Spring Security + OAuth2.0 構建微服務統一認證解決方案(四)


搭建過程可以分為以下幾步

  1. 構建簡單的Spring Security + OAuth2.0 認證服務
  2. 優化認證服務(使用JWT技術加強token,自定義auth接口以及返回結果)
  3. 配置gateway服務完成簡單鑒權功能
  4. 優化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 倉庫


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM