Spring oauth2 SimpleUrlAuthenticationFailureHandler two redirects on failure - spring

For some reason spring with oauth2 and custom SimpleUrlAuthenticationFailureHandler redirects twice on login failure.
It calls http://localhost:8081/login?error as expected but with a location header http://localhost:8081/login therefore I see an extra redirect.
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
http.requestMatchers()
.antMatchers("/login", "/oauth/authorize")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/" + URLConstants.LOGIN)
.loginProcessingUrl("/" + URLConstants.LOGIN)
//.failureUrl("/login?error")
.failureHandler(authFailureHandler)
.permitAll();
And SimpleUrlAuthenticationFailureHandler
#Component
public class AuthFailureHandler extends
SimpleUrlAuthenticationFailureHandler {
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
// some code
saveException(request, exception);
getRedirectStrategy().sendRedirect(request, response, "/login?error");
Above code onAuthenticationFailure pretty much calls same methods as original code in onAuthenticationFailure. I've also tried
super.onAuthenticationFailure(request, response, exception);
But I do get same results (an extra redirect)
If I remove .failureHandler(authFailureHandler) then the code works as expected. Any ideas?
See images attached
Extra redirect - with SimpleUrlAuthenticationFailureHandler
As expected - without SimpleUrlAuthenticationFailureHandler

Related

Spring Security: oauth2Login redirect only on certain paths

I have Spring Security configured to authenticate my website, such that all paths are automatically redirected to the OAuth2 authorization URL (using .oauth2Login()). However, I want unauthenticated requests to the API (i.e. /api/**) to return 401 Unauthorized instead of being redirected. I can't figure out how to do this. Any help would be much appreciated.
Here is my current configuration:
http
.authorizeRequests()
.antMatchers("/api/auth/oauth2/callback").permitAll()
.anyRequest().authenticated()
.oauth2Login()
.authorizationEndpoint()
.baseUri(this.oauth2AuthorizationRedirectBaseUri);
http.logout()
.logoutUrl("/auth/logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
You can define a custom authentication entry point for /API/** and add t to your configuration:
#Component
public class CustomAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(
HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
#Override
public void afterPropertiesSet() throws Exception {
setRealmName("developers");
super.afterPropertiesSet();
}
}
in your Http security configs add:
http.
...
.exceptionHandling()
.defaultAuthenticationEntryPointFor(
new CustomAuthenticationEntryPoint(),
new AntPathRequestMatcher("/api/**"))

Get request is not authorized when CSRF is disabled in Spring Security

Spring Security basic authentication works if I don't add the following
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
After I add this code piece a GET request without an Authorization header gets the response while I expect a response saying Unauthorized. Before adding this configuration GET response gets 401.
The only change is the above class; nothing else was changed.
Try to add .anyRequest().authenticated(), it means, all request must be authenticated. If you want to add an exemption add something like .antMatchers(HttpMethod.GET, "/", "/js/**", "/css/*", "/images/**").permitAll() this will not be authenticated.
Sample Code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.GET, "/", "/js/**", "/css/*", "/images/**").permitAll()
.anyRequest().authenticated();
}
}

Problems using Spring login in REST with CORS

I am trying to implement a website using REST. My strategy to authenticate the users consist of sending a JWT token to the user in reply to a username/password combination sent via POST. The most relevant part of my security conf is shown below.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/images/**", "/scripts/**", "/styles/**", "favicon.ico");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling()
.authenticationEntryPoint(new RESTAuthenticationEntryPoint()).and()
.formLogin()
.successHandler(authenticationSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.loginProcessingUrl("/login") //Not necesary because is the default
.permitAll().and()
.authorizeRequests()
.antMatchers("/api/getStatistics").permitAll()
.anyRequest().denyAll().and()
.addFilterBefore(new JwtTokenAuthenticationFilter(jWTTokenService()), UsernamePasswordAuthenticationFilter.class);
}
#Bean
public SavedRequestAwareAuthenticationSuccessHandler authenticationSuccessHandler() {
return new RESTAuthenticationSuccessHandler(jWTTokenService());
}
#Bean
public JWTTokenService jWTTokenService() {
return new JWTTokenServiceImpl();
}
To allow the CORS access I have written the following lines in a class extending of WebMvcConfigurerAdapter
#Override
public void addCorsMappings(CorsRegistry registry){
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedHeaders("Origin", "X-Requested-With", "Content-Type", "Accept")
.allowedMethods("GET", "POST", "OPTIONS")
.allowCredentials(true).maxAge(3600);
registry.addMapping("/login")
.allowedOrigins("*")
.allowedHeaders("Origin", "X-Requested-With", "Content-Type", "Accept")
.allowedMethods("POST", "OPTIONS")
.allowCredentials(true).maxAge(3600);
}
So when I make a call to /login sending the username and password it is supposed that Spring will catch the request, will process it and then will redirect to the success or failure handler.
Well, instead of that I have gotten an 403 Forbidden response during the CORS preflight. I decide to debug the program because I thought that when I wrote formLogin(), the UsernamePasswordAuthenticationFilter create a new AntPathRequestMatcher with the value ("/login", "POST").
What I found in the debug console was the following
Request 'OPTIONS /login' doesn't match 'POST /login
Of course it does not! Some hours later trying to solve the problem I discovered that everything works if I declare a empty method /login because during the preflight Spring finds the method and then send a 200OK to the client so the client then is allowed to send a POST that is captured by the UsernamePasswordAuthenticationFilter.
#Controller
public class LoginController {
#RequestMapping(value = { "/login" }, method = RequestMethod.POST)
public void dummyLogin() {
}
}
So, my question is: Should I really declare an empty method to "cheat" during the CORS preflight or it is just that I have missed something? Because it is not so elegant to declare a dummy method when you really want to delegate the job to the UsernamePasswordAuthenticationFilter...
The problem is that org.springframework.security.web.authentication.logout.LogoutFilter and org.springframework.security.web.authenticationUsernamePasswordAuthenticationFilter do not continue with the filter chain if they handled a login/logout. And since the configuration via WebSecurityConfigurerAdapter is processed later in the chain, the CorsProcessor is never applied.
I decided to keep the old solution and use a org.springframework.web.filter.CorsFilter.
It is not necessary to have empty method to make it work. The only thing you have to do is to allow OPTIONS call on the /login URL.
.antMatchers(HttpMethod.OPTIONS, "/login").permitAll()
Ex :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling()
.authenticationEntryPoint(new RESTAuthenticationEntryPoint()).and()
.formLogin()
.successHandler(authenticationSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.loginProcessingUrl("/login") //Not necesary because is the default
.permitAll().and()
.authorizeRequests()
.antMatchers("/api/getStatistics").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/login").permitAll()
.anyRequest().denyAll().and()
.addFilterBefore(new JwtTokenAuthenticationFilter(jWTTokenService()), UsernamePasswordAuthenticationFilter.class);
}

Spring security redirect to login and restore form data previously entered

Overview
I have Spring Web-Application secured with Spring Security
On the site there is a form to input some data, this form is public, but the data will only be processed for authenticated users
If the user press the submit button and is not yet logged in, he will be delegated to the login page. Was the login successfull the user will be redirected to a site where the result of the data processing is visible
Problem
In standard configuration all the data which has been setup by the user are lost after the login process. As I understand it its because a new HttpRequest is created for the redirect after the login.
Solution
I have to write a custom LoginUrlAuthenticationEntryPoint which stores the form data in the session
I have to write a custom SavedRequestAwareAuthenticationSuccessHandler which reads the date from the session an add them as parameters to the url
WebApp Configuration
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private SecurityProperties security;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin")
.password("admin")
.roles("ADMIN", "USER")
.and()
.withUser("user")
.password("user")
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/inputForm")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.successHandler(new SavedRequestAwareAuthenticationSuccessHandlerCustom())
.and()
.csrf()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPointCustom("/login"));
}
}
Custom SuccessHandler
public class SavedRequestAwareAuthenticationSuccessHandlerCustom extends SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String text = (String) request.getSession().getAttribute("text");
if (text != null) {
request.getSession().removeAttribute("text");
setDefaultTargetUrl("/user/dashboard/?text=" + text);
}
super.onAuthenticationSuccess(request, response, authentication);
}
}
Custom EntryPoint
public class LoginUrlAuthenticationEntryPointCustom extends LoginUrlAuthenticationEntryPoint {
public LoginUrlAuthenticationEntryPointCustom(String loginFormUrl) {
super(loginFormUrl);
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
throws IOException,
ServletException {
String text = request.getParameter("text");
request.getSession().setAttribute("text", text);
super.commence(request, response, authException);
}
}
What would you say, is this a valid way to restore the form data, are the better/other solutions, maybe a standard way in spring?
Update
It seem's that something is still wrong with my configuration, cause as seen in the debug message, the request ist not saved by the "HttpSessionRequestCache". If I get this working I don't have to work around with custom implementations.
o.s.s.w.util.matcher.AndRequestMatcher : Trying to match using Ant [pattern='/**', GET]
o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'POST /user/dashboard' doesn't match 'GET /**
o.s.s.w.util.matcher.AndRequestMatcher : Did not match
o.s.s.w.s.HttpSessionRequestCache : Request not saved as configured RequestMatcher did not match
kindly make sure that the form method is post
like this
<form th:action="#{/login}" method="post">
<!-- form input -- >
</form>

Spring Boot redirect to current page after successful login

I have login forms in modal windows. After successful login user is redirected to / page. I am trying to find a method to stay on contact page or another page after login. How to do this? My code is:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**","/js/**","/fonts/**","/images/**","/home","/","/kontakt").permitAll()
.antMatchers("/userlist").hasRole("ADMIN")
.anyRequest().authenticated();
http
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/");
}
You could use custom AuthenticationSuccessHandler and set useReferer to true.
#Bean
public AuthenticationSuccessHandler successHandler() {
SimpleUrlAuthenticationSuccessHandler handler = new SimpleUrlAuthenticationSuccessHandler();
handler.setUseReferer(true);
return handler;
}
And in your configure method:
http
.formLogin()
.loginPage("/login")
.successHandler(successHandler())
.permitAll()
.and()
Just to provide an alternative solution:
formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
defaultSuccessUrl is a shortcut to adding the custom SuccessHandler.
I had a weird issue that would cause on login to redirect the user to localhost:8080/js/bootstrap.min.js
If anyone else is experiencing an odd redirection on login, which seems to override the .defaultSuccessUrl(), then try adding this code below in SecurityConfig:
#Override
public void configure(WebSecurity security){
security.ignoring().antMatchers("/css/**","/images/**","/js/**");
}
Add all your Resources/static folders to the antMatchers()
You can as well do it in your AuthenticationSuccessHandler implementation:
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) throws
IOException, ServletException
{
//Do your logic;
response.sendRedirect(request.getHeader("referer");
}
Config is same as the accepted answer,
only luck I had was with extending SavedRequestAwareAuthenticationSuccessHandler.
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
System.out.println("HEP HEP");
super.onAuthenticationSuccess(request, response, authentication);
}
}
I had absolutely the same issue with inadequate redirect after adding bootstrap to my project tree.
Method .defaultSuccessUrl with flag = true saved me time and lines of code.
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/", true)
.permitAll()
The Spring route, ala extending SavedRequestAwareAuthenticationSuccessHandler or SimpleUrlAuthenticationSuccessHandler can be a bit clunky to implement. In the controller (ex. one that processes logins), you can do the header request yourself; ex:
HttpServletRequest request =null;
String priorUrl = request.getHeader("Referer");
You will notice that you will have the URL prior to either a manual (initiated by user) logout or a session timeout (as handled by Spring session): you'll get an https://iAmPriorUrl.com/.... Then you can do whatever you want with it.
#Jonas All you need to do is add .requestCache() at the end
you config will look like this
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll()
.and()
.requestCache()

Resources