Spring Security:在Spring Boot 2.7.0中升级已弃用的WebSecurityConfigurerAdapter

105

我正在尝试更新WebSecurityConfigurerAdapter,因为它已被弃用。该类的配置如下:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UsuariService userDetailsService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;
    
    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                .antMatchers("/api/auth/**").permitAll().antMatchers("/api/test/**").permitAll().antMatchers("/api/v1/**").permitAll().anyRequest()
                .authenticated();

        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
    }

}

现在没有 WebSecurityConfigurerAdapter,我会像这样重新定义同一个类:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {

    @Autowired
    UsuariService userDetailsService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;

    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    @Bean
    AuthenticationManager authenticationManager(AuthenticationManagerBuilder builder) throws Exception {
        return builder.userDetailsService(userDetailsService).passwordEncoder(encoder()).and().build();
    }

    @Bean
    public PasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
                .antMatchers("/api/auth/**").permitAll()
                .antMatchers("/api/test/**").permitAll()
                .antMatchers("/api/v1/**").permitAll()
                .anyRequest().authenticated();
        http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

但不幸的是,我收到了以下错误:

org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': 
  Unsatisfied dependency expressed through method 'setFilterChains' parameter 0; 
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'filterChain' defined in class path resource [cit/base/app/security/WebSecurityConfig.class]: 
  Unsatisfied dependency expressed through method 'filterChain' parameter 0; 
nested exception is org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity' defined in class path resource [org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class]: 
Bean instantiation via factory method failed; 
nested exception is org.springframework.beans.BeanInstantiationException: 
  Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception; 
nested exception is java.lang.IllegalStateException: 
  Cannot apply org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer@3fdc705c to already built object

我会非常感激任何形式的帮助,非常欢迎。


1
欢迎来到 Stack Overflow。如果您将这两个 @Autowired 字段替换为参数注入到 @Bean 方法中,可能会有所帮助。 - Andy Wilkinson
3
我正在做同样的事情,尝试更新WebSecurityConfig。我找到了这个教程:https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter。我不太清楚如何根据它来更新我的方法,但也许你知道。希望能帮到你! - Gloria
对于任何寻找示例的人,我发现了这个网址 https://www.codejava.net/frameworks/spring-boot/fix-websecurityconfigureradapter-deprecated。希望它能有所帮助! - Praveen G
这是有关使用JWT过滤器的更多信息:https://dev59.com/TOk5XIcBkEYKwwoY2NHV - Yann39
9个回答

77

我已经成功更新了方法。这是WebSecurityConfig类,方法的修改如下:

public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

已经成为

@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
    return authenticationConfiguration.getAuthenticationManager();
}

在旧版本中,您注入AuthenticationManagerBuilder,设置userDetailsServicepasswordEncoder并构建它。但是,在此步骤中已经创建了authenticationManager。它是按我们想要的方式创建的(具有userDetailsServicepasswordEncoder)。
接下来,HttpSecurityconfigure()方法被替换为filterChain方法,正如官方网站上所解释的那样:
import com.myproject.UrlMapping;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
   private final UserDetailsService userDetailsService;

   private final AuthEntryPointJwt unauthorizedHandler;

   private final AuthTokenFilter authenticationJwtTokenFilter;

   @Bean
   public PasswordEncoder passwordEncoder() {
       return new BCryptPasswordEncoder();
   }

   @Bean
   public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
       return authenticationConfiguration.getAuthenticationManager();
   }

   @Bean
   public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
       http.cors().and().csrf().disable()
               .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
               .authorizeRequests()
               .antMatchers(UrlMapping.AUTH + UrlMapping.SIGN_UP).permitAll()
               .antMatchers(UrlMapping.AUTH + UrlMapping.LOGIN).permitAll()
               .antMatchers(UrlMapping.VALIDATE_JWT).permitAll()
               .antMatchers("/api/test/**").permitAll()
               .anyRequest().authenticated();

       http.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);

       return http.build();
   }

   @Bean
   public WebMvcConfigurer corsConfigurer() {
       return new WebMvcConfigurer() {
           @Override
           public void addCorsMappings(CorsRegistry registry) {
               registry.addMapping("/**")
                       .allowedMethods("*");
           }
       };
   }
}

我已经在我的build.gradle文件中添加了这个:

implementation 'javax.xml.bind:jaxb-api:2.3.0'

