spring webflux 的资源服务器范围不足
Insufficient scope in resource server with spring webflux
我在 spring-boot 2.1.3.RELEASE 中设置使用 Webflux 的 ResourceServer 时遇到问题。用于身份验证的相同令牌在不使用 Webflux 的资源服务器上工作正常,如果我设置 .permitAll()
它也可以工作(很明显)。这是我的资源服务器配置
授权服务器使用 jwt 令牌存储。
@EnableWebFluxSecurity
public class ResourceServerConfig {
@Value("${security.oauth2.resource.jwt.key-value}")
private String publicKey;
@Autowired Environment env;
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http
.authorizeExchange()
.pathMatchers("/availableInspections/**").hasRole("READ_PRIVILEGE")
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtDecoder(reactiveJwtDecoder())
;
return http.build();
}
@Bean
public ReactiveJwtDecoder reactiveJwtDecoder() throws Exception{
return new NimbusReactiveJwtDecoder(getPublicKeyFromString(publicKey));
}
public static RSAPublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
String publicKeyPEM = key;
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
byte[] encoded = Base64.getMimeDecoder().decode(publicKeyPEM);
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
return pubKey;
}
}
我得到的错误是
WWW-Authenticate: Bearer error="insufficient_scope", error_description="The token provided has insufficient scope [read] for this request", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1", scope="read"
我已验证令牌具有所需的范围...
我需要 change/add 什么才能正确读取我的令牌?
Spring 似乎只添加了 jwt 令牌中 scope
下的内容,忽略了 authorities
中的所有内容 - 因此它们不能用于 webflux 资源服务器,除非我们扩展 JwtAuthenticationConverter 以在令牌中添加 from authorities(或其他声明)。在安全配置中,我添加了 jwtAuthenticationConverter
http
// path config like above
.oauth2ResourceServer()
.jwt()
.jwtDecoder(reactiveJwtDecoder())
.jwtAuthenticationConverter(converter())
并已覆盖 JwtAuthenticationConverter
以包括 authorities
授予的权限:
public class MyJwtAuthenticationConverter extends JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
Arrays.asList("scope", "scp", "authorities"); // added authorities
protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
return this.getScopes(jwt)
.stream()
.map(authority -> SCOPE_AUTHORITY_PREFIX + authority)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
private Collection<String> getScopes(Jwt jwt) {
Collection<String> authorities = new ArrayList<>();
// add to collection instead of returning early
for ( String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES ) {
Object scopes = jwt.getClaims().get(attributeName);
if (scopes instanceof String) {
if (StringUtils.hasText((String) scopes)) {
authorities.addAll(Arrays.asList(((String) scopes).split(" ")));
}
} else if (scopes instanceof Collection) {
authorities.addAll((Collection<String>) scopes);
}
}
return authorities;
}
}
有点晚了,但更简单的解决方案可能是(在 kotlin 中,但很容易翻译成 java):
class CustomJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationToken> {
private val jwtGrantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
override fun convert(source: Jwt): AbstractAuthenticationToken {
val scopes = jwtGrantedAuthoritiesConverter.convert(source)
val authorities = source.getClaimAsStringList("authorities")?.map { SimpleGrantedAuthority(it) }
return JwtAuthenticationToken(source, scopes.orEmpty() + authorities.orEmpty())
}
}
然后向 WebSecurityConfigurerAdapter 提供实现,例如:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
class ResourceServerConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2ResourceServer {
jwt {
jwtDecoder = reactiveJwtDecoder()
jwtAuthenticationConverter = CustomJwtAuthenticationConverter()
}
}
}
}
}
我在 spring-boot 2.1.3.RELEASE 中设置使用 Webflux 的 ResourceServer 时遇到问题。用于身份验证的相同令牌在不使用 Webflux 的资源服务器上工作正常,如果我设置 .permitAll()
它也可以工作(很明显)。这是我的资源服务器配置
授权服务器使用 jwt 令牌存储。
@EnableWebFluxSecurity
public class ResourceServerConfig {
@Value("${security.oauth2.resource.jwt.key-value}")
private String publicKey;
@Autowired Environment env;
@Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
http
.authorizeExchange()
.pathMatchers("/availableInspections/**").hasRole("READ_PRIVILEGE")
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt()
.jwtDecoder(reactiveJwtDecoder())
;
return http.build();
}
@Bean
public ReactiveJwtDecoder reactiveJwtDecoder() throws Exception{
return new NimbusReactiveJwtDecoder(getPublicKeyFromString(publicKey));
}
public static RSAPublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
String publicKeyPEM = key;
publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
byte[] encoded = Base64.getMimeDecoder().decode(publicKeyPEM);
KeyFactory kf = KeyFactory.getInstance("RSA");
RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
return pubKey;
}
}
我得到的错误是
WWW-Authenticate: Bearer error="insufficient_scope", error_description="The token provided has insufficient scope [read] for this request", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1", scope="read"
我已验证令牌具有所需的范围...
我需要 change/add 什么才能正确读取我的令牌?
Spring 似乎只添加了 jwt 令牌中 scope
下的内容,忽略了 authorities
中的所有内容 - 因此它们不能用于 webflux 资源服务器,除非我们扩展 JwtAuthenticationConverter 以在令牌中添加 from authorities(或其他声明)。在安全配置中,我添加了 jwtAuthenticationConverter
http
// path config like above
.oauth2ResourceServer()
.jwt()
.jwtDecoder(reactiveJwtDecoder())
.jwtAuthenticationConverter(converter())
并已覆盖 JwtAuthenticationConverter
以包括 authorities
授予的权限:
public class MyJwtAuthenticationConverter extends JwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
private static final Collection<String> WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES =
Arrays.asList("scope", "scp", "authorities"); // added authorities
protected Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
return this.getScopes(jwt)
.stream()
.map(authority -> SCOPE_AUTHORITY_PREFIX + authority)
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
private Collection<String> getScopes(Jwt jwt) {
Collection<String> authorities = new ArrayList<>();
// add to collection instead of returning early
for ( String attributeName : WELL_KNOWN_SCOPE_ATTRIBUTE_NAMES ) {
Object scopes = jwt.getClaims().get(attributeName);
if (scopes instanceof String) {
if (StringUtils.hasText((String) scopes)) {
authorities.addAll(Arrays.asList(((String) scopes).split(" ")));
}
} else if (scopes instanceof Collection) {
authorities.addAll((Collection<String>) scopes);
}
}
return authorities;
}
}
有点晚了,但更简单的解决方案可能是(在 kotlin 中,但很容易翻译成 java):
class CustomJwtAuthenticationConverter : Converter<Jwt, AbstractAuthenticationToken> {
private val jwtGrantedAuthoritiesConverter = JwtGrantedAuthoritiesConverter()
override fun convert(source: Jwt): AbstractAuthenticationToken {
val scopes = jwtGrantedAuthoritiesConverter.convert(source)
val authorities = source.getClaimAsStringList("authorities")?.map { SimpleGrantedAuthority(it) }
return JwtAuthenticationToken(source, scopes.orEmpty() + authorities.orEmpty())
}
}
然后向 WebSecurityConfigurerAdapter 提供实现,例如:
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
class ResourceServerConfig : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http {
oauth2ResourceServer {
jwt {
jwtDecoder = reactiveJwtDecoder()
jwtAuthenticationConverter = CustomJwtAuthenticationConverter()
}
}
}
}
}