Spring security redirect to login and restore form data previously entered - spring

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>

Related

How do I redirect to a specific uri after Google oauth using Spring Boot

I'm implementing a server using Spring Boot. After the user do an oauth login, I want the user to go redirect to a specific uri so I can let the user register or login. The Google OAuth login seems like it is working fine but it keeps going to "/" uri. I want to user to be redirected to "/api/v1/member/oauth"
This is my Spring Security setup.
...
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs")
.permitAll()
.anyRequest()
.permitAll()
.and()
.oauth2Login()
.defaultSuccessUrl("/api/v1/member/oauth")
.userInfoEndpoint()
.userService(customOAuth2MemberService);
}
...
This is the OAuth service that a user is directed to. (This works fine)
#Service
#RequiredArgsConstructor
public class CustomOAuth2MemberService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User;
try {
oAuth2User = delegate.loadUser(userRequest);
} catch (OAuth2AuthenticationException e) {
throw new CustomException(OAUTH_FAIL);
}
return new DefaultOAuth2User(oAuth2User.getAuthorities(), oAuth2User.getAttributes(), "sub");
}
}
I want to get the DefaultOAuth2User which is returned from the above to this uri.
#PostMapping("/api/v1/member/oauth")
public Object registerOrLogin(DefaultOAuth2User defaultOAuth2user) {
return ResponseEntity.status(200)
.body(DefaultResponseDto.builder()
.responseCode("MEMBER_LOGIN")
.build());
}
It currently is not going to this uri and is redirected to "/".
NEW: I redirected it by having .defaultSuccessUrl() but now the DefaultOAuth2User is not sent with the redirection, causing the parameter of redirected api to be null. How do I fix this problem?
Try to use
.oauth2Login()
.defaultSuccessUrl("/api/v1/member/oauth")
this should override post-authentication behavior and redirect to the desired page after successful login. Also, there is a similar method for setting redirection URL for failed authentication .failureUrl("url").
Spring-Security AbstractAuthenticationProcessingFilter class has successfulAuthentication() methos, which defines what happens when a User is successfully authenticated. You can register your success handler and put your redirect logic there.
But here is a catch, when using OAuth2.0, we need to specify redirect-uri to which user will be landed after client receives an access-token.
If you are okay with this Oauth's redirect-uri, do not alter the redirect in success handler or if you need to redirect irrespective of that, use response.sendRedirect("/social-login-sample/some-page");
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.cors()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs")
.permitAll()
.anyRequest()
.permitAll()
.and()
.oauth2Login()
.userInfoEndpoint()
.userService(customOAuth2MemberService)
.and()
.successHandler(
new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException, ServletException {
// authentication.getName() : Principal Name
CustomOAuth2User oauthUser = (CustomOAuth2User) authentication.getPrincipal();
// Check if user is registered in your Database, if not, register new user
//userService.processAuthenticatedUser(oauthUser.getEmail());
// Get actual redirect-uri set in OAuth-Provider(Google, Facebook)
String redirectUri =
UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replaceQuery(null)
.build()
.toUriString();
log.info("redirectUri: {}", redirectUri);
// Ignore redirect-uri, and send user to a different page instead...
// response.sendRedirect("/social-login-sample/some-ther-page");
}
})
}

Spring security makes a session when a user access the log in page

I'm making log in page, following official guide.
I want to add a function which makes redirect to home if a user join.
The logic is like this.
A user join with email, password and username.
Joinpage redirect to log in page.
if there is authentication, directly go to home.
How can I redirect to home with authentication?
#Slf4j
#Controller
#RequiredArgsConstructor
public class LoginController {
#GetMapping("/login")
public String loginForm(#ModelAttribute LoginForm loginForm) {
return "login/loginForm";
}
}
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserSecurityService userSecurityService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 인가 요청 받기
.antMatchers("/", "/home", "/join",
"/css/**", "/*.ico", "/error").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.failureUrl("/login")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true);
}
basically, the security generates user session along the SessionCreationPolicy value when the user logins successfully.
if you want to change the session system to stateless system.
but following the below code.
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
and then, you need to set the authentication object.
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken("principal", "credentials", null));

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/**"))

Spring oauth2 SimpleUrlAuthenticationFailureHandler two redirects on failure

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

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);
}

Resources