2
我已经测试过了,它完美地工作了!我用authenticationManager()替换了configure()函数,第一次就成功了。非常感谢你的帮助! - Ramon J.
3
想法是从AuthenticationConfiguration获取默认的AuthenticationManager。 这对我来说非常完美。 谢谢! - Amine Aouffen
1
谢谢,@Gloria,它对我也在第一次尝试中起作用了。我在许多网站上搜索,但没有找到任何不使用WebSecurtiyConfigurerAdapter的地方。所以,我被困在我的代码中。但是,你拯救了这一天。 :) - Yash Modi
1
哦,我很高兴听到那个消息。不客气 @YashModi :) - Gloria
1
@Toerktumlare,您是否愿意发布一个使用您认为不是“有缺陷”的东西的答案? - cowley05
显示剩余7条评论

18

我希望这个配置能够适用于UserDetailsServiceAuthenticationManagerBuilderAuthenticationManager

@Configuration
public class BeanConfiguration {

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
@Configuration
public class SpringSecurityConfiguration {

    AuthenticationManager authenticationManager;

    @Autowired
    UserDetailsService userDetailsService;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder.userDetailsService(userDetailsService);
        authenticationManager = authenticationManagerBuilder.build();

        http.csrf().disable().cors().disable().authorizeHttpRequests().antMatchers("/api/v1/account/register", "/api/v1/account/auth").permitAll()
            .anyRequest().authenticated()
            .and()
            .authenticationManager(authenticationManager)
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        return http.build();
    }

}
@Component
class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private AccountService accountService;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        Account account = accountService.findAccountByEmail(email);
        return new UserPrincipalImp(account);
    }

    // ...
}

7

将您的文件更改如下:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SpringSecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.csrf().disable().cors().disable().authorizeHttpRequests()
                .requestMatchers("/user/register").permitAll()
                .anyRequest().authenticated()
                .and()
                .oauth2ResourceServer();

        return http.build();
    }
}

5
没有扩展WebSecurityConfigurerAdapter的SecurityConfig类的完整实现如下所示。
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig {
    
        @Autowired
        private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    
        @Autowired
        UserDetailsService userDetailsService;
    
        @Autowired
        private JwtRequestFilter jwtRequestFilter;
    
        @Bean
        public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{
            // We don't need CSRF for this example
            httpSecurity.csrf().disable()
                    // don't authenticate this particular request
                    .authorizeHttpRequests().antMatchers("/authenticate").permitAll()
                    // all other requests need to be authenticated
                    .anyRequest().authenticated().and()
                    // make sure we use stateless session; session won't be used to
                    // store user's state.
                    .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    
            // Add a filter to validate the tokens with every request
            httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
            return httpSecurity.build();
        }
    
        @Bean
        public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
            return authenticationConfiguration.getAuthenticationManager();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    }

2
你好! 请问您是如何实现“JwtAuthenticationEntryPoint”的?因为我想在获取错误/过期的jwt时处理错误。 - Robs
你可以查看我的代码库来了解Jwt的实现。Spring Boot应用程序与Jwt - Yash Modi
1
@Toerktumlare 你说得对,但有时候会出现在没有OAuth2的情况下使用JWT的情况。 - Sung.P
1
@Toerktumlare,您能否提供一些使用新的Spring Security的开箱即用解决方案的示例?我找不到任何东西。互联网上的每个示例都描述了自定义实现。此外,在Spring文档中(关于用户名和密码身份验证),我也没有找到任何内容。 - Kamil
使用FormLogin,然后实现authenticationSuccessHandler,在其中使用Nimbus创建JWT。如果您希望实现处理JWT的功能,可以参考这篇文章:https://thomasandolf.medium.com/spring-security-jwts-getting-started-ebdb4e4f1dd1。Spring Security在”认证”章节中描述了许多“用户名/密码”验证模型,并提供了处理JWT的完整解决方案。 - Toerktumlare
显示剩余4条评论

2
在Spring Security 6中有一些更改:
  • @EnableGlobalMethodSecurity现已弃用,请使用@EnableMethodSecurity
  • 无法使用antMatchers,请使用requestMatchers
  • 方法configureGlobal(AuthenticationManagerBuilder auth)不起作用,请参考Gloria的答案设置JDBC身份验证

使用Spring Boot 2.x的代码

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;

    @Autowired
    public AuthTokenFilter authenticationJwtTokenFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

    }

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .authorizeRequests().antMatchers("/auth/**").permitAll().antMatchers("/test/**").permitAll()
        .antMatchers("/readiness_check").permitAll().antMatchers("/liveness_check").permitAll()
        .antMatchers("/_ah/start").permitAll().anyRequest().authenticated();
        

        http.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

}

