Spring GateWay OAuth 2 ResourceServer構成Server HttpSecurityでhasRoleが無効な問題

8263 ワード

文書ディレクトリ
  • 問題
  • ソリューション
  • JwtGrantedAuthoritiesConverterソース
  • カスタムReactiveAuthenticationManager
  • 完全構成コード
  • に質問
    gatewayがOAuth 2 ResourceServerとして機能すると、hasRole構成が無効になるという問題が発生します.
    なぜなら、ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpecのデフォルトのReactiveAuthenticationManagerがjwtのauthoritiesの負荷部分をAuthenticationの権限としていないからです.
    簡単に言えば、jwtのClaimのauthoritiesの値をここに入れる必要があるという個人的な質問ですが、公式にはscope``scpという2つのフィールドを権限として使うだけで十分だと考えられていますgithub issue
    ソリューションReactiveAuthenticationManagerパーミッションマネージャのデフォルトのパーミッションマネージャを再定義します.jwtGrantedAuthoritiesConverterをデフォルトのコンバータとして使用します.このコンバータはデフォルトで「scope」、「scp」の2つだけをパーミッションとして読み込みます.
    JwtGrantedAuthoritiesConverterソースコード
    ソースコードを見ると、ソースコードは長くありません.convertメソッドは変換器の呼び出しエントリとして理解でき,このメソッドから見ると
    /**
     * Extracts the {@link GrantedAuthority}s from scope attributes typically found in a
     * {@link Jwt}.
     *
     * @author Eric Deandrea
     * @since 5.2
     */
    public final class JwtGrantedAuthoritiesConverter implements Converter> {
    	private static final String DEFAULT_AUTHORITY_PREFIX = "SCOPE_";
    
    	private static final Collection WELL_KNOWN_AUTHORITIES_CLAIM_NAMES =
    			Arrays.asList("scope", "scp");
    
    	private String authorityPrefix = DEFAULT_AUTHORITY_PREFIX;
    
    	private String authoritiesClaimName;
    
    	/**
    	 * Extract {@link GrantedAuthority}s from the given {@link Jwt}.
    	 *
    	 * @param jwt The {@link Jwt} token
    	 * @return The {@link GrantedAuthority authorities} read from the token scopes
    	 */
    	@Override
    	public Collection convert(Jwt jwt) {
    		Collection grantedAuthorities = new ArrayList<>();
    		for (String authority : getAuthorities(jwt)) {
    			grantedAuthorities.add(new SimpleGrantedAuthority(this.authorityPrefix + authority));
    		}
    		return grantedAuthorities;
    	}
    
    	/**
    	 * Sets the prefix to use for {@link GrantedAuthority authorities} mapped by this converter.
    	 * Defaults to {@link JwtGrantedAuthoritiesConverter#DEFAULT_AUTHORITY_PREFIX}.
    	 *
    	 * @param authorityPrefix The authority prefix
    	 * @since 5.2
    	 */
    	public void setAuthorityPrefix(String authorityPrefix) {
    		Assert.hasText(authorityPrefix, "authorityPrefix cannot be empty");
    		this.authorityPrefix = authorityPrefix;
    	}
    
    	/**
    	 * Sets the name of token claim to use for mapping {@link GrantedAuthority authorities} by this converter.
    	 * Defaults to {@link JwtGrantedAuthoritiesConverter#WELL_KNOWN_AUTHORITIES_CLAIM_NAMES}.
    	 *
    	 * @param authoritiesClaimName The token claim name to map authorities
    	 * @since 5.2
    	 */
    	public void setAuthoritiesClaimName(String authoritiesClaimName) {
    		Assert.hasText(authoritiesClaimName, "authoritiesClaimName cannot be empty");
    		this.authoritiesClaimName = authoritiesClaimName;
    	}
    
    	private String getAuthoritiesClaimName(Jwt jwt) {
    
    		if (this.authoritiesClaimName != null) {
    			return this.authoritiesClaimName;
    		}
    
    		for (String claimName : WELL_KNOWN_AUTHORITIES_CLAIM_NAMES) {
    			if (jwt.containsClaim(claimName)) {
    				return claimName;
    			}
    		}
    		return null;
    	}
    
    	private Collection getAuthorities(Jwt jwt) {
    		String claimName = getAuthoritiesClaimName(jwt);
    
    		if (claimName == null) {
    			return Collections.emptyList();
    		}
    
    		Object authorities = jwt.getClaim(claimName);
    		if (authorities instanceof String) {
    			if (StringUtils.hasText((String) authorities)) {
    				return Arrays.asList(((String) authorities).split(" "));
    			} else {
    				return Collections.emptyList();
    			}
    		} else if (authorities instanceof Collection) {
    			return (Collection) authorities;
    		}
    
    		return Collections.emptyList();
    	}
    }
    

    実はgetAuthorities()という方法が核心です.コードを見るのは説明を見るより速く理解できます.
    また、ソースコードに基づいて、この変換器setAuthoritiesClaimNameの方法はAuthoritiesClaimNameをカスタマイズできることが分かった.注記は、マッピング権限(Authorities)token負荷(token claim)を設定する名前を明確に示しています.
    これで簡単にやりやすい.
    私たちは自分でコンバータを書く必要もありません
    カスタムReactiveAuthenticationManager
    ......
    
    private final
        OAuth2ResourceServerProperties.Jwt Properties;
        
    ......
    
    ReactiveAuthenticationManager getAuthenticationManager() {
            NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = new NimbusReactiveJwtDecoder(Properties.getJwkSetUri());
            JwtReactiveAuthenticationManager jwtReactiveAuthenticationManager = new JwtReactiveAuthenticationManager(nimbusReactiveJwtDecoder);
    
            JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
            jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
            jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities");
    
            JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
            jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
    
            ReactiveJwtAuthenticationConverterAdapter reactiveJwtAuthenticationConverterAdapter = new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
    
            jwtReactiveAuthenticationManager.setJwtAuthenticationConverter(reactiveJwtAuthenticationConverterAdapter);
            return jwtReactiveAuthenticationManager;
        }
    

    さらに、ServerHttpSecurityを設定するときに、前の文を追加します.
    ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec jwtSpec = http.oauth2ResourceServer().jwt();
            jwtSpec.authenticationManager(getAuthenticationManager());
    

    完全な構成コード
    /**
     * 

    Class: ResourceServerConfigurer

    * * @author GodDai * @version 1.0.0 * @since 2020/2/28 11:52 */ @EnableWebFluxSecurity public class ResourceServerConfigurer { private final OAuth2ResourceServerProperties.Jwt Properties; @Autowired public ResourceServerConfigurer(OAuth2ResourceServerProperties Properties) { this.Properties = Properties.getJwt(); } @Bean public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) { http.authorizeExchange() // options .pathMatchers(HttpMethod.OPTIONS).permitAll() // path // ! hasrole jwt .pathMatchers("/api/admin/**").hasRole("ROLE_ADMIN") // .pathMatchers("/**").permitAll() .anyExchange().authenticated() .and().cors().disable() .csrf().disable(); ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec jwtSpec = http.oauth2ResourceServer().jwt(); jwtSpec.authenticationManager(getAuthenticationManager()); SecurityWebFilterChain chain = http.build(); return chain; } ReactiveAuthenticationManager getAuthenticationManager() { NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = new NimbusReactiveJwtDecoder(Properties.getJwkSetUri()); JwtReactiveAuthenticationManager jwtReactiveAuthenticationManager = new JwtReactiveAuthenticationManager(nimbusReactiveJwtDecoder); JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_"); jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("authorities"); JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); ReactiveJwtAuthenticationConverterAdapter reactiveJwtAuthenticationConverterAdapter = new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter); jwtReactiveAuthenticationManager.setJwtAuthenticationConverter(reactiveJwtAuthenticationConverterAdapter); return jwtReactiveAuthenticationManager; } }

    注意!hasroleの値はjwt負荷の値と一致しなければならない.オンラインdebugjwt
    全編のコードを見てほとんど説明がないのではなく、説明がはっきりしていないので、ソースコードと注釈を見る時間を与えてください.とても分かりやすいです.
    この方法は純粋に私自身がでたらめに模索したもので、皆さんがその中の間違いを多く指導し、指摘し、子弟を誤解しないようにしてほしい.