最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

java - Spring Core with Spring Security Oauth2ResourceServer second security filter chain is not active - Stack Overflow

programmeradmin2浏览0评论

Hello all support is appreciated, I set up Spring Core (5.3.39) with Spring Secuirty (5.8.16) to use Oauth2 Resource Server and Authorization Server is Keycloak. I have situation ordering security filter chains and using SecurityMatchers did not activate BearerTokenAuthenticationFilter of second chain. How is it done right?

SpringSecurityConfig.java:

    package edu.remad.tutoring2.security.config;

import java.util.HashMap;
import java.util.Map;

import .springframework.beans.factory.annotation.Value;
import .springframework.context.annotation.Bean;
import .springframework.context.annotation.Configuration;
import .springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import .springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import .springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import .springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import .springframework.security.crypto.password.DelegatingPasswordEncoder;
import .springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity(debug = true)
@EnableMethodSecurity
public class SpringSecurityConfig {

    @Value("${spring.websecurity.debug:true}")
    boolean webSecurityDebug;

    @Bean
    WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.debug(webSecurityDebug);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        String idForEncode = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(idForEncode, new BCryptPasswordEncoder());

        return new DelegatingPasswordEncoder(idForEncode, encoders);
    }
}

SecurityFilterChainsConfig.java:

    package edu.remad.tutoring2.security.config;

import .springframework.beans.factory.annotation.Autowired;
import .springframework.context.annotation.Bean;
import .springframework.context.annotation.Configuration;
import .springframework.core.annotation.Order;
import .springframework.security.config.annotation.web.builders.HttpSecurity;
import .springframework.security.config.http.SessionCreationPolicy;
import .springframework.security.web.SecurityFilterChain;
import .springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;
import .springframework.security.web.authentication.www.BasicAuthenticationFilter;
import .springframework.security.web.csrf.CookieCsrfTokenRepository;
import .springframework.security.web.header.HeaderWriterFilter;
import .springframework.security.web.header.writers.ClearSiteDataHeaderWriter;
import .springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive;
import .springframework.security.web.util.matcher.AntPathRequestMatcher;

import edu.remad.tutoring2.jwt.Tutoring2CustomJwtAuthenticationConverter;
import edu.remad.tutoring2.security.ContentSecurityPolicySettings;
import edu.remad.tutoring2.security.filters.DebugLoggingFilter;
import edu.remad.tutoring2.security.filters.HttpHeadersFilter;
import edu.remad.tutoring2.security.filters.TenantFilter;

@Configuration
public class SecurityFilterChainsConfig {

    private static final ClearSiteDataHeaderWriter.Directive[] COOKIES = Directive.values();

    @Autowired
    private ContentSecurityPolicySettings contentSecurityPolicies;

    @Autowired
    private Tutoring2CustomJwtAuthenticationConverter jwtAuthConverter;

    /**
     * Does form login filter chain and has also http security.
     * 
     * @param http similar to spring security xml config for filtering request
     * @return created security filter chain, {@link SecurityFilterChain}
     * @throws Exception
     */
    @Bean
    @Order(1)
    SecurityFilterChain formloginSecurityFilterChain(HttpSecurity http) throws Exception {
        http.cors().and().headers(headers -> headers.xssProtection().and()
                .contentSecurityPolicy(contentSecurityPolicies.getContentSecurityPolicies()));

        http.addFilterAfter(new TenantFilter(), BasicAuthenticationFilter.class)
                .addFilterAfter(new HttpHeadersFilter(), HeaderWriterFilter.class)
                .addFilterAfter(new DebugLoggingFilter(), HttpHeadersFilter.class)
                .securityContext((securityContext) -> securityContext.requireExplicitSave(true))
                .sessionManagement(
                        session -> session.maximumSessions(1).maxSessionsPreventsLogin(true).expiredUrl("/login"))
                .authorizeRequests(requests -> requests.antMatchers("/", "/helloWorld", "/logoutSuccess", "/signup", "/api/v1/csrf")
                        .permitAll().antMatchers("/hello", "/bye", "/login", "/logout", "/templates/**").authenticated())
                .formLogin(login -> login.loginPage("/myCustomLogin").loginProcessingUrl("/process-login")
                        .defaultSuccessUrl("/hello", true)).csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
                .logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/logoutSuccess")
                        .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(COOKIES))));

        return http.build();
    }

    @Bean
    @Order(2)
    SecurityFilterChain oauth2rescourceserverSecurityFilterChain(HttpSecurity http) throws Exception {
        return http.securityMatcher(AntPathRequestMatcher.antMatcher("/v2/**"))
                .authorizeHttpRequests(requests -> requests.anyRequest().authenticated()).csrf(csrf -> csrf.disable())
                .oauth2ResourceServer(server -> server.jwt().jwtAuthenticationConverter(jwtAuthConverter))
                .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).build();
    }
}

