Spring Security - Form Login

Configure Spring Security using HttpSecurity

In Spring Security’s WebSecurityConfigurerAdapter class, you can customize form login in .config(HttpSecurity) method

Form Login Configuration Example

Here is an example configuration for Form Login.

SecurityConfiguration.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/home")
.failureUrl("/login.html?error=true")
.permitAll()
.and().logout();
}

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder().encode("pass"))
.roles("USER");
}
}

LoginController.java

1
2
3
4
5
6
7
@Controller
public class LoginController {
@GetMapping("/auth/login")
public String login(Principal principal) {
return "login";
}
}

This controller simply returns login page.

Note that you need to add Thymeleaf Dependency so that you can use thymeleaf template. For Gradle, the dependency will be

1
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

classpath:templates/login.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security/">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Login</title>
</head>
<body>
<form th:action="@{/login}" method="POST">
<div>
<label for="username">Username</label>
<input type="text" name="username" id="username">
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password">
</div>
<div>
<button type="submit">Login</button>
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
</div>
</form>
</body>
</html>

This is a very simple Login page with CSRF support. This page is using Thymeleaf template engine so taht csrf token can be sent to client.

About HttpSecurity Object

  • Use java doc to get more details on HttpSecurity usage.
  • HttpSecurity can configure authorization using antMatchers and set the login method(form, basic, openID, oauth2, saml) base on the request
  • you can also config login process, logout process, saml2 login, oauth2 login, session management, csrf, configure filter etc

You can tell Spring Security to use form login authentication by calling HttpSecurity.formLogin() method. HttpSecurity.formLogin() method returns FormLoginConfigurer.

FormLoginConfigurer

FormLoginConfigurer common methods

  • loginPage(String)
  • loginProcessingUrl(String)
  • defaultSuccessUrl(String, boolean)
  • defaultForwardUrl(String)
  • successHandler(AuthenticationSuccessHandler)
  • failureUrl(String)
  • failureForwardUrl(String)
  • failureHandler(AuthenticationFailureHandler)
  • usernameParameter(String)
  • passwordParameter(String)
  • permitAll()

loginPage and loginProcessingUrl

By default login page and login processing url are both /login. We can customize the login page and login processing url.

Here we use /login.html as login page and use the default login processing url.

1
2
.loginPage("/auth/login")
.loginProcessingUrl("/login")

defaultSuccessUrl and successForwardUrl

defaultSuccessUrl method sets the default url to be redirect to. defaultSuccessUrl method accespts second parameter alwaysUse. if alwaysUse is set to true, then logged in user will always be redirected to successUrl. This has the same effect as successForwardUrl() method.

1
2
3
4
5
// just set a defaultSuccessUrl
.defaultSuccessUrl("/loginSuccess")

// always redirect to defaultSuccessUrl, same as successForwardUrl("/loginSuccess")
.defaultSuccessUrl("/loginSuccess", true)

failureUrl() and failureForwardUrl() method are similar. They are used to redirect page when login fails.

usernameParameter and passwordParameter

The default username parameter is “username”. The default password parameter is “password”. You can customize both

1
2
.usernameParameter("username") // default is username
.passwordParameter("password") // default is password

permitAll

Ensures the urls for failureUrl(String) as well as for the HttpSecurityBuilder, the getLoginPage() and getLoginProcessingUrl() are granted access to any user.

1
permitAll()

Redirect to Different page after Successful login

In some cases, you want to redirect to different page base on the user login. If the user has role ‘ROLE_ADMIN’, redirect to ‘/admin’ page, else redirect to ‘/‘.

You can’t do this with defaultSuccessUrl method above, you need to create your own AuthenticationSuccessHandler. AuthenticationSuccessHandler is used after user successfully login.

You can follow the example of SimpleUrlAuthenticationSuccessHandler and create your own AuthenticationSuccessHandler.

CustomAuthenticationSuccessHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.example;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler
{
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

public CustomAuthenticationSuccessHandler() {
}

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}

protected void handle(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
String targetUrl = determineTargetUrl(request, response, authentication);
redirectStrategy.sendRedirect(request, response, targetUrl);
}

protected String determineTargetUrl(HttpServletRequest request,
HttpServletResponse response, Authentication authentication) {
if( authentication.getAuthorities().stream().map(r -> r.toString()).anyMatch(r -> r.equals("ROLE_ADMIN"))){
return "/admin";
} else {
return "/";
}
}

/**
* Removes temporary authentication-related data which may have been stored in the
* session during the authentication process.
*/
protected final void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);

if (session == null) {
return;
}

session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}

To use the CustomAuthenticationSuccessHandler, create a bean and config using .successHandler(customAuthenticationSuccessHandler()) method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

// ...
@Bean
public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
return new CustomAuthenticationSuccessHandler();
}


@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/login")
.loginProcessingUrl("/login")
//.defaultSuccessUrl("/home")
.successHandler(customAuthenticationSuccessHandler())
}

If front-end and back-end are separated, you want to return JSON instead of redirect when the user logs in. In this case, you can also use AuthenticationSuccessHandler to send JSON response. Do not use defaultSuccessUrl() and successForwardUrl() method because they cause redirect.

CSRF

You can choose to enable or disable CSRF toekn. The default is to enable CSRF so that attacker cannot use csrf attack. We can call HTTPSecurity.csrf().disable() to disable it.

After you enable CSRF, you need to add a hidden field in the login form that contains csrf token value.

1
2
3
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />

We will discuss more on CSRF in the future post.

Logout

You can also use HttpSecurity to customize logout page and logout process. HttpSecurity’s logout() method returns a LogoutConfigurer that you can use to customize logout.

LogoutConfigurer important methods

  • logoutUrl(String)
  • logoutSuccessUrl(String)
  • addLogoutHandler(LogoutHandler)
  • deleteCookies(String…)
  • permitAll()

Here is an example configuration for Logouts

1
2
3
4
5
http.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/custom-logout", "GET"))
.deleteCookies("remove")
.logoutSuccessUrl("/auth/login?logout")
.permitAll();

logoutUrl method sets the URL that triggers logout to occur. The default is a POST “/logout” request. If you want to use GET, use .logoutRequestMatcher(new AntPathRequestMatcher("/custom-logout", "GET"))

logoutSuccessUrl method sets the Url to redirect to after logout has occurred. The default is “/login?logout”

Source Code - https://github.com/xinghua24/SpringBootExamples/tree/master/FormLogin

Reference