Spring Boot配置双Spring Security类实现Basic与JWT认证启动报错如何解决?
问题原因与解决方案
1. 可行性说明
Spring Security完全支持多套安全配置分别保护不同路径的接口,你的实现思路是正确的。
2. 启动报错原因
两个WebSecurityConfigurerAdapter实现类都未显式指定@Order注解,Spring Security对该类的默认排序值为100,排序值重复导致上下文初始化失败。
3. 额外隐藏问题
即使解决排序问题,你当前的配置也无法正常实现两种认证逻辑隔离:优先级更高的配置会默认拦截所有请求,导致第二个配置完全不生效。
4. 具体修改步骤
步骤1:给两个配置类设置不同优先级
给路径更具体的Basic认证配置设置更高优先级(Order值越小优先级越高),JWT配置设置更低优先级:
- Basic认证配置类加
@Order(1) - JWT认证配置类加
@Order(2)
步骤2:限制Basic认证配置的生效范围
在Basic认证的configure(HttpSecurity http)方法开头添加antMatcher,限制该配置仅处理指定路径的请求,剩余请求交给下一个优先级的JWT配置处理。
步骤3:解决重复Bean冲突
两个配置类都定义了PasswordEncoder、UserDetailsService,会导致Bean冲突,可将公共的PasswordEncoder抽为独立公共Bean,给Basic认证专用的UserDetailsService添加@Qualifier做区分。
5. 修改后代码示例
Basic认证配置类
@Configuration @EnableWebSecurity @Order(1) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { private static String REALM = "REAME"; private static final String BASIC_AUTH_PATH = "/api/user/email/**"; @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher(BASIC_AUTH_PATH) .csrf().disable() .authorizeRequests() .anyRequest().hasAnyRole("USER","ADMIN") .and() .httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint()).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Bean public AuthEntryPoint getBasicAuthEntryPoint() { return new AuthEntryPoint(); } @Override public void configure(WebSecurity web) { web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**"); } @Bean @Qualifier("basicAuthUserDetails") @Override public UserDetailsService userDetailsService() { UserBuilder users = User.builder(); InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); manager.createUser(users .username("ReadUser") .password(encoder.encode("BimBumBam_2018")) .roles("USER").build()); manager.createUser(users .username("Admin") .password(encoder.encode("MagicaBula_2018")) .roles("USER", "ADMIN").build()); return manager; } }
公共安全配置类
@Configuration public class CommonSecurityConfig { @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
JWT认证配置类
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) @Order(2) public class JWTWebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtUnAuthorizedResponseAuthenticationEntryPoint jwtUnAuthorizedResponseAuthenticationEntryPoint; @Autowired @Qualifier("customUserDetailsService") private UserDetailsService userDetailsService; @Autowired private JwtTokenAuthorizationOncePerRequestFilter jwtAuthenticationTokenFilter; @Autowired private BCryptPasswordEncoder passwordEncoder; @Value("${sicurezza.uri}") private String authenticationPath; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } @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() .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, "/"); } }
内容的提问来源于stack exchange,提问作者AndreaNobili
相关产品推荐
相关产品推荐

