How to logging login and logout in Spring Security 4? - spring

At most want to know what is the reason of login failed!
That is the code:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/assets/**",
"/user/register/**",
"/login/**",
"/page-register.html",
"/page-login.html").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.successForwardUrl("/index.html")
.loginProcessingUrl("/login")
.loginPage("/page-login.html")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.csrf().disable();
}

By default your spring Application has something called a ApplicationEventPublisher, and Spring security sends AbstractAuthenticationEvent through this event mechnism, so all you have to do is listen. This is rather simple, you just need a component which implements ApplicationListener, like this:
#Component
public class SecurityEventListener implements ApplicationListener<AbstractAuthenticationEvent> {
#Override
public void onApplicationEvent(AbstractAuthenticationEvent event) {
System.out.println(event);
}
}
If you are using Spring 4.2 or newer, you don't need to implement the interface, just annotate the method with #EventListener. You can read more about listeners here
When login fails, the event will extend AbstractAuthenticationFailureEvent, and contain the exception which was thrown. You can check the events source property to see what the user entered in the login form, and which IP he connected from.

Related

How to make a custom UsernamePasswordAuthenticationFilter register at an endpoint other than /login?

I've been following a tutorial to implementing JWT authentication in Spring Boot but am trying to adapt it to a case where I have two WebSecurityConfigurerAdapter classes, one for my API (/api/** endpoints) and one for my web front-end (all other endpoints). In the tutorial, a JWTAuthenticationFilter is created as a subclass of UsernamePasswordAuthenticationFilter and added to the chain. According to the author, this filter will automatically register itself with the "/login" endpoint, but I want it to point somewhere different, such as "/api/login" because I'm using this authentication method for my API only.
Here's the security configuration code for both the API and front-end (with some abbrevation):
#EnableWebSecurity
public class MultipleSecurityConfigurations {
#Configuration
#Order(1)
public static class APISecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
}
#Configuration
public static class FrontEndSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/?logout")
.and()
.authorizeRequests()
.mvcMatchers("/").permitAll()
.mvcMatchers("/home").authenticated()
.anyRequest().denyAll()
;
}
}
}
The question is: how can I define an endpoint such as "/api/login" as the endpoint for my custom JWTAuthenticationFilter?
Or, do I need to change the filter to not be a subclass of UsernamePasswordAuthenticationFilter and if so, how would I configure that?
EDIT: Something I've tried:
I guessed that the /api/login endpoint needed to be .permitAll() and I tried using formLogin().loginProcessingUrl(), even though it's not really a form login - it's a JSON login. This doesn't work. When i POST to /api/login I end up getting redirected to the HTML login form as if I were not logged in. Moreover, my Spring boot app throws a weird exception:
org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String ";"
The configuration I'm trying now:
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**")
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.formLogin().loginProcessingUrl("/api/login").and()
.authorizeRequests()
.antMatchers("/api/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()));
}
Since JWTAuthenticationFilter is a UsernamePasswordAuthenticationFilter, you could change the login endpoint directly on the filter instance:
JWTAuthenticationFilter customFilter = new JWTAuthenticationFilter(authenticationManager());
customFilter.setFilterProcessesUrl("/api/login");
http.addFilter(customFilter);
This configures JWTAuthenticationFilter to attempt to authenticate POST requests to /api/login.
If you wish also to change the default POST to another method (e.g. GET), you can set the RequiresAuthenticationRequestMatcher instead. For instance:
customFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/login", "GET"));

Providing custom OAuth2AccessTokenResponseClient to spring

I have a demo spring boot app and I would like to configure oauth login as well my own custom token response client.
This is my configuration:
#Configuration
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.and()
.oauth2Client()
.authorizationCodeGrant()
.accessTokenResponseClient(customAccessTokenResponseClient());
}
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> customAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
client.setRequestEntityConverter(new CustomOAuth2AuthorizationCodeGrantRequestEntityConverter());
return client;
}
}
The login flow is working find, but it looks like my custom token response client is not getting registered. I put a break point in there, but the flow doesn't go through it. It looks like it is still using the defaults.
Am I missing something here or is the order wrong?
Figured out the issue.
The right way to configure is the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2Login()
.tokenEndpoint()
.accessTokenResponseClient(customAccessTokenResponseClient());
}

Login page needs to be prompted if user is not authorized to access specific controller or URL in spring security. How to achieve that?

I'm using spring-boot, spring-security and JSP. If I click on a button it should go to a controller if user is logged in. Otherwise, it should first ask user to login and then get back to that page. In short, user should see the page if he is logged in. How can I achieve this?
I think filters/antmatchers might be used but I am wondering how the user will get back to that particular page/controller after logging in?
Try using something like this to allow users access to certain pages and then set the default success url accordingly. You can have a home page as I use here represented by "/" and once a user logs in they are redirected to your /welcome page.
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// Public access to login, landing, and error pages
http.authorizeRequests().antMatchers("/", "/login", "/errorpage").permitAll();
// Static resource permissions
http.authorizeRequests()
.antMatchers("/css/**", "/fonts/**", "/images/**", "/webfonts/**", "/js/**", "/webjars/**", "/messages/**")
.permitAll();
// Login specifications
http.formLogin().loginPage("/login").defaultSuccessUrl("/welcome", true);
// Logout specifications
http
.logout()
.deleteCookies("remove")
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/")
.permitAll();
}
}
Inside WebSecurityConfigurerAdapter implementation, you need to inform a formLogin and specify the loginPage.
That's just enough to Spring to use the endpoint /login this way.
If you try to access a page without logged, for example /profile, you will be redirected to /login, and after logged, you'll be redirected to /profile
And in this example, you have 3 pages accessible without authentication / ,/homeand/info`
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/info" ).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
...
}

Spring Security: Multiple http elements with Multiple AuthenticationManagers

I am struggling with Java Config for Spring Security. I have multiple entry points but I cannot get the AuthenticationManagers provisioned correctly.
My first configuration file is like this:
#Configuration
#EnableWebSecurity
#Order(100)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.antMatcher("/service/**")
.addFilterAfter(requestHeaderAuthenticationFilter(), SecurityContextPersistenceFilter.class)
.authorizeRequests()
.antMatchers("/service/**").authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER)
.and()
.csrf().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.authenticationProvider(preAuthenticatedAuthenticationProvider(null));
}
#Bean
public RequestHeaderAuthenticationFilter requestHeaderAuthenticationFilter() throws Exception
{
// Takes the value of the specified header as the user principal
RequestHeaderAuthenticationFilter filter = new RequestHeaderAuthenticationFilter();
filter.setPrincipalRequestHeader("SECRET_HEADER");
filter.setAuthenticationManager(authenticationManager());
filter.setExceptionIfHeaderMissing(false);
return filter;
}
This all works correctly. When I set a breakpoint in the RequestHeaderAuthenticationFilter I see an AuthenticationManager with one AuthenticationProvider, and that is the preAuthenticatedAuthenticationProvider (not shown because is just a regular old bean).
I also have a special security chain for admin users and the like:
#Configuration
#Order(101)
public class AdminSecurity extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authenticationProvider(mainSiteLoginAuthenticationProvider())
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.antMatchers("/admin/**").access("SECRET ADMIN ACCESS EXPRESSION")
.antMatchers("/internal/**").access("SECRET INTERNAL ACCESS EXPRESSION")
.anyRequest().permitAll()
.and()
.formLogin()
.defaultSuccessUrl("/admin/thing")
.loginPage("/login")
.loginProcessingUrl("/do_login")
.defaultSuccessUrl("/admin/thing")
.failureUrl("/login?error=true")
.usernameParameter("username")
.passwordParameter("password")
.and()
.logout()
.and()
.exceptionHandling()
//.authenticationEntryPoint(null) // entry-point-ref="loginEntryPoint"
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // create-session="ifRequired"
.and()
.csrf().disable();
}
This is now working (after a lot of struggle), but if I put a breakpoint in the UsernamePasswordAuthenticationFilter, I see that this filter has a different AuthenticationManager instance, which is provisioned with the mainSiteLoginAuthenticationProvider as expected. However, it has a parent AuthenticationManager which is provisioned with the default DaoAuthenticationProvider that generates a temporary password in the logs:
Using default security password: 47032daf-813e-4da1-a224-b6014a705805
So my questions are:
How can I get both security configs to use the same AuthenticationManager? I thought that the SecurityConfig, being order 100, would create one, and then AdminConfig, being 101, would just use it. But I have been unable to get them to use the same AuthenticationManager.
Failing that, how can I prevent the AuthenticationManger of AdminConfig from generating a parent that has the default DaoAuthenticationProvider?
I am using Spring Boot 1.5.9.RELEASE, which means Spring Security 4.2.3.RELEASE.

Why does HttpSecurity configuration via DSL not seem to work the same as explicit configuration?

I went through the trouble to write a DSL to configure the HttpSecurity for my custom authentication mechanism, but most of the configuration I apply to it doesn't seem to be in effect when the application runs, while everything works perfectly when I configure it all manually in the webapp.
First, the manual configuration, which results in my EntryPoint firing, authenticationProvider being queried, the filter being added to the chain, and my rememberMeServices being added to that filter. Everything correct.
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/auth/callback").permitAll()
.anyRequest().authenticated()
.and()
.authenticationProvider(authProvider)
.rememberMe()
.rememberMeServices(rememberMeServices)
.and()
.exceptionHandling()
.authenticationEntryPoint(entryPoint)
.and()
.addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
/* The following code is basically what gets run when the DSL is in use
http
.apply(new EPIdentityDsl())
// lots of setters called here, removed for clarity
.and()
.authorizeRequests().anyRequest().authenticated();
*/
}
}
However, the code in the DSL looks like this, and when it is used, the authenticationEntryPoint never fires. The rememberMeServices do get configured, and it looks like the filter gets added to the chain correctly, but I just get an error page for a 403 response instead of seeing the entryPoint redirection.
public class EPIdentityDsl extends AbstractHttpConfigurer<EPIdentityDsl, HttpSecurity> {
#Override
public void init(HttpSecurity http) throws Exception {
// any method that adds/removes another configurer
// must be done in the init method
log.debug("dsl init");
http
.exceptionHandling()
.and()
.rememberMe();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(filterProcessesUrl).permitAll()
.and()
.authenticationProvider(authProvider)
.exceptionHandling()
.authenticationEntryPoint(entryPoint)
.and()
.rememberMe()
.rememberMeServices(rememberMeServices)
.and()
.addFilterAfter(filter, UsernamePasswordAuthenticationFilter.class);
}
}
Clearly, there's some subtle interaction that I'm missing in the documentation or something, causing my DSL-based configuration of entryPoint to get lost. Any idea why? If I had to guess, it would be that I'm doing something wrong with the way I'm specifying paths, but I can't figure it out.
I had a similar problem. I solved it by moving entryPoint to init

Resources