Since the Spring Security got updated, I had to do some changes in my code. This is my configuration class now.
public class SecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
private final UserRepository userRepository;
public SecurityConfig(UserDetailsServiceImpl userDetailsService, UserRepository userRepository) {
this.userDetailsService = userDetailsService;
this.userRepository = userRepository;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new CustomAuthenticationFilter(authenticationManager()))
.addFilter(new CustomAuthorizationFilter(authenticationManager(), userRepository))
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/user/users/save").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.POST, "/users//register").permitAll()
.antMatchers("/**").permitAll() // Only for testing purposes
.antMatchers("/user/**").hasAuthority("ADMIN")
.antMatchers("/user/**").authenticated();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
However, since I have changed the configure method and the authentication manager to beans, I am not able to pass the authentication manager to my filters. Which look like this.
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
@Value("${security.key}")
private String secretKey;
public CustomAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
LoginRequest loginRequest = null;
try {
loginRequest = new ObjectMapper().readValue(request.getInputStream(), LoginRequest.class);
} catch (IOException e) {
throw new IllegalArgumentException();
}
String username = loginRequest.getUsername();
String password = loginRequest.getPassword();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
return authenticationManager.authenticate(authenticationToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal();
Algorithm algorithm = HMAC512(secretKey.getBytes());
List<String> list = user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
String accessToken = JWT.create()
.withSubject(user.getUsername())
.withExpiresAt(new Date(System.currentTimeMillis() 10*60*1000))
.withClaim("authorities", list)
.sign(algorithm);
response.addHeader("authorization","Bearer " accessToken);
}
}
How can I inject the authenticationManager to my filter?
CodePudding user response:
The recommended way to access the AuthenticationManager
is by using a custom DSL. This is actually how Spring Security internally implements methods like HttpSecurity.authorizeRequests()
.
public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> {
@Override
public void configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
http.addFilter(new CustomAuthenticationFilter(authenticationManager));
}
public static MyCustomDsl customDsl() {
return new MyCustomDsl();
}
}
The custom DSL can then be applied when building the SecurityFilterChain
:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// ...
http.apply(customDsl());
return http.build();
}
This is described in the blog post on migrating away from the WebSecurityConfigurerAdapter
, under the section "Accessing the local AuthenticationManager".
CodePudding user response:
I guess this should work (since all the calls to @Bean methods are proxied via CGLIB and therefore the cached version of the bean is returned):
public class SecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
private final UserRepository userRepository;
private final AuthenticationConfiguration authenticationConfiguration;
public SecurityConfig(UserDetailsServiceImpl userDetailsService, UserRepository userRepository, AuthenticationConfiguration authenticationConfiguration) {
this.userDetailsService = userDetailsService;
this.userRepository = userRepository;
this.authenticationConfiguration = authenticationConfiguration;
}
@Bean
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilter(new CustomAuthenticationFilter(authenticationManager()))
.addFilter(new CustomAuthorizationFilter(authenticationManager(), userRepository))
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/user/users/save").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.antMatchers(HttpMethod.POST, "/users//register").permitAll()
.antMatchers("/**").permitAll() // Only for testing purposes
.antMatchers("/user/**").hasAuthority("ADMIN")
.antMatchers("/user/**").authenticated();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
If there is circular dependencies issue, you can place @Lazy annotation on AuthenticationConfiguration authenticationConfiguration in constructor.