Oauth2ResourcServerConfig.java:

    package edu.remad.tutoring2.security.config;

import java.MalformedURLException;
import java.URL;

import .springframework.context.annotation.Bean;
import .springframework.context.annotation.Configuration;
import .springframework.security.oauth2.jwt.JwtDecoder;
import .springframework.security.oauth2.jwt.NimbusJwtDecoder;

import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.proc.JWSAlgorithmFamilyJWSKeySelector;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;

@Configuration
public class Oauth2ResourcServerConfig {

    private String keySetUri = "http://192.168.120.59:8080/realms/ConnectTrial/protocol/openid-connect/certs";

    @Bean
    JwtDecoder jwtDecoder() throws KeySourceException, MalformedURLException {
        JWSKeySelector<SecurityContext> jwsKeySelector =
                JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(new URL(keySetUri));

        DefaultJWTProcessor<SecurityContext> jwtProcessor =
                new DefaultJWTProcessor<>();
        jwtProcessor.setJWSKeySelector(jwsKeySelector);

        return new NimbusJwtDecoder(jwtProcessor);
    }

}

Tutoring2CustomJwtAuthenticationConverter.java:

    package edu.remad.tutoring2.jwt;

import static edu.remad.tutoring2.appconstants.JwtAppConstants.JWT_CLAIM_RESSOURCE_ACCESS;
import static edu.remad.tutoring2.appconstants.JwtAppConstants.JWT_CONVERTER_PRINCIPAL_ATTRIBUTE;
import static edu.remad.tutoring2.appconstants.JwtAppConstants.JWT_CONVERTER_RESOURCE_ID;
import static edu.remad.tutoring2.appconstants.JwtAppConstants.JWT_ROLES_KEY;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import .springframework.core.convert.converter.Converter;
import .springframework.lang.NonNull;
import .springframework.security.authentication.AbstractAuthenticationToken;
import .springframework.security.core.GrantedAuthority;
import .springframework.security.core.authority.SimpleGrantedAuthority;
import .springframework.security.oauth2.jwt.Jwt;
import .springframework.security.oauth2.jwt.JwtClaimNames;
import .springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import .springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import .springframework.stereotype.Component;

/**
 * Converts roles from Keycloak to Spring Security roles. It reads JWT and fetches all claims and roles as roles. 
 */
@Component
public class Tutoring2CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter;

    /**
     * Default Constructor
     */
    public Tutoring2CustomJwtAuthenticationConverter() {
        jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    }

    @Override
    public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
        Collection<GrantedAuthority> authorities = Stream
                .concat(jwtGrantedAuthoritiesConverter.convert(jwt).stream(), extractJwtResourceRoles(jwt).stream())
                .collect(Collectors.toSet());

        return new JwtAuthenticationToken(jwt, authorities, getPrincipalClaimName(jwt));
    }

    private Collection<? extends GrantedAuthority> extractJwtResourceRoles(Jwt jwt) {
        if (jwt.getClaimAsMap(JWT_CLAIM_RESSOURCE_ACCESS) == null) {
            return Set.of();
        }

        Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
        if (resourceAccess.get(JWT_CONVERTER_RESOURCE_ID) == null) {
            return Set.of();
        }

        if (resourceAccess.get(JWT_CONVERTER_RESOURCE_ID) == null) {
            return Set.of();
        }

        Map<String, Object> resource = (Map<String, Object>) resourceAccess.get(JWT_CONVERTER_RESOURCE_ID);
        Collection<String> resourceRoles = (Collection<String>) resource.get(JWT_ROLES_KEY);

        return resourceRoles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toSet());
    }

    private String getPrincipalClaimName(Jwt jwt) {
        String claimName = JwtClaimNames.SUB;
        
        if (JWT_CONVERTER_PRINCIPAL_ATTRIBUTE != null) {
            claimName = JWT_CONVERTER_PRINCIPAL_ATTRIBUTE;
        }

        return jwt.getClaim(claimName);
    }

}

