使用几个spring安全配置,并根据调用url应用它们

iklwldmw  于 2021-09-29  发布在  Java
关注(0)|答案(2)|浏览(334)

我目前有一个后端应用程序,它基于必须添加到http头中的登录名/密码实现一个非常简单的Spring Security 。
我还有一个前端,它使用okta作为提供者,并使用jwt令牌。
现在,我想让前端应用程序专用的端点使用jwt令牌系统,其他所有应用程序使用当前的登录/密码系统。
我可以让我的应用程序与okta配置或登录/密码配置一起工作,但我不能让两者一起工作。
通过查看堆栈溢出上的不同消息,我实现了双重配置,但它始终是应用的第一个配置。第二个简单地忽略,并且允许外围的端点不使用任何令牌或登录/密码

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Configuration
@Order(1)
public static class OauthOktaConfigurationAdapter extends WebSecurityConfigurerAdapter {
    protected void configure(HttpSecurity http) throws Exception {

        http.cors();
        http.csrf().disable();

        http
                .authorizeRequests().antMatchers("/api/v1/end-point/**").authenticated()
                .and().oauth2ResourceServer().jwt();

        Okta.configureResourceServer401ResponseBody(http);
    }
}

@Configuration
@Order(2)
public static class StandardSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

    @Value("${http.auth-app-id-header-name}")
    private String appIdRequestHeaderName;
    @Value("${http.auth-api-key-header-name}")
    private String apiKeyRequestHeaderName;
    private final AuthenticationManager authenticationManager;

    @Autowired
    public StandardSecurityConfigurationAdapter(AuthenticationManager authenticationManager) {
        super();
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors();
        http.csrf().disable();

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().addFilter(initAuthenticationFilter())
                .antMatcher("/api/v1/tools/**")
                .authorizeRequests().anyRequest().authenticated();
    }

    private RequestHeaderAuthenticationFilter initAuthenticationFilter() {
        RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter(appIdRequestHeaderName,
                apiKeyRequestHeaderName);
        requestHeaderAuthenticationFilter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
        requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager);
        return requestHeaderAuthenticationFilter;
    }
}

@Override
@Bean
@Primary
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}
}

在这段代码中,即使我调用了/api/v1/tools,也永远不会使用配置2。如果我删除配置1,则应用配置2。
你能帮我理解我做错了什么吗?
编辑1:
在eleftheria stein kousathana的帮助和建议下,我更改了配置(并添加了swagger白名单配置)

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

private static final String[] AUTH_WHITELIST = {
        "/v2/api-docs",
        "/swagger-resources/configuration/ui",
        "/swagger-resources",
        "/swagger-resources/configuration/security",
        "/swagger-ui.html",
        "/webjars/**"
};

@Configuration
@Order(1)
public static class SwaggerConfigurationAdapter extends WebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {

        System.out.println("Loading configuration 1");

        http.cors();
        http.csrf().disable();

        http
                .requestMatchers(matchers -> matchers.antMatchers(AUTH_WHITELIST))
                .authorizeRequests(authz -> {
                    authz.anyRequest().permitAll();
                });
    }
}

@Configuration
@Order(2)
public static class OauthOktaConfigurationAdapter extends WebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {

        System.out.println("Loading configuration 2");

        http.cors();
        http.csrf().disable();

        http
                .requestMatchers(matchers -> matchers.antMatchers("/api/v1/end-point/**"))
                    .authorizeRequests(authz -> {
                        try {
                            authz.anyRequest().authenticated().and().oauth2ResourceServer().jwt();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    });

        Okta.configureResourceServer401ResponseBody(http);
    }
}

