簡體   English   中英

用於基於 JWT 的身份驗證、驗證和授權方案的 Spring Security 過濾器,示例

[英]Spring Security filters for JWT-based authentication, verification and authorization scheme, by example

Java + Spring(和 Spring Security)在這里,有興趣使用承載令牌為我的 Web 服務實現基於 JWT 的身份驗證機制。 使用 Spring Security 進行身份驗證和授權的正確方法的理解是通過使用提供的(或自定義的)過濾器,如下所示:

  • 您指定應用程序中的哪些 URL 是經過身份驗證的(因此需要經過身份驗證的請求才能訪問)
    • 這通常在擴展WebSecurityConfigurerAdapter@EnableWebSecurity Web 安全類中WebSecurityConfigurerAdapter
  • 對於任何未經身份驗證的 URL,任何過濾器都不應阻止對所請求資源的訪問
  • 身份驗證過濾器有效地提供了“登錄”端點
    • 請求客戶端最初應點擊此登錄端點(身份驗證過濾器)以獲取可用於進行后續 API 調用的身份驗證令牌
    • 此過濾器應接收一種類型的“登錄請求”對象,其中包含主體(例如用戶名)和憑據(例如密碼)
    • 此身份驗證過濾器應使用登錄請求中包含的主體/憑據來確定它們是否代表系統中的有效用戶
      • 如果是這樣,則會生成一個身份驗證令牌(JWT 等)並以某種方式在響應中將其發送回請求者
      • 否則,如果主體/憑證與系統中的有效用戶不匹配,則返回錯誤響應並且身份驗證失敗
  • 對於經過身份驗證的 URL,驗證過濾器會驗證請求是否包含身份驗證令牌以及身份驗證令牌是否有效(已正確簽名,包含用戶信息,例如 JWT 聲明,未過期等)
    • 如果身份驗證令牌有效,則請求繼續到授權過濾器(見下文)
    • 否則,如果身份驗證令牌無效,則驗證失敗並且過濾器將錯誤響應發送回客戶端
  • 最后,授權過濾器驗證與有效身份驗證令牌關聯的用戶是否具有發出此類請求的能力/權限
    • 如果他們這樣做,那么請求被允許繼續到任何資源/控制器被寫入來處理它,並且該資源/控制器將響應提供給請求者
    • 如果沒有,則向客戶端返回錯誤響應
    • 理想情況下,此 authz 過濾器中的邏輯(代碼)可以訪問添加到資源方法的權限注釋,以便我可以添加端點並為其指定權限,而無需修改 authz 過濾器的代碼

因此,首先,如果我上面所說的任何內容是 Spring Security(或一般的 Web 安全)反模式或被誤導,請首先提供課程更正並引導我朝着正確的方向前進!

假設我或多或少正確理解了上面的“身份驗證流程”......

是否有任何特定的 Spring Security 過濾器已經為我處理了所有這些問題,或者可以擴展並覆蓋一些方法來以這種方式運行? 或者任何非常接近的東西? 查看特定於身份驗證的 Spring Security 過濾器列表,我看到:

  • UsernamePasswordAuthenticationFilter -> 看起來像是 authn 過濾器的一個不錯的候選者,但在查詢字符串中需要一個usernamepassword參數,這對我來說很奇怪,最重要的是,它不會生成 JWT
  • CasAuthenticationFilter -> 看起來像是用於基於 CAS 的 SSO,不適合在非 SSO 上下文中使用
  • BasicAuthenticationFilter -> 用於基於 HTTP 基本身份驗證的身份驗證,不適用於更復雜的設置

至於令牌驗證和授權,我(出乎我的意料)在 Spring Security 環境中看不到任何符合條件的內容。

除非有人知道我可以輕松使用或子類化的特定於 JWT 的過濾器,否則我認為我需要實現自己的自定義過濾器,在這種情況下,我想知道如何配置 Spring Security 以使用它們而不使用任何其他身份驗證過濾器(例如UsernamePasswordAuthenticationFilter )作為過濾器鏈的一部分。

據我了解,您想要:

  1. 通過用戶名和密碼驗證用戶並使用 JWT 進行響應
  2. 在后續請求中,使用該 JWT 對用戶進行身份驗證

username/password -> JWT本身並不是一種既定的身份驗證機制,這就是 Spring Security 還沒有直接支持的原因。

不過,您可以很容易地自行獲取它。

首先,創建一個生成 JWT 的/token端點:

@RestController
public class TokenController {

    @Value("${jwt.private.key}")
    RSAPrivateKey key;

    @PostMapping("/token")
    public String token(Authentication authentication) {
        Instant now = Instant.now();
        long expiry = 36000L;
        // @formatter:off
        String scope = authentication.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.joining(" "));
        JWTClaimsSet claims = new JWTClaimsSet.Builder()
                .issuer("self")
                .issueTime(new Date(now.toEpochMilli()))
                .expirationTime(new Date(now.plusSeconds(expiry).toEpochMilli()))
                .subject(authentication.getName())
                .claim("scope", scope)
                .build();
        // @formatter:on
        JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).build();
        SignedJWT jwt = new SignedJWT(header, claims);
        return sign(jwt).serialize();
    }

    SignedJWT sign(SignedJWT jwt) {
        try {
            jwt.sign(new RSASSASigner(this.key));
            return jwt;
        }
        catch (Exception ex) {
            throw new IllegalArgumentException(ex);
        }
    }

}

其次,配置 Spring Security 以允許 HTTP Basic(對於/token端點)和 JWT(對於其余部分):

@Configuration
public class RestConfig extends WebSecurityConfigurerAdapter {

    @Value("${jwt.public.key}")
    RSAPublicKey key;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // @formatter:off
        http.authorizeRequests((authz) -> authz.anyRequest().authenticated())
            .csrf((csrf) -> csrf.ignoringAntMatchers("/token"))
            .httpBasic(Customizer.withDefaults())
            .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
            .sessionManagement((session) -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .exceptionHandling((exceptions) -> exceptions
                .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
            );
        // @formatter:on
    }

    @Bean
    UserDetailsService users() {
        // @formatter:off
        return new InMemoryUserDetailsManager(
            User.withUsername("user")
                .password("{noop}password")
                .authorities("app")
                .build());
        // @formatter:on
    }

    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.key).build();
    }

}

我認為有興趣在spring-authorization-server添加對此類內容的支持以減少/token樣板,如果您有興趣貢獻您的努力!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM