123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- [[servlet-authentication-form]]
- = Form Login
- :figures: servlet/authentication/unpwd
- Spring Security provides support for username and password being provided through an HTML form.
- This section provides details on how form based authentication works within Spring Security.
- // FIXME: describe authenticationentrypoint, authenticationfailurehandler, authenticationsuccesshandler
- This section examines how form-based login works within Spring Security.
- First, we see how the user is redirected to the login form:
- .Redirecting to the Login Page
- image::{figures}/loginurlauthenticationentrypoint.png[]
- The preceding figure builds off our xref:servlet/architecture.adoc#servlet-securityfilterchain[`SecurityFilterChain`] diagram.
- image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the resource (`/private`) for which it is not authorized.
- image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
- image:{icondir}/number_3.png[] Since the user is not authenticated, xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] initiates __Start Authentication__ and sends a redirect to the login page with the configured xref:servlet/authentication/architecture.adoc#servlet-authentication-authenticationentrypoint[`AuthenticationEntryPoint`].
- In most cases, the `AuthenticationEntryPoint` is an instance of {security-api-url}org/springframework/security/web/authentication/LoginUrlAuthenticationEntryPoint.html[`LoginUrlAuthenticationEntryPoint`].
- image:{icondir}/number_4.png[] The browser requests the login page to which it was redirected.
- image:{icondir}/number_5.png[] Something within the application, must <<servlet-authentication-form-custom,render the login page>>.
- [[servlet-authentication-usernamepasswordauthenticationfilter]]
- When the username and password are submitted, the `UsernamePasswordAuthenticationFilter` authenticates the username and password.
- The `UsernamePasswordAuthenticationFilter` extends xref:servlet/authentication/architecture.adoc#servlet-authentication-abstractprocessingfilter[AbstractAuthenticationProcessingFilter], so the following diagram should look pretty similar:
- .Authenticating Username and Password
- image::{figures}/usernamepasswordauthenticationfilter.png[]
- The figure builds off our xref:servlet/architecture.adoc#servlet-securityfilterchain[`SecurityFilterChain`] diagram.
- image:{icondir}/number_1.png[] When the user submits their username and password, the `UsernamePasswordAuthenticationFilter` creates a `UsernamePasswordAuthenticationToken`, which is a type of xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[`Authentication`], by extracting the username and password from the `HttpServletRequest` instance.
- image:{icondir}/number_2.png[] Next, the `UsernamePasswordAuthenticationToken` is passed into the `AuthenticationManager` instance to be authenticated.
- The details of what `AuthenticationManager` looks like depend on how the xref:servlet/authentication/passwords/index.adoc#servlet-authentication-unpwd-storage[user information is stored].
- image:{icondir}/number_3.png[] If authentication fails, then __Failure__.
- . The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder] is cleared out.
- . `RememberMeServices.loginFail` is invoked.
- If remember me is not configured, this is a no-op.
- See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc.
- . `AuthenticationFailureHandler` is invoked.
- See the {security-api-url}org/springframework/security/web/authentication/AuthenticationFailureHandler.html[`AuthenticationFailureHandler`] class in the Javadoc
- image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
- . `SessionAuthenticationStrategy` is notified of a new login.
- See the {security-api-url}org/springframework/security/web/authentication/session/SessionAuthenticationStrategy.html[`SessionAuthenticationStrategy`] interface in the Javadoc.
- . The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
- See the {security-api-url}org/springframework/security/web/context/SecurityContextPersistenceFilter.html[`SecurityContextPersistenceFilter`] class in the Javadoc.
- . `RememberMeServices.loginSuccess` is invoked.
- If remember me is not configured, this is a no-op.
- See the {security-api-url}org/springframework/security/web/authentication/RememberMeServices.html[`RememberMeServices`] interface in the Javadoc.
- . `ApplicationEventPublisher` publishes an `InteractiveAuthenticationSuccessEvent`.
- . The `AuthenticationSuccessHandler` is invoked. Typically, this is a `SimpleUrlAuthenticationSuccessHandler`, which redirects to a request saved by xref:servlet/architecture.adoc#servlet-exceptiontranslationfilter[`ExceptionTranslationFilter`] when we redirect to the login page.
- [[servlet-authentication-form-min]]
- By default, Spring Security form login is enabled.
- However, as soon as any servlet-based configuration is provided, form based login must be explicitly provided.
- The following example shows a minimal, explicit Java configuration:
- .Form Login
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- public SecurityFilterChain filterChain(HttpSecurity http) {
- http
- .formLogin(withDefaults());
- // ...
- }
- ----
- XML::
- +
- [source,xml,role="secondary"]
- ----
- <http>
- <!-- ... -->
- <form-login />
- </http>
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
- http {
- formLogin { }
- }
- // ...
- }
- ----
- ======
- In the preceding configuration, Spring Security renders a default login page.
- Most production applications require a custom login form.
- [[servlet-authentication-form-custom]]
- The following configuration demonstrates how to provide a custom login form.
- .Custom Login Form Configuration
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- public SecurityFilterChain filterChain(HttpSecurity http) {
- http
- .formLogin(form -> form
- .loginPage("/login")
- .permitAll()
- );
- // ...
- }
- ----
- XML::
- +
- [source,xml,role="secondary"]
- ----
- <http>
- <!-- ... -->
- <intercept-url pattern="/login" access="permitAll" />
- <form-login login-page="/login" />
- </http>
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- open fun filterChain(http: HttpSecurity): SecurityFilterChain {
- http {
- formLogin {
- loginPage = "/login"
- permitAll()
- }
- }
- // ...
- }
- ----
- ======
- [[servlet-authentication-form-custom-html]]
- When the login page is specified in the Spring Security configuration, you are responsible for rendering the page.
- // FIXME: default login page rendered by Spring Security
- The following https://www.thymeleaf.org/[Thymeleaf] template produces an HTML login form that complies with a login page of `/login`.:
- .Login Form - src/main/resources/templates/login.html
- [source,xml]
- ----
- <!DOCTYPE html>
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
- <head>
- <title>Please Log In</title>
- </head>
- <body>
- <h1>Please Log In</h1>
- <div th:if="${param.error}">
- Invalid username and password.</div>
- <div th:if="${param.logout}">
- You have been logged out.</div>
- <form th:action="@{/login}" method="post">
- <div>
- <input type="text" name="username" placeholder="Username"/>
- </div>
- <div>
- <input type="password" name="password" placeholder="Password"/>
- </div>
- <input type="submit" value="Log in" />
- </form>
- </body>
- </html>
- ----
- There are a few key points about the default HTML form:
- * The form should perform a `post` to `/login`.
- * The form needs to include a xref:servlet/exploits/csrf.adoc#servlet-csrf[CSRF Token], which is xref:servlet/exploits/csrf.adoc#servlet-csrf-include-form-auto[automatically included] by Thymeleaf.
- * The form should specify the username in a parameter named `username`.
- * The form should specify the password in a parameter named `password`.
- * If the HTTP parameter named `error` is found, it indicates the user failed to provide a valid username or password.
- * If the HTTP parameter named `logout` is found, it indicates the user has logged out successfully.
- Many users do not need much more than to customize the login page.
- However, if needed, you can customize everything shown earlier with additional configuration.
- [[servlet-authentication-form-custom-controller]]
- If you use Spring MVC, you need a controller that maps `GET /login` to the login template we created.
- The following example shows a minimal `LoginController`:
- .LoginController
- [tabs]
- ======
- Java::
- +
- [source,java,role="primary"]
- ----
- @Controller
- class LoginController {
- @GetMapping("/login")
- String login() {
- return "login";
- }
- }
- ----
- Kotlin::
- +
- [source,kotlin,role="secondary"]
- ----
- @Controller
- class LoginController {
- @GetMapping("/login")
- fun login(): String {
- return "login"
- }
- }
- ----
- ======
|