使用Spring Boot 3.1.13进行编码

    @Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsServiceImpl userDetailsService;

    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;

    @Autowired
    public AuthTokenFilter authenticationJwtTokenFilter;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

    }

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

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .authorizeRequests().antMatchers("/auth/**").permitAll().antMatchers("/test/**").permitAll()
        .antMatchers("/readiness_check").permitAll().antMatchers("/liveness_check").permitAll()
        .antMatchers("/_ah/start").permitAll().anyRequest().authenticated();
        

        http.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }

}

JWT的创建也需要更改,因为jjwt依赖无法正常工作。可以参考此页面Spring Security参考文档

非常感谢您的巨大贡献!真的非常有帮助。 - undefined

1

在我的情况下它有效,最简单的方法是直接将您的userDetailService类传递到SecurityFilterChain函数中。

注意: http.userDetailsService(customUserDetailService);

如果配置中有@Bean方法,则BCryptPasswordEncoder类会自动获取自动装配为密码编码器。

@Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }

代码:

package com.example.blogapi.config;

import com.example.blogapi.security.CustomUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;


@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {


    @Autowired
    private CustomUserDetailService customUserDetailService;


    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {


        http
                .csrf().disable()
                .authorizeHttpRequests(
                        (authz) -> authz.anyRequest()
                                .authenticated())
                .httpBasic(Customizer.withDefaults())
                .userDetailsService(customUserDetailService);

        return http.build();
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }





}

1
最简单的方法是向框架提供一个UserDetailsService的bean,就像你正在使用bcrypt一样,框架会为你处理其余部分。 - Toerktumlare

1
下面的代码演示了在没有使用WebSecurityConfigurerAdapter的情况下实现Spring Security基本身份验证的可能解决方案。 在旧版本中,我们注入AuthenticationManagerBuilder,设置userDetailsService、passwordEncoder并构建它。但是,AuthenticationManager是按照我们想要的方式创建的(具有userDetailsService和passwordEncoder)。
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // disabling csrf since we won't use form login
                .csrf().disable()
                // setting stateless session, because we choose to implement Rest API
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // giving permission to every request for /login endpoint
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                // for everything else, the user has to be authenticated
                .anyRequest().authenticated()
                .and()
                .httpBasic();

        return http.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web -> web.ignoring().antMatchers("/images/**", "/js/**", "/webjars/**"));
    }
}

0
  • 安全配置
@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        int rounds = 12;
        return new BCryptPasswordEncoder(rounds);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                .csrf()
                .disable()
                .httpBasic()
                .and()
                .authorizeHttpRequests()
                /*.requestMatchers("/user/**").hasRole("USER")*/
                .requestMatchers("/user/**", "/user/info/**").hasAuthority("USER")
                .anyRequest().authenticated()
                .and()
                .formLogin().permitAll()
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);;

        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(UserDetailsService customUserDetailsService) {

        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(customUserDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());

        List<AuthenticationProvider> providers =  List.of(authProvider);

        return new ProviderManager(providers);
    }
}

  • 服务
@Service
@RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {

    private final CustomerRepository customerRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        final CustomerModel customer = customerRepository.findByEmail(username); /*email*/

        Set<UserRole> roles = new HashSet<>();
        roles.add(new UserRole("USER"));
        roles.add(new UserRole("ADMIN"));

        if (customer == null) {
            throw new UsernameNotFoundException(username);
        }

        String email = customer.email();
        String password = customer.password();

        return User
                .withUsername(email)
                .password(password)
                /*.roles("USER")*/ /*Into a Security filter must be expression -> hasRole()*/
        
                .authorities(convertAuthorities(roles))
                .build();
    }

    private Set<GrantedAuthority> convertAuthorities(Set<UserRole> userRoles) {
        Set<GrantedAuthority> authorities=new HashSet<>();
        for (UserRole userRole : userRoles) {
            authorities.add(new SimpleGrantedAuthority(userRole.nameRole()));
        }
        return authorities;
    }
}


0

取决于已弃用的WebSecurityConfigurerAdapter

对于"基本身份验证",我们可以使用以下代码片段:

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.csrf().disable()
            .authorizeRequests(authorize -> authorize.anyRequest().authenticated())
            .httpBasic(HttpBasicConfigurer::withDefaults)
            .and().build();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接