pom.xml:

    <project xmlns=".0.0"
    xmlns:xsi=";
    xsi:schemaLocation=".0.0 .0.0.xsd">

    <!-- ###################### -->
    <!-- start of project setup -->
    <!-- ###################### -->
    <modelVersion>4.0.0</modelVersion>
    <groupId>edu.remad</groupId>
    <artifactId>tutoring2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>Tutoring 2 Maven Webapp</name>
    <url>;/url>
    <!-- #################### -->
    <!-- end of project setup -->
    <!-- #################### -->

    <!-- ################### -->
    <!-- start of properties -->
    <!-- ################### -->
    <properties>
        <mavenpiler.target>11</mavenpiler.target>
        <mavenpiler.source>11</mavenpiler.source>
        <encoding>UTF-8</encoding>
        <spring.version>5.3.39</spring.version>
        <spring.security.version>5.8.16</spring.security.version>
        <spring.boot.version>2.7.14</spring.boot.version>
        <junit5.version>5.10.0</junit5.version>
        <lombok.version>1.18.30</lombok.version>
        <spring.oauth2.resourceserver.version>2.7.14</spring.oauth2.resourceserver.version>
        <version>3.3.2</version>
    </properties>
    <!-- ################# -->
    <!-- end of properties -->
    <!-- ################# -->

    <!-- ##################### -->
    <!-- start of dependencies -->
    <!-- ##################### -->
    <dependencies>
        <!-- .springframework/spring-beans -->
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <!-- spring security needed deoendencies -->
        <dependency>
            <groupId>.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
            <version>5.8.16</version>
        </dependency>

        <!-- Spring framework -->
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.6.9.Final</version>
        </dependency>
        <!-- Hibernate Validator -->
        <dependency>
            <groupId>.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.4.3.Final</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.1.0</version>
        </dependency>
        <dependency>
            <groupId>.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- CGLib for @Configuration -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>2.2.2</version>
            <scope>runtime</scope>
        </dependency>
        <!-- Servlet Spec -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <!--<dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
        </dependency>-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <!-- Maven Plugins section -->
        <dependency>
            <groupId>.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
        </dependency>
        <dependency>
            <groupId>.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.1.2</version>
        </dependency>
        <!-- JAXB, for version 3 use Jakarta -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.8</version>
        </dependency>
        <dependency>
            <groupId>.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>5.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.16.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.16.0</version>
        </dependency>

        <!-- Testing -->
        <dependency>
            <groupId>.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit5.version}</version>
        </dependency>
        <dependency>
            <groupId>.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit5.version}</version>
        </dependency>
        <dependency>
            <groupId>.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit5.version}</version>
        </dependency>
        <dependency>
            <groupId>.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit5.version}</version>
        </dependency>
        <dependency>
            <groupId>.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <version>1.10.0</version>
        </dependency>
        <dependency>
            <groupId>.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>5.6.0</version>
        </dependency>
        <dependency>
            <groupId>.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>5.6.0</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- resource-server -->
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
            <version>${spring.oauth2.resourceserver.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>${spring.oauth2.resourceserver.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.oauth2.resourceserver.version}</version>
        </dependency>

        <!-- start of own implementations -->
        <dependency>
            <groupId>edu.remad</groupId>
            <artifactId>ical4j-builder</artifactId>
            <version>1.0.6-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>edu.remad</groupId>
            <artifactId>pdf-toolboxing</artifactId>
            <version>0.30.1-SNAPSHOT</version>
        </dependency>
        <!-- end of own implementation -->
    </dependencies>
    <!-- ################### -->
    <!-- end of dependencies -->
    <!-- ################### -->

    <!-- ################# -->
    <!-- start of profiles -->
    <!-- ################# -->
    <profiles>
        <profile>
            <id>development</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <maven.test.skip>true</maven.test.skip>
            </properties>
        </profile>
        <profile>
            <id>withoutTests</id>
            <properties>
                <maven.test.skip>true</maven.test.skip>
            </properties>
        </profile>
        <profile>
            <id>withIntegrationTests</id>
            <properties>
            </properties>
        </profile>
    </profiles>

    <!-- ############### -->
    <!-- end of profiles -->
    <!-- ############### -->

    <!-- ###################### -->
    <!-- start of build section -->
    <!-- ###################### -->
    <build>
        <finalName>tutoring2</finalName>
        <plugins>
            <plugin>
                <groupId>.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${mavenpiler.target}</source>
                    <target>${mavenpiler.source}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.4.0</version>
            </plugin>
            <plugin>
                <groupId>.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.1</version>
                <dependencies>
                    <dependency>
                        <groupId>.junit.jupiter</groupId>
                        <artifactId>junit-jupiter-engine</artifactId>
                        <version>${junit5.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
    <!-- #################### -->
    <!-- end of build section -->
    <!-- #################### -->
</project>

Hello all support is appreciated, I set up Spring Core (5.3.39) with Spring Secuirty (5.8.16) to use Oauth2 Resource Server and Authorization Server is Keycloak. I have situation ordering security filter chains and using SecurityMatchers did not activate BearerTokenAuthenticationFilter of second chain. How is it done right?

SpringSecurityConfig.java:

    package edu.remad.tutoring2.security.config;

import java.util.HashMap;
import java.util.Map;

import .springframework.beans.factory.annotation.Value;
import .springframework.context.annotation.Bean;
import .springframework.context.annotation.Configuration;
import .springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import .springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import .springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import .springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import .springframework.security.crypto.password.DelegatingPasswordEncoder;
import .springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity(debug = true)
@EnableMethodSecurity
public class SpringSecurityConfig {

    @Value("${spring.websecurity.debug:true}")
    boolean webSecurityDebug;

    @Bean
    WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.debug(webSecurityDebug);
    }

    @Bean
    PasswordEncoder passwordEncoder() {
        String idForEncode = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(idForEncode, new BCryptPasswordEncoder());

        return new DelegatingPasswordEncoder(idForEncode, encoders);
    }
}

SecurityFilterChainsConfig.java:

    package edu.remad.tutoring2.security.config;

import .springframework.beans.factory.annotation.Autowired;
import .springframework.context.annotation.Bean;
import .springframework.context.annotation.Configuration;
import .springframework.core.annotation.Order;
import .springframework.security.config.annotation.web.builders.HttpSecurity;
import .springframework.security.config.http.SessionCreationPolicy;
import .springframework.security.web.SecurityFilterChain;
import .springframework.security.web.authentication.logout.HeaderWriterLogoutHandler;
import .springframework.security.web.authentication.www.BasicAuthenticationFilter;
import .springframework.security.web.csrf.CookieCsrfTokenRepository;
import .springframework.security.web.header.HeaderWriterFilter;
import .springframework.security.web.header.writers.ClearSiteDataHeaderWriter;
import .springframework.security.web.header.writers.ClearSiteDataHeaderWriter.Directive;
import .springframework.security.web.util.matcher.AntPathRequestMatcher;

import edu.remad.tutoring2.jwt.Tutoring2CustomJwtAuthenticationConverter;
import edu.remad.tutoring2.security.ContentSecurityPolicySettings;
import edu.remad.tutoring2.security.filters.DebugLoggingFilter;
import edu.remad.tutoring2.security.filters.HttpHeadersFilter;
import edu.remad.tutoring2.security.filters.TenantFilter;

@Configuration
public class SecurityFilterChainsConfig {

    private static final ClearSiteDataHeaderWriter.Directive[] COOKIES = Directive.values();

    @Autowired
    private ContentSecurityPolicySettings contentSecurityPolicies;

    @Autowired
    private Tutoring2CustomJwtAuthenticationConverter jwtAuthConverter;

    /**
     * Does form login filter chain and has also http security.
     * 
     * @param http similar to spring security xml config for filtering request
     * @return created security filter chain, {@link SecurityFilterChain}
     * @throws Exception
     */
    @Bean
    @Order(1)
    SecurityFilterChain formloginSecurityFilterChain(HttpSecurity http) throws Exception {
        http.cors().and().headers(headers -> headers.xssProtection().and()
                .contentSecurityPolicy(contentSecurityPolicies.getContentSecurityPolicies()));

        http.addFilterAfter(new TenantFilter(), BasicAuthenticationFilter.class)
                .addFilterAfter(new HttpHeadersFilter(), HeaderWriterFilter.class)
                .addFilterAfter(new DebugLoggingFilter(), HttpHeadersFilter.class)
                .securityContext((securityContext) -> securityContext.requireExplicitSave(true))
                .sessionManagement(
                        session -> session.maximumSessions(1).maxSessionsPreventsLogin(true).expiredUrl("/login"))
                .authorizeRequests(requests -> requests.antMatchers("/", "/helloWorld", "/logoutSuccess", "/signup", "/api/v1/csrf")
                        .permitAll().antMatchers("/hello", "/bye", "/login", "/logout", "/templates/**").authenticated())
                .formLogin(login -> login.loginPage("/myCustomLogin").loginProcessingUrl("/process-login")
                        .defaultSuccessUrl("/hello", true)).csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
                .logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/logoutSuccess")
                        .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(COOKIES))));

        return http.build();
    }

    @Bean
    @Order(2)
    SecurityFilterChain oauth2rescourceserverSecurityFilterChain(HttpSecurity http) throws Exception {
        return http.securityMatcher(AntPathRequestMatcher.antMatcher("/v2/**"))
                .authorizeHttpRequests(requests -> requests.anyRequest().authenticated()).csrf(csrf -> csrf.disable())
                .oauth2ResourceServer(server -> server.jwt().jwtAuthenticationConverter(jwtAuthConverter))
                .sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).build();
    }
}

