Spring Boot - 支持多个 issuer-uri

问题描述 投票:0回答:3

嗨,我一直在网上搜索但收效甚微,我希望 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'
    }
}
java spring spring-boot spring-security auth0
3个回答
9
投票

我在这个问题上苦苦挣扎了一段时间。

下面的代码适用于几个问题(用户池)。它是一个 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


0
投票

还不能评论,就这样评论吧

Igor 上面的解决方案是正确的并且有效!

只是一个小提示,您可能想将 JwtIssuerAuthenticationManagerResolver 实现为 @Bean。这样做将禁用 spring boot UserDetailsServiceAutoConfiguration 自动配置,在这种情况下我们不希望这样做(但还有其他方法可以做到这一点)。


0
投票

您可以在 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;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.