@Configuration
@Order(3)
public static class StandardSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

    @Value("${algo.http.auth-app-id-header-name}")
    private String appIdRequestHeaderName;
    @Value("${algo.http.auth-api-key-header-name}")
    private String apiKeyRequestHeaderName;
    private final AuthenticationManager authenticationManager;

    @Autowired
    public StandardSecurityConfigurationAdapter(AuthenticationManager authenticationManager) {
        super();
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        System.out.println("Loading configuration 3");

        http.cors();
        http.csrf().disable();

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().addFilter(initAuthenticationFilter())
                .requestMatchers(matchers -> matchers.antMatchers("/api/**"))
                .authorizeRequests(authz -> {
                    try {
                        authz.anyRequest().authenticated();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
    }

    private RequestHeaderAuthenticationFilter initAuthenticationFilter() {
        RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter(appIdRequestHeaderName,
                apiKeyRequestHeaderName);
        requestHeaderAuthenticationFilter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
        requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManager);
        return requestHeaderAuthenticationFilter;
    }
}

@Override
@Bean
@Primary
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}
}

我觉得我离成功很近了
未经身份验证时,可以访问swaggers
与“/api/v1/end-point/”对应的路由需要一个jwt令牌,否则会出现401错误
与“/api/
”对应的路由需要登录名/密码,否则会出现401错误
但现在我有以下错误:
每次我在swagger下请求页面或调用api时,我的web浏览器都会要求我输入登录名/密码。
如果取消,我仍然可以在swagger ui上导航并调用“/api/v1/end-point/”。即使每个登录/密码在配置3中有效,也会被拒绝。
如果我没有填写登录名/密码并调用“/api/
”的任何路由,我会出现以下错误:

2021-07-23 14:49:16.642 [http-nio-8081-exec-9] INFO  c.c.a.a.c.CorrelationIdLoggingAspect - Calling api.controller.endpoint.getActivities executed in 197ms.
2021-07-23 14:49:22.247 [http-nio-8081-exec-1] ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [/secret] threw exception [Filter execution threw an exception] with root cause
java.lang.StackOverflowError: null
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:205)
    at com.sun.proxy.$Proxy236.authenticate(Unknown Source)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:195)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:501)
    at jdk.internal.reflect.GeneratedMethodAccessor220.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:205)
    at com.sun.proxy.$Proxy236.authenticate(Unknown Source)
    at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:195)
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:501)
xkftehaa

xkftehaa1#

如果我正确理解了您的程序草图和描述,让我尝试总结一下。您的应用程序旨在支持以下内容:
向公众提供 swagger 的用户界面,并允许浏览api定义。
使用经过身份验证的api端点(前缀为 /api/v1/end-point )通过okta从另一个客户处提供的jwt(不是炫耀)。
使用经过身份验证的api端点(前缀为 /api ,但不是 /api/v1/end-point )通过以用户名/密码为标题的swagger。
注意:我不打算在这里介绍如何将okta配置为提供者,也不介绍如何配置swagger。如果这些步骤没有正确执行,您可能仍然存在问题。
至于Spring Security ,我认为您的主要问题是因为您似乎没有为基于头的配置配置身份验证提供程序。这通常是通过 UserDetailsService (请参阅userdetailsservice一节),或者有许多更高级的方法。在您的情况下,只需按如下方式添加bean就足够了:

@Bean
    public UserDetailsService userDetailsService() {
        // @formatter:off
        UserDetails userDetails = User.builder()
                .username("api-client")
                .password("{noop}my-api-key")
                .roles("USER")
                .build();
        // @formatter:on

        return new InMemoryUserDetailsManager(userDetails);
    }