Oauth2ResourcServerConfig.java:

    package edu.remad.tutoring2.security.config;

import java.MalformedURLException;
import java.URL;

import .springframework.context.annotation.Bean;
import .springframework.context.annotation.Configuration;
import .springframework.security.oauth2.jwt.JwtDecoder;
import .springframework.security.oauth2.jwt.NimbusJwtDecoder;

import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.proc.JWSAlgorithmFamilyJWSKeySelector;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;

@Configuration
public class Oauth2ResourcServerConfig {

    private String keySetUri = "http://192.168.120.59:8080/realms/ConnectTrial/protocol/openid-connect/certs";

    @Bean
    JwtDecoder jwtDecoder() throws KeySourceException, MalformedURLException {
        JWSKeySelector<SecurityContext> jwsKeySelector =
                JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(new URL(keySetUri));

        DefaultJWTProcessor<SecurityContext> jwtProcessor =
                new DefaultJWTProcessor<>();
        jwtProcessor.setJWSKeySelector(jwsKeySelector);

        return new NimbusJwtDecoder(jwtProcessor);
    }

}

Tutoring2CustomJwtAuthenticationConverter.java:

    package edu.remad.tutoring2.jwt;

import static edu.remad.tutoring2.appconstants.JwtAppConstants.JWT_CLAIM_RESSOURCE_ACCESS;
import static edu.remad.tutoring2.appconstants.JwtAppConstants.JWT_CONVERTER_PRINCIPAL_ATTRIBUTE;
import static edu.remad.tutoring2.appconstants.JwtAppConstants.JWT_CONVERTER_RESOURCE_ID;
import static edu.remad.tutoring2.appconstants.JwtAppConstants.JWT_ROLES_KEY;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import .springframework.core.convert.converter.Converter;
import .springframework.lang.NonNull;
import .springframework.security.authentication.AbstractAuthenticationToken;
import .springframework.security.core.GrantedAuthority;
import .springframework.security.core.authority.SimpleGrantedAuthority;
import .springframework.security.oauth2.jwt.Jwt;
import .springframework.security.oauth2.jwt.JwtClaimNames;
import .springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import .springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import .springframework.stereotype.Component;

