嗨,我一直在网上搜索但收效甚微,我希望 SO 的某个人可以提供帮助。
目前我有我的 Spring Boot API(版本 2.5.4)接受 Auth0 提供的 JWT。现在我在那里创建了第二个租户,我正在努力了解如何支持两个或多个发行者 uri。
这是我目前的做法:
@Configuration
@EnableWebSecurity(debug = false)
@EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${auth0.audience}")
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("http://localhost:3010"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
configuration.setAllowCredentials(true);
configuration.addAllowedHeader("Authorization");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.headers().referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.SAME_ORIGIN)
.and()
.xssProtection()
.and()
.contentSecurityPolicy("script-src 'self'").and()
.and()
.csrf()
.disable()
.formLogin()
.disable()
.httpBasic()
.disable()
.exceptionHandling()
.authenticationEntryPoint(new RestAuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/api/v1/user/profile/**").permitAll()
.antMatchers("/user/profile/**").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer().jwt();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/health", "/health/**");
}
@Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
任何人都可以阐明一些情况,使用 Node 我可以将 issuer-uri 设置为一个字符串数组,它开箱即用。希望春天有类似的东西吗?
编辑: 如果需要,这里是我的版本构建文件:
plugins {
id "org.springframework.boot" version "2.5.4"
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
id "com.github.davidmc24.gradle.plugin.avro" version "1.2.0"
id "idea"
id 'com.google.cloud.tools.jib' version '3.0.0'
}
apply plugin: 'idea'
group 'org.example'
version '1.0'
java {
sourceCompatibility = JavaVersion.VERSION_14
targetCompatibility = JavaVersion.VERSION_14
}
jib.from.image = 'openjdk:15-jdk-buster'
jib.to.image = 'gcr.io/thefullstack/tfs-project-service'
ext {
avroVersion = "1.10.1"
}
repositories {
mavenCentral()
jcenter()
maven {
url "https://packages.confluent.io/maven/"
}
}
avro {
createSetters = true
fieldVisibility = "PRIVATE"
}
dependencies {
implementation('org.springframework.boot:spring-boot-starter-data-elasticsearch')
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-mongodb'
implementation group: 'org.springframework.data', name: 'spring-data-elasticsearch'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-security'
implementation group: 'org.springframework.security', name: 'spring-security-oauth2-client'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-oauth2-resource-server'
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-cache'
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation group: 'org.springframework.integration', name: 'spring-integration-core', version: '5.5.3'
// implementation group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-logging'
implementation group: 'org.springframework.cloud', name: 'spring-cloud-gcp-starter-logging', version: '1.2.8.RELEASE'
implementation("org.springframework.cloud:spring-cloud-gcp-starter-pubsub:1.2.5.RELEASE")
implementation("org.springframework.integration:spring-integration-core")
implementation group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.860'
implementation group: 'com.mashape.unirest', name: 'unirest-java', version: '1.4.9'
implementation group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
implementation group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: '2.12.3'
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.3'
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
implementation group: 'org.openapitools', name: 'jackson-databind-nullable', version: '0.2.1'
implementation group: 'commons-io', name: 'commons-io', version: '2.6'
implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.4'
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.11'
implementation group: 'com.auth0', name: 'java-jwt', version: '3.12.0'
implementation "org.apache.avro:avro:1.10.1"
implementation "org.apache.avro:avro:${avroVersion}"
implementation 'org.projectlombok:lombok:1.18.20'
annotationProcessor 'org.projectlombok:lombok:1.18.20'
implementation 'com.amazonaws:aws-java-sdk-s3'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation group: 'junit', name: 'junit', version: '4.12'
testImplementation 'org.projectlombok:lombok:1.18.20'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.20'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
我在这个问题上苦苦挣扎了一段时间。
下面的代码适用于几个问题(用户池)。它是一个 spring.security.oauth2.resourceserver.jwt.issuer-uri property
的替代品请看SpringSecurity配置的代码:
@Component
@ConfigurationProperties(prefix = "config")
class JWTIssuersProps {
private List<String> issuers;
// getter and setter
public List<String> getIssuers() {
return issuers;
}
public void setIssuers(List<String> issuers) {
this.issuers = issuers;
}
}
@Configuration
public class JWTCustomSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private JWTIssuersProps props;
Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(authenticationManagers::get);
@Override
protected void configure(HttpSecurity http) throws Exception {
List<String> propsIssuers = props.getIssuers();
propsIssuers.forEach(issuer -> addManager(authenticationManagers, issuer));
http.
// CORS configuration
cors().configurationSource(request -> {
var cors = new CorsConfiguration();
cors.setAllowedOrigins(List.of("http://localhost:3000"));
cors.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
cors.setAllowedHeaders(List.of("*"));
return cors;
})
.and()
.authorizeRequests()
.antMatchers("/actuator/**").permitAll()
.and()
.oauth2ResourceServer(oauth2ResourceServer -> {
oauth2ResourceServer.authenticationManagerResolver(this.authenticationManagerResolver);
});
}
public void addManager(Map<String, AuthenticationManager> authenticationManagers, String issuer) {
JwtDecoder jwtDecoder = JwtDecoders.fromOidcIssuerLocation(issuer);
JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider(jwtDecoder);
authenticationProvider.setJwtAuthenticationConverter(new MyJwtAuthenticationConverter());
authenticationManagers.put(issuer, authenticationProvider::authenticate);
}
static class MyJwtAuthenticationConverter extends JwtAuthenticationConverter {
@Override
protected Collection<GrantedAuthority> extractAuthorities(final Jwt jwt) {
List<String> groups = jwt.getClaim("cognito:groups");
System.out.println("User groups list.size =" + groups.size());
System.out.println("User groups:");
groups.forEach(System.out::println);
List<GrantedAuthority> authorities = new ArrayList<>();
for (String role : groups) {
authorities.add(new SimpleGrantedAuthority(role));
}
return authorities;
}
}
}
它需要 application.yaml 文件:
config:
issuers:
- https://cognito-idp.ca-central-1.amazonaws.com/<pool-ID-1>
- https://cognito-idp.ca-central-1.amazonaws.com/<pool-ID-2>
我那里也有 CORS 配置。并阅读 Amazon Cognito 群组。
您可以在这里找到完整的项目源代码:https://github.com/grenader/spring-security-cognito-oauth2-jwt/tree/java
还不能评论,就这样评论吧
Igor 上面的解决方案是正确的并且有效!
只是一个小提示,您可能想将 JwtIssuerAuthenticationManagerResolver 实现为 @Bean。这样做将禁用 spring boot UserDetailsServiceAutoConfiguration 自动配置,在这种情况下我们不希望这样做(但还有其他方法可以做到这一点)。
您可以在 Spring boot 2.7.10 中使用此实现来支持多个发行者。此示例中的 auth-servers urls 等于 bellows urls.
http://localhost:9292/auth/realms/sso
http://www.auth-server.org:9292/auth/realms/sso
http://127.0.0.1:9292/auth/realms/sso
这也是我的 application.properties 配置
spring.security.oauth2.client.registration.keycloak.client-id=sample-client
spring.security.oauth2.client.provider.keycloak.authorization-uri=http://www.auth-server.org:9292/auth/realms/sso/protocol/openid-connect/auth
spring.security.oauth2.client.provider.keycloak.token-uri=http://www.auth-server.org:9292/auth/realms/sso/protocol/openid-connect/token
spring.security.oauth2.client.registration.keycloak.redirect-uri=http://localhost:8080/login/oauth2/code/sample-client
spring.security.oauth2.client.registration.keycloak.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.keycloak.scope=openid
这个类只是一个简单的配置。
@Configuration
public class ClientSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.authorizeHttpRequests()
.anyRequest()
.authenticated();
http.oauth2Login();
http.oauth2ResourceServer(httpSecurity -> httpSecurity.jwt(
jwtConfigurer -> jwtConfigurer.decoder(CustomJwtDecoder::getJwt)
));
// @formatter:on
}
}
并支持多个发行者,编写两个类 CustomJwtDecoder 和 CustomJwtDecoderFactory,它们按顺序实现两个接口 JwtDecoder 和 JwtDecoderFactory。
这两个类的实现:
@Component
public class CustomJwtDecoder implements JwtDecoder {
@Override
public Jwt decode(String token) throws JwtException {
return getJwt(token);
}
public static Jwt getJwt(String token) {
Jwt result = null;
JwtDecoder jdLocal = JwtDecoders.fromIssuerLocation("http://localhost:9292/auth/realms/sso");
JwtDecoder jdDomain = JwtDecoders.fromIssuerLocation("http://www.auth-server.org:9292/auth/realms/sso");
JwtDecoder jdIp = JwtDecoders.fromIssuerLocation("http://127.0.0.1:9292/auth/realms/sso");
try { result = jdLocal.decode(token); } catch (Exception ex) { ex.printStackTrace(); }
try { result = jdDomain.decode(token); } catch (Exception ex) { ex.printStackTrace(); }
try { result = jdIp.decode(token); } catch (Exception ex) { ex.printStackTrace(); }
return result;
}
}
和这个班级
@Component
public class CustomJwtDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
@Override
public JwtDecoder createDecoder(ClientRegistration context) {
return CustomJwtDecoder::getJwt;
}
}