这显然是一个不适合生产的例子。但重要的一点是,你必须提供一种解决问题的方法 RequestHeaderAuthenticationFilter 以确定凭据是否有效。
无论是用户的用户名/密码,还是api客户端的appid/apikey principal (请参阅身份验证)是通过 UserDetailsService ,然后由 AuthenticationProvider .
我还建议您重写 configure(WebSecurity web) 方法 WebSecurityConfigurerAdapter 履行你的职责 permitAll 并将配置压缩为两个,同时消除 /api/** 模式,使整个应用程序在默认情况下是安全的。下面是一个完整的示例(省略任何okta特定代码),演示了spring security lambda dsl的正确用法:

@Configuration
public class SecurityConfiguration {

    private static final String[] AUTH_WHITELIST = {
        "/v2/api-docs",
        "/swagger-resources/configuration/ui",
        "/swagger-resources",
        "/swagger-resources/configuration/security",
        "/swagger-ui.html",
        "/webjars/**"
    };

    @Order(1)
    @EnableWebSecurity
    public static class OauthOktaConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
                .antMatcher("/api/v1/end-point/**")
                .authorizeRequests((authorizeRequests) ->
                    authorizeRequests
                        .anyRequest().authenticated()
                )
                .oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
                .sessionManagement((sessionManagement) ->
                    sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .cors(withDefaults())
                .csrf(CsrfConfigurer::disable);
            // @formatter:on
        }

    }

    @Order(2)
    @EnableWebSecurity
    public static class StandardSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {

        @Value("${algo.http.auth-app-id-header-name}")
        private String appIdRequestHeaderName;

        @Value("${algo.http.auth-api-key-header-name}")
        private String apiKeyRequestHeaderName;

        @Override
        public void configure(WebSecurity web) {
            web.ignoring().antMatchers(AUTH_WHITELIST);
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // @formatter:off
            http
                .addFilterAt(initAuthenticationFilter(), RequestHeaderAuthenticationFilter.class)
                .authorizeRequests((authorizeRequests) ->
                    authorizeRequests
                        .anyRequest().authenticated()
                )
                .sessionManagement((sessionManagement) ->
                    sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                )
                .cors(withDefaults())
                .csrf(CsrfConfigurer::disable);
            // @formatter:on
        }

        private RequestHeaderAuthenticationFilter initAuthenticationFilter() throws Exception {
            RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter();
            requestHeaderAuthenticationFilter.setPrincipalRequestHeader(appIdRequestHeaderName);
            requestHeaderAuthenticationFilter.setCredentialsRequestHeader(apiKeyRequestHeaderName);
            requestHeaderAuthenticationFilter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
            requestHeaderAuthenticationFilter.setAuthenticationManager(authenticationManagerBean());
            return requestHeaderAuthenticationFilter;
        }

    }

    @Bean
    public UserDetailsService userDetailsService() {
        // @formatter:off
        UserDetails userDetails = User.builder()
                .username("api-client")
                .password("{noop}my-api-key")
                .roles("USER")
                .build();
        // @formatter:on

        return new InMemoryUserDetailsManager(userDetails);
    }

}

最后一点注意:一个警告是我包括了禁用csrf,您已经这么做了。如果您不打算在带有会话的web浏览器中使用此应用程序,那么这样做是合理的。因为我将这两种配置都标记为无状态(您的okta+jwt示例不是),所以这似乎是合理的。然而,大多数情况下,您确实不想禁用csrf保护,尤其是当原因是“我不知道如何使我的ui应用程序在启用csrf的情况下工作”时

qzwqbdag

qzwqbdag2#

首先,非常感谢你的帮助。我花时间回答,因为我想了解你的答案。
关于我试图实现的草图的描述,你是对的。通过您的配置,我现在可以无需任何登录/密码即可访问swagger。
第一个配置(okta)运行良好,我认为最后一个配置(登录/密码)也可以。
当我尝试访问受登录名和密码保护的路由时,我现在面临最后一个错误。我面临一个问题,spring抛出了一个“org.springframework.security.authentication.providernotfoundexception:找不到org.springframework.security.web.authentication.Preauthentication.preauthenticatedauthenticationtoken的authenticationprovider”异常。
我期待着解决这个问题,我想在那之后一切都会好起来。
让我谦虚地指出,setter方法:

requestHeaderAuthenticationFilter.setPrincipalRequestHeader(appIdRequestHeaderName);
requestHeaderAuthenticationFilter.setCredentialsRequestHeader(apiKeyRequestHeaderName);

不可访问,我一直通过构造函数设置它们。

RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter = new RequestHeaderAuthenticationFilter(appIdRequestHeaderName, apiKeyRequestHeaderName)

相关问题