/**
 * Converts roles from Keycloak to Spring Security roles. It reads JWT and fetches all claims and roles as roles. 
 */
@Component
public class Tutoring2CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter;

    /**
     * Default Constructor
     */
    public Tutoring2CustomJwtAuthenticationConverter() {
        jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
    }

    @Override
    public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
        Collection<GrantedAuthority> authorities = Stream
                .concat(jwtGrantedAuthoritiesConverter.convert(jwt).stream(), extractJwtResourceRoles(jwt).stream())
                .collect(Collectors.toSet());

        return new JwtAuthenticationToken(jwt, authorities, getPrincipalClaimName(jwt));
    }

    private Collection<? extends GrantedAuthority> extractJwtResourceRoles(Jwt jwt) {
        if (jwt.getClaimAsMap(JWT_CLAIM_RESSOURCE_ACCESS) == null) {
            return Set.of();
        }

        Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
        if (resourceAccess.get(JWT_CONVERTER_RESOURCE_ID) == null) {
            return Set.of();
        }

        if (resourceAccess.get(JWT_CONVERTER_RESOURCE_ID) == null) {
            return Set.of();
        }

        Map<String, Object> resource = (Map<String, Object>) resourceAccess.get(JWT_CONVERTER_RESOURCE_ID);
        Collection<String> resourceRoles = (Collection<String>) resource.get(JWT_ROLES_KEY);

        return resourceRoles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toSet());
    }

    private String getPrincipalClaimName(Jwt jwt) {
        String claimName = JwtClaimNames.SUB;
        
        if (JWT_CONVERTER_PRINCIPAL_ATTRIBUTE != null) {
            claimName = JWT_CONVERTER_PRINCIPAL_ATTRIBUTE;
        }

        return jwt.getClaim(claimName);
    }

}

