form.adoc 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. [[servlet-authentication-form]]
  2. = Form Login
  3. :figures: servlet/authentication/unpwd
  4. Spring Security provides support for username and password being provided through an HTML form.
  5. This section provides details on how form based authentication works within Spring Security.
  6. // FIXME: describe authenticationentrypoint, authenticationfailurehandler, authenticationsuccesshandler
  7. This section examines how form-based login works within Spring Security.
  8. First, we see how the user is redirected to the login form:
  9. .Redirecting to the Login Page
  10. [.invert-dark]
  11. image::{figures}/loginurlauthenticationentrypoint.png[]
  12. The preceding figure builds off our xref:servlet/architecture.adoc#servlet-securityfilterchain[`SecurityFilterChain`] diagram.
  13. image:{icondir}/number_1.png[] First, a user makes an unauthenticated request to the resource (`/private`) for which it is not authorized.
  14. image:{icondir}/number_2.png[] Spring Security's xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] indicates that the unauthenticated request is __Denied__ by throwing an `AccessDeniedException`.
  15. 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`].
  16. In most cases, the `AuthenticationEntryPoint` is an instance of javadoc:org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint[].
  17. image:{icondir}/number_4.png[] The browser requests the login page to which it was redirected.
  18. image:{icondir}/number_5.png[] Something within the application, must <<servlet-authentication-form-custom,render the login page>>.
  19. [[servlet-authentication-usernamepasswordauthenticationfilter]]
  20. When the username and password are submitted, the `UsernamePasswordAuthenticationFilter` creates a `UsernamePasswordAuthenticationToken` which is a type of https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-authentication[Authentication], by extracting the username and password from the `HttpServletRequest` instance.
  21. The `UsernamePasswordAuthenticationFilter` extends xref:servlet/authentication/architecture.adoc#servlet-authentication-abstractprocessingfilter[AbstractAuthenticationProcessingFilter], so the following diagram should look pretty similar:
  22. .Authenticating Username and Password
  23. [.invert-dark]
  24. image::{figures}/usernamepasswordauthenticationfilter.png[]
  25. The figure builds off our xref:servlet/architecture.adoc#servlet-securityfilterchain[`SecurityFilterChain`] diagram.
  26. 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.
  27. image:{icondir}/number_2.png[] Next, the `UsernamePasswordAuthenticationToken` is passed into the `AuthenticationManager` instance to be authenticated.
  28. 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].
  29. image:{icondir}/number_3.png[] If authentication fails, then __Failure__.
  30. . The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder] is cleared out.
  31. . `RememberMeServices.loginFail` is invoked.
  32. If remember me is not configured, this is a no-op.
  33. See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc.
  34. . `AuthenticationFailureHandler` is invoked.
  35. See the javadoc:org.springframework.security.web.authentication.AuthenticationFailureHandler[] class in the Javadoc
  36. image:{icondir}/number_4.png[] If authentication is successful, then __Success__.
  37. . `SessionAuthenticationStrategy` is notified of a new login.
  38. See the javadoc:org.springframework.security.web.authentication.session.SessionAuthenticationStrategy[] interface in the Javadoc.
  39. . The xref:servlet/authentication/architecture.adoc#servlet-authentication-authentication[Authentication] is set on the xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder].
  40. See the javadoc:org.springframework.security.web.context.SecurityContextPersistenceFilter[] class in the Javadoc.
  41. . `RememberMeServices.loginSuccess` is invoked.
  42. If remember me is not configured, this is a no-op.
  43. See the javadoc:org.springframework.security.web.authentication.RememberMeServices[] interface in the Javadoc.
  44. . `ApplicationEventPublisher` publishes an `InteractiveAuthenticationSuccessEvent`.
  45. . 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.
  46. [[servlet-authentication-form-min]]
  47. By default, Spring Security form login is enabled.
  48. However, as soon as any servlet-based configuration is provided, form based login must be explicitly provided.
  49. The following example shows a minimal, explicit Java configuration:
  50. .Form Login
  51. [tabs]
  52. ======
  53. Java::
  54. +
  55. [source,java,role="primary"]
  56. ----
  57. public SecurityFilterChain filterChain(HttpSecurity http) {
  58. http
  59. .formLogin(withDefaults());
  60. // ...
  61. }
  62. ----
  63. XML::
  64. +
  65. [source,xml,role="secondary"]
  66. ----
  67. <http>
  68. <!-- ... -->
  69. <form-login />
  70. </http>
  71. ----
  72. Kotlin::
  73. +
  74. [source,kotlin,role="secondary"]
  75. ----
  76. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  77. http {
  78. formLogin { }
  79. }
  80. // ...
  81. }
  82. ----
  83. ======
  84. In the preceding configuration, Spring Security renders a default login page.
  85. Most production applications require a custom login form.
  86. [[servlet-authentication-form-custom]]
  87. The following configuration demonstrates how to provide a custom login form.
  88. .Custom Login Form Configuration
  89. [tabs]
  90. ======
  91. Java::
  92. +
  93. [source,java,role="primary"]
  94. ----
  95. public SecurityFilterChain filterChain(HttpSecurity http) {
  96. http
  97. .formLogin((form) -> form
  98. .loginPage("/login")
  99. .permitAll()
  100. );
  101. // ...
  102. }
  103. ----
  104. XML::
  105. +
  106. [source,xml,role="secondary"]
  107. ----
  108. <http>
  109. <!-- ... -->
  110. <intercept-url pattern="/login" access="permitAll" />
  111. <form-login login-page="/login" />
  112. </http>
  113. ----
  114. Kotlin::
  115. +
  116. [source,kotlin,role="secondary"]
  117. ----
  118. open fun filterChain(http: HttpSecurity): SecurityFilterChain {
  119. http {
  120. formLogin {
  121. loginPage = "/login"
  122. permitAll()
  123. }
  124. }
  125. // ...
  126. }
  127. ----
  128. ======
  129. [[servlet-authentication-form-custom-html]]
  130. When the login page is specified in the Spring Security configuration, you are responsible for rendering the page.
  131. // FIXME: default login page rendered by Spring Security
  132. The following https://www.thymeleaf.org/[Thymeleaf] template produces an HTML login form that complies with a login page of `/login`.:
  133. .Login Form - src/main/resources/templates/login.html
  134. [source,xml]
  135. ----
  136. <!DOCTYPE html>
  137. <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
  138. <head>
  139. <title>Please Log In</title>
  140. </head>
  141. <body>
  142. <h1>Please Log In</h1>
  143. <div th:if="${param.error}">
  144. Invalid username and password.</div>
  145. <div th:if="${param.logout}">
  146. You have been logged out.</div>
  147. <form th:action="@{/login}" method="post">
  148. <div>
  149. <input type="text" name="username" placeholder="Username"/>
  150. </div>
  151. <div>
  152. <input type="password" name="password" placeholder="Password"/>
  153. </div>
  154. <input type="submit" value="Log in" />
  155. </form>
  156. </body>
  157. </html>
  158. ----
  159. There are a few key points about the default HTML form:
  160. * The form should perform a `post` to `/login`.
  161. * The form needs to include a xref:servlet/exploits/csrf.adoc#servlet-csrf[CSRF Token], which is xref:servlet/exploits/csrf.adoc#csrf-integration-form[automatically included] by Thymeleaf.
  162. * The form should specify the username in a parameter named `username`.
  163. * The form should specify the password in a parameter named `password`.
  164. * If the HTTP parameter named `error` is found, it indicates the user failed to provide a valid username or password.
  165. * If the HTTP parameter named `logout` is found, it indicates the user has logged out successfully.
  166. Many users do not need much more than to customize the login page.
  167. However, if needed, you can customize everything shown earlier with additional configuration.
  168. [[servlet-authentication-form-custom-controller]]
  169. If you use Spring MVC, you need a controller that maps `GET /login` to the login template we created.
  170. The following example shows a minimal `LoginController`:
  171. .LoginController
  172. [tabs]
  173. ======
  174. Java::
  175. +
  176. [source,java,role="primary"]
  177. ----
  178. @Controller
  179. class LoginController {
  180. @GetMapping("/login")
  181. String login() {
  182. return "login";
  183. }
  184. }
  185. ----
  186. Kotlin::
  187. +
  188. [source,kotlin,role="secondary"]
  189. ----
  190. @Controller
  191. class LoginController {
  192. @GetMapping("/login")
  193. fun login(): String {
  194. return "login"
  195. }
  196. }
  197. ----
  198. ======