我可以有两个 Spring Security 配置类:一个使用基本身份验证保护一些 API,另一个使用 JWT 令牌保护 API?

Posted

技术标签:

【中文标题】我可以有两个 Spring Security 配置类:一个使用基本身份验证保护一些 API,另一个使用 JWT 令牌保护 API?【英文标题】:Can I have two Spring Security configuration classes: one that protect some API with basic authentication and another protecting APIs with JWT token? 【发布时间】:2022-01-05 08:55:36 【问题描述】:

我正在开发一个 Spring Boot 项目,该项目对 Spring Security 配置有特殊要求。

基本上我的 Spring Boot 项目公开了一些 API:其中一个必须使用 基本身份验证(用户名和密码)进行保护,其他必须使用 JWT 令牌身份验证.

注意:为什么会有这种行为?我将简要解释一下。目前我有 2 个微服务(然后我会有更多)。第一个(这个提到的项目)旨在处理数据库上的用户。它包含一个特定的控制器方法,该方法获取用户电子邮件地址(用户名)并将所有用户信息返回给将使用这些信息以生成 JWT 令牌的其他微服务。因此,此方法受 基本身份验证 的保护(因此创建 JWT 令牌的微服务以及与数据库交互的其他微服务可以使用“服务用户凭据”以安全的方式进行通信)。这个项目(我们正在谈论的与数据库交互的项目)还将包含一些其他从客户端使用的 API,因此这些 API 必须使用第二个微服务生成的 JWT 令牌进行保护。

所以基本上我正在尝试使用两个配置类来拆分 Spring Security 配置:

    第一个配置类保护我的 API,旨在将用户详细信息返回给将生成 JWT 令牌的微服务。此行为运行良好(我能够在旨在生成 JWT 令牌的微服务上调用授权 API,此调用返回此微服务上与我的数据库交互的用户信息的 API,并且正确生成了令牌。

    第二个配置类旨在保护所有其他必须受 JWT 令牌身份验证保护的 API(这是因为这些 API 由客户端调用以执行以下操作:添加新用户、更改特定用户详细信息、删除一个用户,列出所有用户等等......所以这些是可以由具有不同权限的登录用户执行的操作,这些权限被定义到我的令牌中)。

在这里我发现了一些困难。

这是我的 SecurityConfiguration 配置类,旨在配置基本身份验证,以保护采用用户名并将用户信息返回到将生成 JWT 令牌的其他微服务的单个 API:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter 
    private static String REALM = "REAME";

    private static final String[] USER_MATCHER =  "/api/user/email/**";
    private static final String[] ADMIN_MATCHER =  "/api/user/email/**";

    @Override
    protected void configure(HttpSecurity http) throws Exception 
            
        http.csrf().disable()
                   .authorizeRequests()
                   .antMatchers(USER_MATCHER).hasAnyRole("USER")
                   .antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
                   .antMatchers("/api/users/test").permitAll()
                   //.antMatchers("/api/users/**").permitAll()
                   .anyRequest().authenticated()
                   .and()
                   .httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint()).and()
                   .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
    
    @Bean
    public AuthEntryPoint getBasicAuthEntryPoint()
    
        return new AuthEntryPoint();
    

    /* To allow Pre-flight [OPTIONS] request from browser */
    @Override
    public void configure(WebSecurity web) 
    
        web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
    

    @Bean
    public BCryptPasswordEncoder passwordEncoder()
    
        return new BCryptPasswordEncoder();
    ;

    @Bean
    @Override
    public UserDetailsService userDetailsService()
    
        UserBuilder users = User.builder();

        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

        manager.createUser(users
                .username("ReadUser")
                .password(new BCryptPasswordEncoder().encode("BimBumBam_2018"))
                .roles("USER").build());

        manager.createUser(users
                .username("Admin")
                .password(new BCryptPasswordEncoder().encode("MagicaBula_2018"))
                .roles("USER", "ADMIN").build());

        return manager;
    

