admin管理员组

文章数量:1386692

I want to add a second authentication provider to my spring project but i'm using the Spring Boot Active Directory Starter dependency where i only configure the oauth2 resource server with a keyset uri and an authentication converter.

How would i approach this problem? Can i just add a second authentication provider or is some configuration needed for the aad auth before? If some configuration is needed could you explain this then to me?

Here is my current Spring Security Config

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SpringSecurityConfig implements WebMvcConfigurer {

    @Value("${security.oauth2.resource.jwt.key-uri}")
    private String keySetUri;

    @Value("${security.oauth2.resource.id}")
    private String resourceId;

    @Value("${security.oauth2.issuer}")
    private String issuer;

    @Value("${security.oauth2.scope.access-as-user}")
    private String accessAsUserScope;

    private final FigaroAuthenticationConverter authenticationConverter;

    @Autowired
    public SpringSecurityConfig(FigaroAuthenticationConverter authenticationConverter) {
        this.authenticationConverter = authenticationConverter;
    }

    /**
     * Configures the security filter chain for the application.
     *
     * @param http the HttpSecurity to modify
     * @return the configured SecurityFilterChain
     * @throws Exception if an error occurs while configuring the filter chain
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.cors(corsCustomizer ->
                corsCustomizer.configurationSource(request -> {

                    CorsConfiguration config = new CorsConfiguration();

                    config.setAllowedOrigins(List.of("http://localhost:4200"));
                    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
                    config.setAllowCredentials(true);
                    config.setExposedHeaders(List.of("Authorization"));
                    config.setAllowedHeaders(List.of(
                            "Access-Control-Allow-Headers",
                            "Access-Control-Allow-Origin",
                            "Access-Control-Request-Method",
                            "Access-Control-Request-Headers",
                            "Origin",
                            "Cache-Control",
                            "Content-Type",
                            "Authorization"
                    ));

                    config.setMaxAge(3600L);

                    return config;
                }));

        http.sessionManagement(Customizer.withDefaults())
                .sessionManagement(sessionManagementConfigurer ->
                        sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));


        http.oauth2ResourceServer(oauth2 ->
                oauth2.jwt(jwtConfigurer -> {

                    jwtConfigurer.jwkSetUri(this.keySetUri);
                    jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter());

                }));

        http.authorizeHttpRequests(auth -> {
            auth.requestMatchers(HttpMethod.OPTIONS)
                    .permitAll();
            auth.requestMatchers("/actuator/**", "/api-docs").permitAll();
            auth.anyRequest().authenticated();
        });

        return http.build();
    }

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(this.authenticationConverter);
        return jwtAuthenticationConverter;
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(this.keySetUri).build();
    }
}

I havn't tried anything. But i don't really now where to start. I researched before this post a bit and found out that i could achive this with something like that

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(oAuth2ResourceServerConfigurer);
        auth.authenticationProvider(customAuthenticationProvider);
}

----------------------- EDIT -----------------------

I've tried solving this issue with a request filter that is executed before the authentication filter. I thought i could authenticate it in the filter and proceed normally and it would skip the rest of the authentication but this didn't work they way i expected it.

public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {

    private static final String HEADER_NAME = "X-API-KEY";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        logger.info("Checking for API key");

        String apiKey = request.getHeader(HEADER_NAME);
        
        logger.info(apiKey);

        if (StringUtils.hasText(apiKey)) {

            Authentication auth = new ApiKeyAuthenticationToken(apiKey, List.of(new SimpleGrantedAuthority("ROLE_VIEWER")));
            SecurityContextHolder.getContext().setAuthentication(auth);

            return;

        }

        filterChain.doFilter(request, response);

    }
}

As you can see i set the authentication and return immediatly. This raises now the issue that i get a status code 200 but no response body. This is because i return there, right?

The filter doesn't make very much sense for now, because i havn't added the "checking" part, but for now it is just for testing

I want to add a second authentication provider to my spring project but i'm using the Spring Boot Active Directory Starter dependency where i only configure the oauth2 resource server with a keyset uri and an authentication converter.

How would i approach this problem? Can i just add a second authentication provider or is some configuration needed for the aad auth before? If some configuration is needed could you explain this then to me?

Here is my current Spring Security Config

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SpringSecurityConfig implements WebMvcConfigurer {

    @Value("${security.oauth2.resource.jwt.key-uri}")
    private String keySetUri;

    @Value("${security.oauth2.resource.id}")
    private String resourceId;

    @Value("${security.oauth2.issuer}")
    private String issuer;

    @Value("${security.oauth2.scope.access-as-user}")
    private String accessAsUserScope;

    private final FigaroAuthenticationConverter authenticationConverter;

    @Autowired
    public SpringSecurityConfig(FigaroAuthenticationConverter authenticationConverter) {
        this.authenticationConverter = authenticationConverter;
    }

    /**
     * Configures the security filter chain for the application.
     *
     * @param http the HttpSecurity to modify
     * @return the configured SecurityFilterChain
     * @throws Exception if an error occurs while configuring the filter chain
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.cors(corsCustomizer ->
                corsCustomizer.configurationSource(request -> {

                    CorsConfiguration config = new CorsConfiguration();

                    config.setAllowedOrigins(List.of("http://localhost:4200"));
                    config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS"));
                    config.setAllowCredentials(true);
                    config.setExposedHeaders(List.of("Authorization"));
                    config.setAllowedHeaders(List.of(
                            "Access-Control-Allow-Headers",
                            "Access-Control-Allow-Origin",
                            "Access-Control-Request-Method",
                            "Access-Control-Request-Headers",
                            "Origin",
                            "Cache-Control",
                            "Content-Type",
                            "Authorization"
                    ));

                    config.setMaxAge(3600L);

                    return config;
                }));

        http.sessionManagement(Customizer.withDefaults())
                .sessionManagement(sessionManagementConfigurer ->
                        sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS));


        http.oauth2ResourceServer(oauth2 ->
                oauth2.jwt(jwtConfigurer -> {

                    jwtConfigurer.jwkSetUri(this.keySetUri);
                    jwtConfigurer.jwtAuthenticationConverter(jwtAuthenticationConverter());

                }));

        http.authorizeHttpRequests(auth -> {
            auth.requestMatchers(HttpMethod.OPTIONS)
                    .permitAll();
            auth.requestMatchers("/actuator/**", "/api-docs").permitAll();
            auth.anyRequest().authenticated();
        });

        return http.build();
    }

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(this.authenticationConverter);
        return jwtAuthenticationConverter;
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri(this.keySetUri).build();
    }
}

I havn't tried anything. But i don't really now where to start. I researched before this post a bit and found out that i could achive this with something like that

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(oAuth2ResourceServerConfigurer);
        auth.authenticationProvider(customAuthenticationProvider);
}

----------------------- EDIT -----------------------

I've tried solving this issue with a request filter that is executed before the authentication filter. I thought i could authenticate it in the filter and proceed normally and it would skip the rest of the authentication but this didn't work they way i expected it.

public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {

    private static final String HEADER_NAME = "X-API-KEY";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        logger.info("Checking for API key");

        String apiKey = request.getHeader(HEADER_NAME);
        
        logger.info(apiKey);

        if (StringUtils.hasText(apiKey)) {

            Authentication auth = new ApiKeyAuthenticationToken(apiKey, List.of(new SimpleGrantedAuthority("ROLE_VIEWER")));
            SecurityContextHolder.getContext().setAuthentication(auth);

            return;

        }

        filterChain.doFilter(request, response);

    }
}

As you can see i set the authentication and return immediatly. This raises now the issue that i get a status code 200 but no response body. This is because i return there, right?

The filter doesn't make very much sense for now, because i havn't added the "checking" part, but for now it is just for testing

Share Improve this question edited Mar 19 at 6:37 DasShorty asked Mar 18 at 13:44 DasShortyDasShorty 113 bronze badges 8
  • 1 Depending on the version of Spring Security and Spring Boot you don't need your configure method just configure and expose the AuthenticationProvider as beans and they will be automatically be detected. – M. Deinum Commented Mar 18 at 14:33
  • What is the nature of the second provider? Also oauth2 or something internal? – John Williams Commented Mar 18 at 14:38
  • @JohnWilliams the second provider should be a simple api key auth provider. – DasShorty Commented Mar 19 at 6:26
  • @M.Deinum i tried what you said but still nothing changed. I use an different header for api key authentication than the "Authentication" header. Is there a possible way to "change" the header name in the provider? I don't want to use the Authentication header due to the already existing usage from the other auth provider. Is there maybe a way to say like every request with header "x" goes to this provider and every other request goes to the other? – DasShorty Commented Mar 19 at 7:01
  • The provider doesn't do anything with the request or the header. You still would need a filter that creates a custom Authentication object that works with your header. Based on the Authentication type the correct AuthenticationProvider will be selected. – M. Deinum Commented Mar 19 at 7:23
 |  Show 3 more comments

1 Answer 1

Reset to default 0

You can do this by creating a filter which implements the API Key style validation and adding it to the http filter chain. This will leave the oauth2 capability untouched.

This is a sample implementation from a current project:

@Service
@Slf4j
public class ApiKeyFilter extends GenericFilterBean {

    public ApiKeyFilter(ApplicationProperties applicationProperties) {
        
        try {
            ourApiKey = applicationProperties.getCurata().getOurApiKey();            
        } catch (Exception e) {
            log.error("Problems setting up api-key filter. Will not be able to authenticate Curata callbacks in spite of being indicated in the logs as ready for use");
            ourApiKey = "nonesense";
        }

    }

  
    public static final String API_KEY_HEADER = "X-API-Key";

    private String ourApiKey;

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;

        String apiKey = resolveApiKey(httpServletRequest);
        if (StringUtils.hasText(apiKey) && apiKey.equals(ourApiKey)) {
            Authentication authentication = getAuthentication();
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(servletRequest, servletResponse);
    }

    private String resolveApiKey(HttpServletRequest request) {
        String submittedApiKey = request.getHeader(API_KEY_HEADER);
        if (StringUtils.hasText(submittedApiKey)) {
            return submittedApiKey;
        }
        return null;
    }
    
    private Authentication getAuthentication() {

        Collection<? extends GrantedAuthority> authorities = Arrays
            .stream(new String[] {AuthoritiesConstants.CURATA})
            .map(SimpleGrantedAuthority::new)
            .collect(Collectors.toList());

        User principal = new User("curata", "", authorities);

        return new UsernamePasswordAuthenticationToken(principal, "from X-API-Key", authorities);
    }


}

And add it to the http filter chain as follows:

@Component
@NoArgsConstructor
public class ApiKeyConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {

    @Autowired
    ApplicationProperties applicationProperties;
    
    @Override
    public void configure(HttpSecurity http) {
        ApiKeyFilter customFilter = new ApiKeyFilter(applicationProperties);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

本文标签: javaHow to add a second authentication provider for Spring Boot if i use Aad JwtsStack Overflow