Auto-Authentication in Spring Security - spring

I have a Secured Spring MVC project. I would like to auto-authorize users when a new account is successfully created. This is usually done where a new account is created, as follows:
#Controller
#RequestMapping("/spitter")
public class SpitterController {
...
#Inject AuthenticationManager authMgr;
#Inject AccountService accountService;
...
#RequestMapping(value="/register", method=POST)
public String processRegistration(
#ModelAttribute("spitter") #Valid Spitter form,
BindingResult result) {
convertPasswordError(result);
String psswd = form.getPassword();
accountService.registerAccount(toAccount(form), psswd, result);
// Auto-Authentication
Authentication authRequest =
new UsernamePasswordAuthenticationToken(form.getUsername(), psswd);
Authentication authResult = authMgr.authenticate(authRequest);
SecurityContextHolder.getContext()
.setAuthentication(authResult);
return (result.hasErrors() ? VN_REG_FORM : VN_REG_OK);
}
...
}
I'm using Java configuration. My security configuration file is
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
AccountService accountService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.rememberMe()
.tokenRepository(new InMemoryTokenRepositoryImpl())
.tokenValiditySeconds(2419200)
.key("spittrKey")
.and()
.httpBasic()
.realmName("Spittr")
.and()
.authorizeRequests()
.antMatchers("/user").hasAnyAuthority("admin", "user")
.antMatchers("/admin").hasAuthority("admin")
.antMatchers("spitter/me").authenticated()
.antMatchers(HttpMethod.POST, "/spittles").authenticated()
.anyRequest().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new UserDetailsServiceAdapter(accountService))
.passwordEncoder(passwordEncoder());
}
}
If I were using XML configuration, I would have an authentication manager element:
<authentication-manager alias="authenticationManager">
...
</authentication-manager/>
where the alias is set because one gets an authentication manager from the http element in addition to the authentication-manager element and you have to distinguish between the two.
However, with my configuration, there is apparently no AuthenticationManager being created:
org.springframework.beans.factory.BeanCreationException:
...
Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: org.springframework.security.authentication.AuthenticationManager spittr.web.SpitterController.authMngr; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.authentication.AuthenticationManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {#javax.inject.Inject()}
...
This is a bit surprising. I thought that at least one such bean would be created by default. I'm not sure what the best solution is.

its not exposed as a spring bean by default, you need to override the authenticationManagerBean() methon on WebSecurityConfigurerAdapter
#Bean(name="myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
Ref: http://docs.spring.io/autorepo/docs/spring-security-javaconfig-build/1.0.0.M1/api-reference/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.html#authenticationManagerBean()

Related

Spring Security: Global AuthenticationManager without the WebSecurityConfigurerAdapter

Im trying to get rid of WebSecurityConfigurerAdapter. The AuthenticationManager is configured like the following:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public static class DefaultSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
#Bean
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder());
}
Now without the WebSecurityConfigurerAdapter i define the global AuthenticationManager like this:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public static class DefaultSecurityConfig {
#Bean
public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth ) throws Exception {
return auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder()).and().build();
}
And i get the error:
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 'defaultSecurityFilterChain' defined in class path resource [org/springframework/boot/autoconfigure/security/servlet/SpringBootWebSecurityConfiguration.class]: Unsatisfied dependency expressed through method 'defaultSecurityFilterChain' 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#536a79bd to already built object
Im using Spring Boot v2.6.4
I'm stuck here for a while i would appreciate any help
Replace
#Bean
public AuthenticationManager authenticationManager(AuthenticationManagerBuilder auth ) throws Exception {
return auth.userDetailsService(userDetailsService())
.passwordEncoder(passwordEncoder()).and().build();
}
by
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
In my project, I needed to use the AuthenticationManager in the Controller, to make a custom login. Then I declare:
#RestController
#RequestMapping("/api/login")
#Slf4j
#RequiredArgsConstructor
public class LoginResource {
private final AuthenticationManager authenticationManager;
....
}
In SecurityConfig:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
#EnableGlobalMethodSecurity(securedEnabled = true)
public class SecurityConfig {
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtRequestFilter jwtRequestFilter;
private final UserDetailsService jwtUserDetailsService;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/login").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManagerBean(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}
}
Then Works fine to me!!
Replace your code with this section below.
Hope it works, working fine in my code
#Bean
public AuthenticationManager authenticationManagerBean(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder
.userDetailsService(jwtUserDetailsService)
.passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}

