我可以有两个 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入门(2-2)Spring Security 的运行原理 2
使用 oAuth 和基于表单的身份验证配置 Spring Security