pom.xml:

    <project xmlns="http://maven.apache./POM/4.0.0"
    xmlns:xsi="http://www.w3./2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache./POM/4.0.0 https://maven.apache./xsd/maven-4.0.0.xsd">

    <!-- ###################### -->
    <!-- start of project setup -->
    <!-- ###################### -->
    <modelVersion>4.0.0</modelVersion>
    <groupId>edu.remad</groupId>
    <artifactId>tutoring2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>Tutoring 2 Maven Webapp</name>
    <url>http://maven.apache.</url>
    <!-- #################### -->
    <!-- end of project setup -->
    <!-- #################### -->

    <!-- ################### -->
    <!-- start of properties -->
    <!-- ################### -->
    <properties>
        <mavenpiler.target>11</mavenpiler.target>
        <mavenpiler.source>11</mavenpiler.source>
        <encoding>UTF-8</encoding>
        <spring.version>5.3.39</spring.version>
        <spring.security.version>5.8.16</spring.security.version>
        <spring.boot.version>2.7.14</spring.boot.version>
        <junit5.version>5.10.0</junit5.version>
        <lombok.version>1.18.30</lombok.version>
        <spring.oauth2.resourceserver.version>2.7.14</spring.oauth2.resourceserver.version>
        <version>3.3.2</version>
    </properties>
    <!-- ################# -->
    <!-- end of properties -->
    <!-- ################# -->

    <!-- ##################### -->
    <!-- start of dependencies -->
    <!-- ##################### -->
    <dependencies>
        <!-- https://mvnrepository/artifact/.springframework/spring-beans -->
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <!-- spring security needed deoendencies -->
        <dependency>
            <groupId>.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.security.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
            <version>5.8.16</version>
        </dependency>

        <!-- Spring framework -->
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jdbc</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.6.9.Final</version>
        </dependency>
        <!-- Hibernate Validator -->
        <dependency>
            <groupId>.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.4.3.Final</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.1.0</version>
        </dependency>
        <dependency>
            <groupId>.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
        <!-- CGLib for @Configuration -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>2.2.2</version>
            <scope>runtime</scope>
        </dependency>
        <!-- Servlet Spec -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <!--<dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <version>5.0.0</version>
        </dependency>-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.2</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <!-- Maven Plugins section -->
        <dependency>
            <groupId>.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
        </dependency>
        <dependency>
            <groupId>.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>3.1.2</version>
        </dependency>
        <!-- JAXB, for version 3 use Jakarta -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
            <version>2.3.8</version>
        </dependency>
        <dependency>
            <groupId>.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>5.3.2</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.16.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
            <version>2.16.0</version>
        </dependency>

        <!-- Testing -->
        <dependency>
            <groupId>.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit5.version}</version>
        </dependency>
        <dependency>
            <groupId>.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit5.version}</version>
        </dependency>
        <dependency>
            <groupId>.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit5.version}</version>
        </dependency>
        <dependency>
            <groupId>.junit.jupiter</groupId>
            <artifactId>junit-jupiter-params</artifactId>
            <version>${junit5.version}</version>
        </dependency>
        <dependency>
            <groupId>.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <version>1.10.0</version>
        </dependency>
        <dependency>
            <groupId>.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>5.6.0</version>
        </dependency>
        <dependency>
            <groupId>.mockito</groupId>
            <artifactId>mockito-junit-jupiter</artifactId>
            <version>5.6.0</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <!-- resource-server -->
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
            <version>${spring.oauth2.resourceserver.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>${spring.oauth2.resourceserver.version}</version>
        </dependency>
        <dependency>
            <groupId>.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring.oauth2.resourceserver.version}</version>
        </dependency>

        <!-- start of own implementations -->
        <dependency>
            <groupId>edu.remad</groupId>
            <artifactId>ical4j-builder</artifactId>
            <version>1.0.6-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>edu.remad</groupId>
            <artifactId>pdf-toolboxing</artifactId>
            <version>0.30.1-SNAPSHOT</version>
        </dependency>
        <!-- end of own implementation -->
    </dependencies>
    <!-- ################### -->
    <!-- end of dependencies -->
    <!-- ################### -->

    <!-- ################# -->
    <!-- start of profiles -->
    <!-- ################# -->
    <profiles>
        <profile>
            <id>development</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <maven.test.skip>true</maven.test.skip>
            </properties>
        </profile>
        <profile>
            <id>withoutTests</id>
            <properties>
                <maven.test.skip>true</maven.test.skip>
            </properties>
        </profile>
        <profile>
            <id>withIntegrationTests</id>
            <properties>
            </properties>
        </profile>
    </profiles>

    <!-- ############### -->
    <!-- end of profiles -->
    <!-- ############### -->

    <!-- ###################### -->
    <!-- start of build section -->
    <!-- ###################### -->
    <build>
        <finalName>tutoring2</finalName>
        <plugins>
            <plugin>
                <groupId>.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>${mavenpiler.target}</source>
                    <target>${mavenpiler.source}</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>${lombok.version}</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
            <plugin>
                <groupId>.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.4.0</version>
            </plugin>
            <plugin>
                <groupId>.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.1</version>
                <dependencies>
                    <dependency>
                        <groupId>.junit.jupiter</groupId>
                        <artifactId>junit-jupiter-engine</artifactId>
                        <version>${junit5.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
    <!-- #################### -->
    <!-- end of build section -->
    <!-- #################### -->
</project>
Share Improve this question edited Mar 18 at 21:24 dur 17k26 gold badges90 silver badges144 bronze badges asked Mar 16 at 19:06 ReMadWebReMadWeb 681 silver badge6 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

The filter chain with @Order(1) must have a securityMatcher for a filter chain with @Order(2) to have a chance to be tried. So, switch the @Order definitions or move securityMatcher.

The security filter chains are tried in @Order (1 is tried before 2). The first matching a request is used and others are discarded. If there is no securityMatcher, a filter chain matches all requests, which is a good choice for the last filter chain in order (acts as default), but not for the 1st (as in your conf).

As a side note, you could find this starter of mine useful. It would save you the burden of maintaining an authorities converter and an oauth2ResourceServer filter chain. But be aware that the resource server filter chain auto-configured by my starter has no securityMatcher and lowest precedence. So in addition to removing your oauth2rescourceserverSecurityFilterChain, Oauth2ResourcServerConfig, and Tutoring2CustomJwtAuthenticationConverter, you'll have to define a securityMatcher for your formloginSecurityFilterChain (with something similar to what you have to authorizeRequests)

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论