How can I provide a Spring configuation bean in a WebSecurityConfigurerAdapter for testing?

I use the following SecurityConfiguration class for securing endpoints in a Spring Boot application. This class depends on the ApiConfiguration class which provides the username and password for the in-memory authentication.
When starting a #WebMvcTest for a controller, then Spring also tries to initialize the security configuration but fails to load the application context.
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'ApiConfiguration' available: expected at least 1 bean which qualifies as autowire candidate.
I tried adding a MockBean to the test class:
#MockBean
private ApiConfiguration apiConfiguration;
This resolves the above issue, but then the username and password are null.
Is there any Spring support for providing this configuration bean for testing?
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
#Configuration
#Order(1)
public static class ApiSecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final String API_USER_ROLE = "API_USER";
private final ApiConfiguration apiConfig;
public ApiSecurityConfiguration(ApiConfiguration apiConfig) {
this.apiConfig = apiConfig;
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser(apiConfig.getUsername())
.password(passwordEncoder().encode(apiConfig.getPassword()))
.roles(API_USER_ROLE);
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.formLogin().disable()
.mvcMatcher("/api/**")
.authorizeRequests()
.anyRequest().hasRole(API_USER_ROLE)
.and()
.httpBasic()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
}
I ended up creating a CustomApiConfiguration annotated with #TestConfiguration. This seems to do the trick. At least, Spring is able to read the properties when setting up the SecurityConfiguration class.
#TestConfiguration
public class CustomApiConfiguration {
#Bean
public ApiConfiguration apiConfiguration() {
final ApiConfiguration config = new ApiConfiguration();
config.setUsername("api-username");
config.setPassword("api-password");
return config;
}
}

required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found

When launching with mvn spring-boot:run or even with gradle returns that issue.
***************************
APPLICATION FAILED TO START
***************************
Description:
Field userDetailsService in webroot.websrv.auth.config.WebSecurityConfiguration required a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.core.userdetails.UserDetailsService' in your configuration.
BUILD SUCCESSFUL
Total time: 19.013 secs
Here are the main classes, all the requirements looks ok to me, I am using the org.springframework.boot release 1.5.7.RELEASE
package webroot.websrv.auth.config;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(
HttpMethod.GET,
"/",
"/**/*.html",
"/**/*.{png,jpg,jpeg,svg.ico}",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl();
}
}
and:
package webroot.websrv;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class WebcliApplication {
public static void main(String[] args) {
SpringApplication.run(WebcliApplication.class, args);
}
}
Using Maven or Gradle it returns the same issue. All annotations and packages names seems to be as required.
Add a bean for UserDetailsService
#Autowired
private UserDetailsService userDetailsService;
#Bean
public UserDetailsService userDetailsService() {
return super.userDetailsService();
}
I also come accross this error. In my case, I have a class JwtUserDetailsService and I have forget implement UserDetailsService. After adding implements UserDetailsService the error was disappered.
Note that: if you also have own UserDetailsService and you use Munna's answer, than you got error StackoverflowError it mean you also repited my mistake.
In Service class make annotation
#Service
to
#Service("userDetailsService")

How do i get a reference to an AuthenticationManager using a Spring java security configuration?

