基於表單的登入通常是保護 Spring Boot 應用程式 Web 前端的首選。它確保只有使用者使用使用者名稱和密碼對自己進行身份驗證後才能存取應用程式的某些區域,並且此狀態儲存在會話中。將基於表單的登入新增至我們的 Spring Boot 應用程式需要執行哪些步驟?
首先,我們使用 Bootify Builder 在目前版本 3.3.2 中建立一個簡單的 Spring Boot 應用程式 - 為此,我們只需點擊開啟專案即可。我們選擇 Thymeleaf + Bootstrap 作為前端堆疊 - Thymeleaf 是 Spring Boot 最常用的模板引擎,並允許伺服器端渲染。 Bootstrap 將作為 WebJar 整合到我們的應用程式中。選擇您想要連接的任何資料庫 - 現在也可以使用嵌入式資料庫。
在實體標籤中,我們建立表格 User 和 TodoList,並使用 N:1 關聯將它們連接起來。對於 TodoList,我們啟動前端的 CRUD 選項 - 這將是我們隨後使用 Spring Security 保護的區域。
我們非常簡單的資料庫架構的預覽
現在我們已經可以下載完成的應用程式並將其匯入到我們最喜歡的 IDE 中。
IntelliJ 中我們應用程式的第一個版本
基於表單的登入是在 Spring Security 的幫助下提供的。所以我們首先需要相關的依賴項,我們將其分別加入我們的build.gradle或pom.xml中。
implementation('org.springframework.boot:spring-boot-starter-security') implementation('org.thymeleaf.extras:thymeleaf-extras-springsecurity6')
模組 spring-boot-starter-security 整合了 Spring Security。 thymeleaf-extras-springsecurity6 包含一個小助手,它在我們的 Thymeleaf 範本中提供身份驗證狀態 - 稍後會詳細介紹。
有了這個,我們可以已經提供中央安全配置 - 直接在我們的最終版本中。
@Configuration @EnableMethodSecurity(prePostEnabled = true) public class HttpSecurityConfig { @Bean public PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager( final AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } @Bean public SecurityFilterChain configure(final HttpSecurity http) throws Exception { return http.cors(withDefaults()) .csrf(withDefaults()) .authorizeHttpRequests(authorize -> authorize.anyRequest().permitAll()) .formLogin(form -> form .loginPage("/login") .usernameParameter("email") .failureUrl("/login?loginError=true")) .logout(logout -> logout .logoutSuccessUrl("/login?logoutSuccess=true") .deleteCookies("JSESSIONID")) .exceptionHandling(exception -> exception .authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login?loginRequired=true"))) .build(); } }
我們的表單登入設定
理論上,Spring Security 可以提供較少配置的登入表單,但這會缺乏設計和我們希望向使用者呈現的一些訊息。但我們先來看看配置。
BCryptPasswordEncoder 是當今的標準將密碼的雜湊值及其單獨的鹽儲存在單一欄位中。如果我們沒有遺留需求,我們應該使用這個。此外,我們提供 AuthenticationManager 作為 bean,以便能夠將其整合到其他服務中。
作為第三個 bean,我們建立 SecurityFilterChain,因為這是自 Spring 3.0 以來所需的方法。我們應該正確配置 CORS 和 CSRF 以關閉相應的攻擊向量。預設配置通常足以滿足此目的。
在我們的設定類別中,我們放置了註解@EnableMethodSecurity,稍後將使用@PreAuthorize(...) 保護所需的控制器端點。因此我們允許使用permitAll()存取整個應用程式。如果沒有基於註解的安全性,我們也應該在這個地方配置受保護資源的路徑。
formLogin() 和 logout() 方法是為我們的後續控制器量身定制的,以便我們能夠始終向使用者顯示適當的訊息。 Spring Security 自動提供一個登入端點,可以透過 POST 請求提交使用者名稱和密碼。這裡我們將使用者名字段的名稱改為「電子郵件」。修改登出為之後帶參數重定向回登入頁面。
要從已建立的表中載入用戶,我們需要以 bean 的形式提供 UserDetailsService 的實作 - 它將自動找到並用作用戶來源。
@Service public class HttpUserDetailsService implements UserDetailsService { private final UserRepository userRepository; public HttpUserDetailsService(final UserRepository userRepository) { this.userRepository = userRepository; } @Override public HttpUserDetails loadUserByUsername(final String username) { final User user = userRepository.findByEmailIgnoreCase(username); if (user == null) { log.warn("user not found: {}", username); throw new UsernameNotFoundException("User " + username + " not found"); } final List<SimpleGrantedAuthority> roles = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); return new HttpUserDetails(user.getId(), username, user.getHash(), roles); } }
UserDetailsService 實作自動用作使用者來源
在我們的儲存庫中,我們應該新增一個方法 User findByEmailIgnoreCase(String email) 來對資料庫執行搜尋查詢 - 忽略大小寫允許使用者在編寫電子郵件時出現小錯誤。對於每個使用者來說,這裡的角色始終是 ROLE_USER。由於我們目前沒有可用的註冊端點,因此我們現在可以在應用程式中新增一個簡單的資料載入器。需要設定檔“本地”才能啟動。
@Component @Profile("local") public class UserLoader implements ApplicationRunner { private final UserRepository userRepository; private final PasswordEncoder passwordEncoder; public UserLoader(final UserRepository userRepository, final PasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; } @Override public void run(final ApplicationArguments args) { if (userRepository.count() != 0) { return; } final User user = new User(); user.setEmail("test@test.com"); user.setHash(passwordEncoder.encode("testtest")); userRepository.save(user); } }
用於在本地初始化第一個使用者的輔助類別
With this we can already add the LoginController. Since the POST endpoint is automatically provided by Spring Security, a GET endpoint is sufficient here to show the template to the user.
@Controller public class AuthenticationController { @GetMapping("/login") public String login(@RequestParam(name = "loginRequired", required = false) final Boolean loginRequired, @RequestParam(name = "loginError", required = false) final Boolean loginError, @RequestParam(name = "logoutSuccess", required = false) final Boolean logoutSuccess, final Model model) { model.addAttribute("authentication", new AuthenticationRequest()); if (loginRequired == Boolean.TRUE) { model.addAttribute(WebUtils.MSG_INFO, WebUtils.getMessage("authentication.login.required")); } if (loginError == Boolean.TRUE) { model.addAttribute(WebUtils.MSG_ERROR, WebUtils.getMessage("authentication.login.error")); } if (logoutSuccess == Boolean.TRUE) { model.addAttribute(WebUtils.MSG_INFO, WebUtils.getMessage("authentication.logout.success")); } return "authentication/login"; } }
Backend for rendering the login page
The request parameters that we had already specified in our security configuration are converted to corresponding messages here. In our simple application from Bootify the corresponding helpers are already included. Here we also need the AuthenticationRequest object with getters and setters.
public class AuthenticationRequest { @NotNull @Size(max = 255) private String email; @NotNull @Size(max = 255) private String password; }
The corresponding template for our controller could then look like this.
<!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{layout}"> <head> <title>[[#{authentication.login.headline}]]</title> </head> <body> <div layout:fragment="content"> <h1 class="mb-4">[[#{authentication.login.headline}]]</h1> <div th:replace="~{fragments/forms::globalErrors('authentication')}" /> <form th:action="${requestUri}" method="post"> <div th:replace="~{fragments/forms::inputRow(object='authentication', field='email')}" /> <div th:replace="~{fragments/forms::inputRow(object='authentication', field='password', type='password')}" /> <input type="submit" th:value="#{authentication.login.headline}" class="btn btn-primary mt-4" /> </form> </div> </body> </html>
As Thymeleaf doesn't allow direct access to request object anymore, we're providing the requestUri in the model.
@ModelAttribute("requestUri") String getRequestServletPath(final HttpServletRequest request) { return request.getRequestURI(); }
_ Providing the requestUri - as part of the AuthenticationController or a general ControllerAdvice _
With this template we send a POST request to the /login endpoint. The INFO or ERROR messages are automatically displayed by the layout. All used messages have to be present in our messages.properties.
authentication.login.headline=Login authentication.email.label=Email authentication.password.label=Password authentication.login.required=Please login to access this area. authentication.login.error=Your login was not successful - please try again. authentication.logout.success=Your logout was successful. navigation.login=Login navigation.logout=Logout
Last we can extend our layout.html. With this we also always show a login / logout link in the header. Spring Security also automatically provides a /logout endpoint, but we have to address it via POST.
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <!-- ... --> <a sec:authorize="!isAuthenticated()" th:href="@{/login}" class="nav-link">[[#{navigation.login}]]</a> <form sec:authorize="isAuthenticated()" th:action="@{/logout}" method="post" class="nav-link"> <input th:value="#{navigation.logout}" type="submit" class="unset" /> </form> <!-- ... --> </html>
Adding login / logout links to our layout
In the html tag we've extended the namespace to use the helpers from the thymeleaf-extras-springsecurity6 module. As a final step we only need to add the annotation @PreAuthorize("hasAuthority('ROLE_USER')") at our TodoListController.
With this we have all needed pieces of our puzzle together! Now we start our application and when we want to see the todo lists, we should be redirected to the login page. Here we can log in with test@test.com / testtest.
Automatic redirect to the login
In the Free plan of Bootify, Spring Boot prototypes with its own database schema, REST API and frontend can be generated. In the Professional plan, among other things, Spring Security with the form-based login is available to generate the setup described here - exactly matching the created database and the selected settings. A registration endpoint and role source can be specified as well.
» See Features and Pricing
以上是使用 Spring Boot 和 Thymeleaf 進行表單登錄的詳細內容。更多資訊請關注PHP中文網其他相關文章!