如您所见,此保护使用 基本身份验证 /api/user/email/** 端点。这是从用户电子邮件(用户名)将用户信息返回到将生成 JWT 令牌的微服务的端点。它工作正常:我的两个微服务正确交互,并且 JWT 令牌由另一个微服务正确生成。在我插入旨在通过此 JWT 令牌保护所有其他 API 的第二个配置类之前,情况一直如此。

这是我的新 JWTWebSecurityConfig 类,旨在保护该项目的所有其他 API,这些 API 与与我的系统用户相关的数据库表进行交互:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter 
    
    @Autowired
    private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint;
    
    @Autowired
    @Qualifier("customUserDetailsService")
    private UserDetailsService userDetailsService;
    
    @Autowired
    private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter;
    
    @Value("$sicurezza.uri")
    private String authenticationPath;
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
    
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoderBean());
    

    @Bean
    public PasswordEncoder passwordEncoderBean() 
    
        return new BCryptPasswordEncoder();
    

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
    
        return super.authenticationManagerBean();
    
    
    private static final String[] NOAUTH_MATCHER = "/api/articoli/noauth/**";
    private static final String[] USER_MATCHER =  "/api/users/jwttest";
    private static final String[] ADMIN_MATCHER =  "/api/users/jwttest" ;
    
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception 
    
        httpSecurity.csrf().disable()
        .exceptionHandling().authenticationEntryPoint(jwtUnAuthorizedResponseAuthenticationEntryPoint)
        .and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests()
        .antMatchers(NOAUTH_MATCHER).permitAll() //End Point che non richiede autenticazione
        .antMatchers(USER_MATCHER).hasAnyRole("USER")
        .antMatchers(ADMIN_MATCHER).hasAnyRole("ADMIN")
        .anyRequest().authenticated();
        
        httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        
        httpSecurity.headers().frameOptions()
        .sameOrigin().cacheControl();  
        
    
    
    @Override
    public void configure(WebSecurity webSecurity) 
    
        webSecurity.ignoring()
        .antMatchers(HttpMethod.POST, authenticationPath)
        .antMatchers(HttpMethod.OPTIONS, "/**")
        .and().ignoring()
        .antMatchers(HttpMethod.GET, "/");  
    
    
    

正如您所看到的,目前它只保护我在该项目的控制器中定义的测试 API 端点 /api/users/jwttest。所以基本上我想要的是使用生成的 JWT 令牌与这个受 JWT 保护的端点进行交互。

我不完全确定我的方法是否正确。在运行这个项目的那一刻(之后我插入了第二个配置类)它在项目启动时给了我以下错误:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Injection of autowired dependencies failed; nested exception is java.lang.IllegalStateException: @Order on WebSecurityConfigurers must be unique. Order of 100 was already used on com.easydefi.users.security.SecurityConfiguration$$EnhancerBySpringCGLIB$$fe033b29@3f142e87, so it cannot be used on com.easydefi.users.security.jwt.JWTWebSecurityConfig$$EnhancerBySpringCGLIB$$d9d962a3@552fffc8 too.
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:405) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1431) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:619) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:918) ~[spring-context-5.3.12.jar:5.3.12]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.12.jar:5.3.12]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145) ~[spring-boot-2.5.6.jar:2.5.6]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-2.5.6.jar:2.5.6]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:434) ~[spring-boot-2.5.6.jar:2.5.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:338) ~[spring-boot-2.5.6.jar:2.5.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-2.5.6.jar:2.5.6]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332) ~[spring-boot-2.5.6.jar:2.5.6]
    at com.easydefi.users.GetUserWsApplication.main(GetUserWsApplication.java:10) ~[classes/:na]
Caused by: java.lang.IllegalStateException: @Order on WebSecurityConfigurers must be unique. Order of 100 was already used on com.easydefi.users.security.SecurityConfiguration$$EnhancerBySpringCGLIB$$fe033b29@3f142e87, so it cannot be used on com.easydefi.users.security.jwt.JWTWebSecurityConfig$$EnhancerBySpringCGLIB$$d9d962a3@552fffc8 too.
    at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.setFilterChainProxySecurityConfigurer(WebSecurityConfiguration.java:165) ~[spring-security-config-5.5.3.jar:5.5.3]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:724) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:119) ~[spring-beans-5.3.12.jar:5.3.12]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.3.12.jar:5.3.12]
    ... 17 common frames omitted

是否可以配置 Spring Security 以保护一些具有基本身份验证的 API 和一些具有 JWT 令牌身份验证的其他 API?这种方法可以被认为是正确的吗?我的代码有什么问题?我错过了什么?我该如何解决这个错误?

【问题讨论】:

这能回答你的问题吗? Multiple WebSecurityConfigurerAdapter in spring boot for multiple patterns 【参考方案1】:

您必须先选择哪个配置。标记她@Order(1)。标记第二个配置@Order(2)

【讨论】:

以上是关于我可以有两个 Spring Security 配置类:一个使用基本身份验证保护一些 API,另一个使用 JWT 令牌保护 API?的主要内容,如果未能解决你的问题,请参考以下文章

配置Spring Security以使用两个不同的登录页面

Spring Security:如何将两个应用程序与单独的 Spring Security 配置集成?

Spring Security入门(2-2)Spring Security 的运行原理 2

使用 oAuth 和基于表单的身份验证配置 Spring Security

可以使用 Spring Security 实时加载自定义配置文件吗?

Spring Security OAuth2 简单配置