I’m using Spring 4.1.5.RELEASE and Spring Security 3.2.5.RELEASE. I’m doing all security configuration through Java (as opposed to XML). I’m struggling to get a reference to the AuthenticationManager for use with my custom usernamepassword authentication filter …
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages="com.mainco", excludeFilters=#ComponentScan.Filter(Controller.class))
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String ROLE3 = "ROLE3";
private static final String ROLE2 = "ROLE2";
private static final String ROLE1 = "ROLE1";
#Resource(name="userDetailsService")
private UserDetailsService userDetailsService;
#Resource(name="myAuthenticationSuccessHandler")
private MyAuthenticationSuccessHandler authSuccessHandler;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(authenticationFilter(), MyUsernamePasswordAuthenticationFilter.class);
http
.authorizeRequests()
.antMatchers("/403").permitAll()
.antMatchers("/login/**").permitAll()
.antMatchers("/resources/**").permitAll()
.antMatchers("/ROLE1/**").hasRole(ROLE1)
.antMatchers("/common/**").hasAnyRole(ROLE1, ROLE2, ROLE3)
.antMatchers("/ROLE3/**").hasAnyRole(ROLE1, ROLE2, ROLE3)
.antMatchers("/ROLE2/**").hasAnyRole(ROLE1, ROLE2)
.antMatchers("/*/**").fullyAuthenticated()
.and().formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("username")
.passwordParameter("password")
.successHandler(authSuccessHandler)
.and().logout().logoutSuccessUrl("/login?logout")
.and().exceptionHandling().accessDeniedPage("/403")
.and().csrf().disable();
}
#Bean(name="passwordEncoder")
public PasswordEncoder passwordEncoder() {
return new StandardPasswordEncoder();
}
#Bean
public JSONUsernamePasswordAuthenticationFilter authenticationFilter() {
final MyUsernamePasswordAuthenticationFilter authFilter = new MyUsernamePasswordAuthenticationFilter();
authFilter.setAuthenticationSuccessHandler(authSuccessHandler);
authFilter.setUsernameParameter("username");
authFilter.setPasswordParameter("password");
return authFilter;
}
This configuration fails upon startup with the message
“Caused by: java.lang.IllegalArgumentException: authenticationManager must be specified”.
How do I get a reference to the AuthenticationManager for use with my filter?
You can override authenticationManagerBean() method from WebSecurityConfigurerAdapter to expose AuthenticationManager as a bean like so:
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}

spring circular dependencies

I have the following configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class SecurityConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(SecurityConfig.class);
#Resource
private UserDetailsService userDetailsService;
#Resource
private PasswordEncoder passwordEncoder;
.....
#Configuration
#Order(2)
public static class MobileApiSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Resource
private UserDetailsService userDetailsService;
#Resource
private PasswordEncoder passwordEncoder;
#Autowired
private CustomBasicAuthenticationFilter customBasicAuthenticationFilter;
#Autowired
private TokenSecurityFilter tokenSecurityFilter;
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
protected void configure(HttpSecurity http) throws Exception {
http
.addFilter(customBasicAuthenticationFilter)
.addFilterBefore(tokenSecurityFilter, CustomBasicAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.csrf().disable()
.authorizeRequests()
.antMatchers(Mappings.MOBILE_API + "/**").hasAnyRole(Globals.MOBILE_API_USER_ROLE)
.and()
.exceptionHandling()
.authenticationEntryPoint(new CustomBasicAuthenticationEntryPoint())
.and()
.requestCache()
.requestCache(new NullRequestCache());
}
}
This is my custom filter:
#Component
public class CustomBasicAuthenticationFilter extends BasicAuthenticationFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomBasicAuthenticationFilter.class);
#Autowired
private PrincipalRepository principalRepository;
#Autowired
private AuthenticationCache authenticationCache;
#Autowired
public CustomBasicAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
#Override
protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
Authentication authResult) throws IOException {
Principal principal = principalRepository.findOne(PrincipalPredicates.userNameEquals(authResult.getName()));
if (principal != null) {
principal.setLastLoginTime(DateTime.now());
principalRepository.save(principal);
} else {
LOGGER.error("Unable to retrieve user " + authResult.getName());
}
authenticationCache.add(authResult, request, response);
super.onSuccessfulAuthentication(request, response, authResult);
}
}
But, when trying to deploy to Tomcat, the following exception is thrown:
Error creating bean with name 'customBasicAuthenticationFilter' defined in file [C:\work\...]: Unsatisfied dependency expressed through constructor argument with index 0 of type [org.springframework.security.authentication.AuthenticationManager]: : No qualifying bean of type [org.springframework.security.authentication.AuthenticationManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [org.springframework.security.authentication.AuthenticationManager] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
I am not sure why my filter is looking for an authenticationManager since I am autowiring it. Help is appreciated it.
Updated Question: I added this code to resolve the authenticationManager issue:
#Bean(name="myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
By adding the above, I am able to get past the authenticationManager issue, but now I am getting this issue:
org.springframework.beans.factory.BeanCreationException:
Could not autowire field: private CustomBasicAuthenticationFilter SecurityConfig$MobileApiSecurityConfigurerAdapter.customBasicAuthenticationFilter;
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'customBasicAuthenticationFilter':
Requested bean is currently in creation: Is there an unresolvable circular reference?
Thanks
You can try to add #EnableWebSecurity annotation on top of MobileApiSecurityConfigurerAdapter class definition.
I added the #Lazy annotation to the filter and I'm able to deploy now.
Feel free to offer other solutions.

Resources