I'm struggling with the problem from the title for few days already and I'm pretty frustrated. I have no idea what I'm doing wrong and why my implementation isn't working.
Let me show you what I've got:
Custom AuthenticationProvider:
#Component
public class AuthProvider implements AuthenticationProvider {
private Logger logger = LoggerFactory.getLogger(AuthProvider.class);
public AuthProvider() {
logger.info("Building...");
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
logger.info("Authenticate...");
return null;
}
public boolean supports(Class<?> authentication) {
logger.info("Supports...");
return true;
}
}
WebSecurity config:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthProvider authProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated();
}
}
As you can see I've added loggers into the AuthenticationProvider but not any of them is getting called.
What I've tried:
adding #Autowired to configure where the AuthenticationManagerBuilder is
adding #EnableGlobalMethodSecurity(prePostEnabled=true) to the class
adding custom AuthenticationProvider directly to HttpSecurity
How I've tested it:
debugging via IntelliJ - no results, no breakpoint is getting called.
running the app and sending a request - also no results, no logs, nothing.
Please guys help me somehow. I'm outta energy. I hate wasting so much time on things that should just work :(
Probably you missed the following method in your WebSecurityConfigurerAdapter:
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
The same happened to me.
Using the isAssignableFrom() method instead of instead of == or equals we get a true, then the flow would pass through authenticate()
override fun supports(authentication: Class<*>): Boolean {
return UsernamePasswordAuthenticationToken::class.java.isAssignableFrom(authentication)
}
GL
Source
Related
I'm fairly new at the whole Spring Security thing, and I'm trying to set up a website that uses pre-authentication. I have followed some examples and tutorials and I think it's going well. But i have stucked on one thing.
When i navigate through the .jsp pages everything works fine. I can access the "public" pages, and i get "Access Denied" when i try to access the "private" pages (I haven't finished the filter for private pages).
But if I check the logs when navigating I get the same error everytime accessing a page:
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken
The thing is, I have a Provider for this, or at least I think so.
SecurityConfig.java:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean(name = "myAuthenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
#Bean
public AccessDecisionManager accessDecisionManager()
{
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(new RoleVoter());
return new AffirmativeBased(decisionVoters);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
DlaUserDetailsService userDetailsService = new DlaUserDetailsService();
provider.setPreAuthenticatedUserDetailsService(userDetailsService);
auth.authenticationProvider(provider);
}
#Override
public void configure(HttpSecurity http) throws Exception
{
DlaSpringMvcFilter filter = new DlaSpringMvcFilter();
AuthenticationManager authenticationManager = this.authenticationManager();
filter.setAuthenticationManager(authenticationManager);
http.addFilter(filter).authorizeRequests()
.antMatchers("/private/**").hasRole("ADMIN")
.antMatchers("/public/**").permitAll();
http.csrf().disable();
}
}
What have I done wrong here? Am I missing something?
I'm looking into ways to customize Spring Boot's default .loginForm(...) action. Here's my -simplified- config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/account/login/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().loginPage("/account/login").successHandler(new WebAuthenticationSuccessHandler())
.and().logout().logoutRequestMatcher(new AntPathRequestMatcher("/account/logout")).deleteCookies("remember-me")
.and().requestCache().requestCache(new NullRequestCache())
.and().exceptionHandling().authenticationEntryPoint(new SomeEntryPoint("/account/login"))
.and().rememberMe().tokenRepository(...)
.and().csrf().csrfTokenRepository(...)
.and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
basically, I am happy with this configuration and posting to /account/login but I would like to add yet another constraint (e.g. check if IP of the user is in the database as authorized IP address) to the user verification step.
I was wondering where would be the correct place to put that piece of code, UserDetailsManager or UserDetailsService etc. seemed quite wrong, as I am looking to customize only the login action. Thanks!
Here's the answer that I could come up with:
public class IpAuthenticationProvider implements AuthenticationProvider {
private AuthenticationProvider delegate;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String address = ((WebAuthenticationDetails) authentication.getDetails()).getRemoteAddress();
// Throw an AuthenticationException if there's a bad IP here
return delegate.authenticate(authentication);
}
#Override
public boolean supports(Class<?> aClass) {
return delegate supports(aClass);
}
public void setDelegate(AuthenticationProvider delegate) {
this.delegate = delegate;
}
}
to configure it:
#Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
IpAuthenticationProvider provider = new IpAuthenticationProvider();
provider.setDelegate(authenticationProvider())
auth.authenticationProvider(provider);
}
#Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsServiceBean());
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
}
Reference, it was all there from the beginning.
I am having a problem similar to PreAuthorize annotation doesn't work with jersey. I created a configuration class for Spring Security and the authentication works but the authorization does not.
Here is my code
SpringSecurityConfig.java
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Order(1)
#ComponentScan({"com.foo.rest.resources.Template"})
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserService userService;
private final TokenAuthenticationService tokenAuthenticationService;
public SpringSecurityConfig() {
super(true);
this.userService = new UserService();
tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", userService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().and()
.anonymous().and()
.servletApi().and()
.headers().cacheControl().and()
.authorizeRequests()
// Allow anonymous logins
.antMatchers("/auth/**").permitAll()
// All other request need to be authenticated
.anyRequest().authenticated().and()
// Custom Token based authentication based on the header previously given to the client
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
#Override
public UserService userDetailsService() {
return userService;
}
#Bean
public TokenAuthenticationService tokenAuthenticationService() {
return tokenAuthenticationService;
}
}
and Template.java
#Component
#Path("/template")
#Produces(MediaType.APPLICATION_JSON)
public class Template {
#GET
#Secured("ROLE_EDITOR")
public User getTemplate() {
return new Template();
}
}
My guess is that the authentication is handled in the filter chain but it never comes back around after the authorization tag is reached. Any idea how to make this work?
I think your #ComponentScan is configured wrongly and doesn't pick the Template resource correctly.
According to #ComponentScan documentation the value is an alias for basePackages but you have given a Class instead of Package. Try and change it to look like following and see.
#ComponentScan({"com.foo.rest.resources.*"})
And make sure you haven't missed any steps in Jersey Spring Integration as per the documentation
I am trying to add user ip verification during login process. If ip address of the user is not in the database the application should reject the authentication.
The problem: Given the setup below it turns out that auth.authenticationProvider() is not replacing the default DaoAuthenticationProvider, but adds UserIpAuthenticationProvider as a first AuthenticationProvider in the list.
In the case when username/password combination is incorrect the framework ends up calling UserDetailsService.loadUserByUsername() twice, once from UserIpAuthenticationProvider, another time from internal DaoAuthenticationProvider which throws the final BadCredentialsException().
The question: is there any setting that can be set in Spring Boot so that Spring Security does not add it's own internal instance DaoAuthenticationProvider, but only use my UserIpAuthenticationProvider, which already has all the necessary functionality (perhaps by somehow replacing AuthenticationManagerBuilder to be able to override userDetailsService() method?).
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<AuthenticationManagerBuilder,T>(userDetailsService));
}
Configuration: In my understanding, UserDetailsService is supposed to provide all the necessary details about the user so that AuthenticationProvider can make a decision whether the authentication was successful or not.
Since all the necessary information is loaded from the database, it seems natural to extend DaoAuthenticationProvider and add an additional verification in overriden additionalAuthenticationChecks() method (white-listed IP list is in the database, so they are loaded as part of the user object in IpAwareUser).
#Named
#Component
class UserIpAuthenticationProvider extends DaoAuthenticationProvider {
#Inject
public UserIpAuthenticationProvider(UserDetailsService userDetailsService)
{
...
}
#SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
super.additionalAuthenticationChecks(userDetails, authentication);
WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
IpAwareUser ipAwareUser = (IpAwareUser) userDetails;
if (!ipAwareUser.isAllowedIp(details.getRemoteAddress()))
{
throw new DisabledException("Login restricted from ip: " + details.getRemoteAddress());
}
}
}
This is injected into SecurityConfiguration:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilter(authenticationFilter);
http.authorizeRequests()
.antMatchers("/", "/javascript/**", "/css/**").permitAll()
.antMatchers("...").access("...")
.anyRequest().authenticated()
.and().formLogin().loginPage("/").permitAll()
.and().logout().invalidateHttpSession(true).deleteCookies("JSESSIONID").permitAll()
.and().csrf().disable()
;
}
#Inject
private UserDetailsService userDetailsService;
#Inject
private UserIpAuthenticationProvider userIpAuthenticationProvider;
#Inject
private JsonUsernamePasswordAuthenticationFilter authenticationFilter;
#Bean
public JsonUsernamePasswordAuthenticationFilter authenticationFilter() {
return new JsonUsernamePasswordAuthenticationFilter();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(userIpAuthenticationProvider);
auth.userDetailsService(userDetailsService);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() throws Exception {
return new JsonAuthenticationSuccessHandler();
}
#Bean
public AuthenticationFailureHandler authenticationFailureHandler() throws Exception {
return new JsonAuthenticationFailureHandler();
}
}
and application configuration:
#Configuration
#EnableAutoConfiguration
#ComponentScan(basePackageClasses = {SecurityConfiguration.class, DataController.class, DaoService.class})
public class Application extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application;
}
}
Any guidance on this will be much appreciated.
The comments on the question contained the answer:
#ArunM: your project gave me an idea: I do not need to call auth.userDetailsService(userDetailsService); in SecurityConfiguration.configure(), which will prevent creation of internal DaoAuthenticationProvider! My UserIpAuthenticationProvider can get instance of UserDetailsService via injection.
The AuthenticationManagerBuilder.userDetailsService method does not only set the default UserDetailsService but also applies a DaoAuthenticationConfigurer which registers the DaoAuthenticationProvider.
If you want a customized DaoAuthenticationProvider, pass the UserDetailsService to the provider in the constructor or inject it. And to prevent the default DaoAuthenticationProvider from being registered, don't call AuthenticationManagerBuilder.userDetailsService.
This is also mentioned in this Spring Security issue.
Defining your own DaoAuthenticationProvider
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
return new UserIpAuthenticationProvider();
}
should replace the Spring Boot default instance (not that the bean type is DaoAuthenticationProvider and not UserIpAuthenticationProvider)
I am having some issues getting my application set up using method level annotation controlled by #EnableGlobalMethodSecurity I am using Servlet 3.0 style initialization using
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(MultiSecurityConfig.class);
}
}
I have attempted 2 different ways of initialising an AuthenticationManager both with their own issues. Please note that not using #EnableGlobalMethodSecurity results in a successful server start up and all of the form security executes as expected. My issues arise when I add #EnableGlobalMethodSecurity and #PreAuthorize("hasRole('ROLE_USER')") annotations on my controller.
I am attempting to set up form-based and api-based security independently. The method based annotations need only work for the api security.
One configuration was the following.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").httpBasic();
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and()
.formLogin().loginPage("/login").permitAll();
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
}
This is not ideal as I really want only a single registration of the authentication mechanism but the main issue is that it results in the following exception:
java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found []
As far as I am aware #EnableGlobalMethodSecurity sets up its own AuthenticationManager so I'm not sure what the problem is here.
The second configuration is as follows.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR)
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN").and()
.and()
.build();
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").httpBasic();
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and()
.formLogin().loginPage("/login").permitAll();
}
}
}
This config actually starts successfully but with an exception
java.lang.IllegalArgumentException: A parent AuthenticationManager or a list of AuthenticationProviders is required
at org.springframework.security.authentication.ProviderManager.checkState(ProviderManager.java:117)
at org.springframework.security.authentication.ProviderManager.<init>(ProviderManager.java:106)
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.performBuild(AuthenticationManagerBuilder.java:221)
and when I test I found that the security doesn't work.
I've been looking at this for a couple of days now and even after diving into spring security implementation code I can't seem to find what is wrong with my configuration.
I am using spring-security-3.2.0.RC1 and spring-framework-3.2.3.RELEASE.
When you use the protected registerAuthentication methods on WebSecurityConfigurerAdapter it is scoping the Authentication to that WebSecurityConfigurerAdapter so EnableGlobalMethodSecurity cannot find it. If you think about this...it makes sense since the method is protected.
The error you are seeing is actually a debug statement (note the level is DEBUG). The reason is that Spring Security will try a few different ways to automatically wire the Global Method Security. Specifically EnableGlobalMethodSecurity will try the following ways to try and get the AuthenticationManager:
If you extend GlobalMethodSecurityConfiguration and override the registerAuthentication it will use the AuthenticationManagerBuilder that was passed in. This allows for isolating the AuthenticationManager in the same way you can do so with WebSecurityConfigurerAdapter
Try to build from the global shared instance of AuthenticationManagerBuilder, if it fails it logs the error message you are seeing (Note the logs also state "This is ok for now, we will try using an AuthenticationManager directly")
Try to use an AuthenticationManager that is exposed as a bean.
For your code, you are going to be best off using something like the following:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
// Since MultiSecurityConfig does not extend GlobalMethodSecurityConfiguration and
// define an AuthenticationManager, it will try using the globally defined
// AuthenticationManagerBuilder to create one
// The #Enable*Security annotations create a global AuthenticationManagerBuilder
// that can optionally be used for creating an AuthenticationManager that is shared
// The key to using it is to use the #Autowired annotation
#Autowired
public void registerSharedAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
// Since we didn't specify an AuthenticationManager for this class,
// the global instance is used
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.httpBasic();
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
// Since we didn't specify an AuthenticationManager for this class,
// the global instance is used
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
}
NOTE: More documentation around this will be getting added to the reference in the coming days.