# Introduction [回首頁](https://hackmd.io/VbsC73UFQpi4qlHrKLECQg) Spring Security 是一個功能強大且靈活的安全框架,用於在Spring應用程式中實現身份驗證(Authentication)、授權(Authorization)和安全保護。它是Spring框架的一個子專案,專注於解決應用程式的安全需求,確保應用程式的資料和功能只能被授權的使用者訪問。 Spring Security提供了一系列功能來保護應用程式,包括: 1. 身份驗證(Authentication):Spring Security可以驗證使用者的身份,確保使用者是合法的並且可以訪問應用程式。它支援各種身份驗證方式,如基本認證、表單認證、OAuth等,並可以整合其他第三方身份驗證系統。 2. 授權(Authorization):一旦使用者通過身份驗證,Spring Security可以根據使用者的角色和權限來控制其對應用程式資源的訪問權限。它支援細緻的授權,可以基於角色、權限、表達式等進行控制。 3. 密碼加密:Spring Security提供了安全的密碼儲存和加密機制,可以確保使用者密碼不以明文形式儲存於資料庫中,提高系統的安全性。 4. 會話管理:Spring Security可以管理使用者的會話,包括設定會話超時時間、單一登入(Single Sign-On)、會話固定保護等功能。 5. 防止常見的安全漏洞:Spring Security內建了防止跨站請求偽造(CSRF)、點擊劫持(Clickjacking)等常見安全漏洞的功能,幫助開發人員預防安全風險。 6. 擴展性:Spring Security是高度可擴展的,可以根據應用程式的特定需求進行客製化和擴展。它提供了很多可插拔的元件,使得開發人員能夠根據具體情況靈活調整安全策略。 Spring Security可以與Spring框架無縫整合,並且可以與各種持久化框架(如Hibernate、JPA等)和前端框架(如Angular、React、Vue等)配合使用。使用Spring Security,開發人員可以輕鬆地為應用程式添加強大的安全功能,保護使用者資料和系統資源,確保應用程式的安全性和穩定性。 # SecurityConfig.java 常用的Spring Security配置(SecurityConfig)是一個Java類,用於設定Spring Security的安全功能。在這個配置類中,我們可以定義如何進行身份驗證、授權和安全保護,以及其他相關的安全設定。以下是一個常見的SecurityConfig的示例: ## 新版 - 官網介紹 [configuration]( https://docs.spring.io/spring-security/reference/servlet/configuration/java.html) [authorization]( https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html) 基本上就是不繼承 WebSecurityConfigurerAdapter, 然後api有調整為用 SecurityFilterChain ```java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public UserDetailsService userDetailsService() throws Exception { // ensure the passwords are encoded properly User.UserBuilder users = User.withDefaultPasswordEncoder(); InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(users.username("user").password("user").roles("USER").build()); manager.createUser(users.username("admin").password("admin").roles("USER","ADMIN").build()); return manager; } @Bean public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests( requests -> requests .requestMatchers("/test/demo").permitAll() .requestMatchers("/test/admin").hasRole("ADMIN") .requestMatchers("/test/normal").hasRole("NORMAL") .anyRequest() .authenticated() ) .formLogin(withDefaults()) .logout(withDefaults()); return http.build(); } ``` ## 舊版 下面自己寫得很high, 但是改版後就不能用了 哈哈... ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() // 允許公開訪問的URL .antMatchers("/admin/**").hasRole("ADMIN") // 需要ADMIN角色的URL .antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 需要USER或ADMIN角色的URL .anyRequest().authenticated() // 其他所有URL需要身份驗證 .and() .formLogin() .loginPage("/login") // 登入頁面的URL .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } } ``` 這是一個基本的SecurityConfig示例,其中包含以下重要部分: 1. @Configuration:標記這是一個配置類。 2. @EnableWebSecurity:啟用Web安全功能。 3. configure(HttpSecurity http):配置HttpSecurity,定義URL的授權規則和安全設定。 4. configureGlobal(AuthenticationManagerBuilder auth):配置全局的身份驗證管理器,設定使用自定義的UserDetailsService來查詢使用者資訊。 5. PasswordEncoder:設定密碼加密方式,這裡使用了BCryptPasswordEncoder。 此示例中的配置確保了以下功能: - 公開的URL(/public/**)可以被所有人訪問。 - 需要ADMIN角色的URL(/admin/**)只能被具有ADMIN角色的使用者訪問。 - 需要USER或ADMIN角色的URL(/user/**)只能被具有USER或ADMIN角色的使用者訪問。 - 其他所有URL需要身份驗證,如果使用者未登入,會被導向到登入頁面。 - 登入頁面為/login,並且是公開訪問的。 - 設定了BCryptPasswordEncoder來加密使用者的密碼。 這只是一個簡單的例子,實際的SecurityConfig會根據應用程式的需求和安全策略進行客製化設定。 # 在controller 的應用範例 以下是Spring Security在Controller中的應用範例。假設我們有一個用戶管理系統,需要對用戶進行驗證和授權,只有經過驗證的用戶才能訪問特定的資源。 首先,我們需要配置Spring Security,設定用戶驗證和授權規則。這裡使用基於記憶體的用戶驗證,並限制只有ROLE_ADMIN角色的用戶才能訪問管理用戶的API。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/users/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .httpBasic(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("admin").password("{noop}admin").roles("ADMIN"); } } ``` 上述配置中,我們設定了一個安全規則,限制只有ROLE_ADMIN角色的用戶才能訪問`/users/**`下的API,其他所有請求需要驗證。使用`httpBasic()`來啟用HTTP Basic認證。 接著,在Controller中加入安全註解來限制特定API的訪問權限。 ```java @RestController @RequestMapping("/users") public class UserController { @GetMapping("/") public ResponseEntity<List<User>> getAllUsers() { // 返回所有用戶列表 List<User> userList = // ... 根據實際需求取得用戶列表 return ResponseEntity.ok(userList); } @PostMapping("/") public ResponseEntity<String> addUser(@RequestBody User user) { // 新增用戶的邏輯 return ResponseEntity.ok("User added successfully."); } @DeleteMapping("/{id}") public ResponseEntity<String> deleteUserById(@PathVariable("id") int id) { // 刪除用戶的邏輯 return ResponseEntity.ok("User deleted successfully."); } } ``` 在Controller中,我們只需要定義相應的API接口,不需要額外處理驗證和授權,Spring Security會自動處理這些事務。當用戶訪問`/users/**`下的API時,Spring Security會自動檢查用戶是否有相應的角色,如果沒有,會返回401 Unauthorized的響應。 請注意,在這個範例中,我們使用了基於記憶體的用戶驗證,實際應用中,可以使用數據庫、LDAP等來進行真實的用戶驗證和授權。另外,如果需要更細緻的授權規則,可以使用更高級的Spring Security功能,如方法級別的安全註解、自定義的AccessDecisionVoter等。 # 介紹@PreAuthorize 以及常用的 annotaion `@PreAuthorize`是Spring Security提供的一個方法級別的安全註解之一,它用於在方法執行之前進行權限驗證。使用`@PreAuthorize`可以方便地對方法進行授權檢查,確保只有符合指定條件的用戶可以調用這個方法。 常用的Spring Security註解包括: 1. `@Secured`:用於在方法上指定權限要求,指定哪些角色或權限可以訪問該方法。例如:`@Secured("ROLE_ADMIN")`表示只有ROLE_ADMIN角色的用戶可以訪問該方法。 2. `@PreAuthorize`:用於在方法上指定SpEL表達式,根據SpEL表達式的結果來判斷是否有權限訪問該方法。例如:`@PreAuthorize("hasRole('ROLE_ADMIN')")`表示只有ROLE_ADMIN角色的用戶可以訪問該方法。 3. `@PostAuthorize`:用於在方法上指定SpEL表達式,根據SpEL表達式的結果來判斷是否有權限訪問方法的返回值。例如:`@PostAuthorize("returnObject.createdBy == principal.username")`表示只有方法返回值中的createdBy字段等於當前用戶的username時,用戶才有權限訪問該方法的返回值。 4. `@RolesAllowed`:用於在方法上指定允許的角色列表,可以使用`@RolesAllowed({"ROLE_ADMIN", "ROLE_USER"})`來指定多個角色。 這些註解可以用於Controller的方法上,也可以用於Service層的方法上,以便在不同層級進行權限檢查。使用這些註解可以讓我們在程式碼中直接定義授權條件,讓安全性設定更加靈活和易於維護。 # ref - 官網介紹 [configuration]( https://docs.spring.io/spring-security/reference/servlet/configuration/java.html) [authorization]( https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html) - github [github search](https://github.com/search?q=spring+security&type=repositories) [jwt-spring-security-demo](https://github.com/szerhusenBC/jwt-spring-security-demo) [spring-security-registration](https://github.com/Baeldung/spring-security-registration) - 26 springboot + spring security [wuyouzhuguli](https://github.com/wuyouzhuguli/SpringAll) [whyalwaysmea](https://github.com/whyalwaysmea/Spring-Security) [527515025](https://github.com/527515025/springBoot) - spring security + jwt [spring-boot-3-jwt-security](https://github.com/ali-bouali/spring-boot-3-jwt-security) [Spring Boot Security 和 JWT 教程及示例](https://www.bezkoder.com/spring-boot-security-jwt/#Source_Code) [使用 Spring Boot 3 和 Spring Security 6 進行 JWT 身份驗證和授權](https://medium.com/@truongbui95/jwt-authentication-and-authorization-with-spring-boot-3-and-spring-security-6-2f90f9337421) # 問題集 ## restful api 顯示403 https://www.baeldung.com/java-spring-fix-403-error 加入 .csrf().disable(); 可以解決 正式機不可以這樣.. ```java @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests( requests -> requests .anyRequest().permitAll() ) .csrf().disable(); return http.build(); } ``` ## @RequiredArgsConstructor [@RequiredArgsConstructor](https://kucw.github.io/blog/2020/3/java-lombok/) ## mysql properties設定 [spring-boot-3-crud-restful-webservices](https://www.javaguides.net/2022/12/spring-boot-3-crud-restful-webservices.html) [阿里雲](https://github.com/alibaba/druid) # project code review [RuoYi-Vue](https://github.com/yangzongzhuan/RuoYi-Vue) table ![](https://hackmd.io/_uploads/BJucaT6o2.png) ## login + Security 流程圖 直接看圖 [xmind](https://gitmind.com/app/docs/mghw8db3) ## SysLoginService ### AuthenticationManager 在 Spring Security 中,`AuthenticationManager` 是用於進行身份驗證的核心介面。它負責處理用戶的身份驗證請求,確保請求中提供的用戶名和密碼是有效的,並返回驗證後的 `Authentication` 對象表示驗證成功。 通常情況下,`AuthenticationManager` 是由 Spring Security 自動配置的,你無需自己創建。當你在配置中啟用了 Spring Security,它會自動創建一個預設的 `AuthenticationManager`。 在你的程式碼中,使用 `@Resource` 注解來注入 `AuthenticationManager`。這樣你就可以在需要的地方使用它來進行身份驗證。 例如,你可以在某個控制器或服務中使用 `AuthenticationManager` 來進行身份驗證,像這樣: ```java @RestController public class AuthController { @Resource private AuthenticationManager authenticationManager; @PostMapping("/login") public ResponseEntity<String> login(@RequestBody LoginRequest loginRequest) { try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) ); SecurityContextHolder.getContext().setAuthentication(authentication); // 身份驗證成功,返回 JWT Token 或其他相關操作 return ResponseEntity.ok("Login successful!"); } catch (AuthenticationException e) { // 身份驗證失敗,返回相應的錯誤提示 return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication failed"); } } } ``` 在這個例子中,`authenticationManager.authenticate()` 方法被用來進行身份驗證,並根據驗證結果進行相應的處理。 請注意,如果你使用的是 Spring Boot,並且在專案中已經配置了 Spring Security,則 `AuthenticationManager` 會自動注入並可用於進行身份驗證。 ### authenticationManager.authenticate 執行流程, 會用到userDetailsService? `authenticationManager.authenticate` 方法是 Spring Security 中用於進行身份驗證的核心方法。該方法接受一個 `Authentication` 對象作為參數,表示用戶提供的身份信息,然後進行驗證。 執行流程如下: 1. 當用戶提交登入請求時,Spring Security 會獲取用戶提供的身份信息(通常是用戶名和密碼),並創建一個 `Authentication` 對象來表示這些信息。 2. 接下來,這個 `Authentication` 對象會被傳遞給 `authenticationManager.authenticate` 方法。 3. 在 `authenticationManager.authenticate` 方法內部,Spring Security 會選擇適合的 `AuthenticationProvider` 來進行具體的身份驗證。`AuthenticationProvider` 是 Spring Security 中的一個接口,用於執行不同類型的身份驗證策略。 4. `AuthenticationProvider` 的實現類會根據具體的驗證策略來驗證用戶的身份信息。這可能涉及到用戶名和密碼的比對、查詢用戶信息、檢查用戶是否被鎖定等等。 5. 如果驗證成功,`AuthenticationProvider` 將返回一個新的 `Authentication` 對象,其中包含了用戶的身份信息以及所擁有的權限(角色)。同時,`Authentication` 對象的 `isAuthenticated` 屬性將被設定為 `true`,表示用戶已經通過驗證。 6. 如果驗證失敗,`AuthenticationProvider` 將拋出 `AuthenticationException` 的子類,表示身份驗證失敗。在這種情況下,`Authentication` 對象的 `isAuthenticated` 屬性仍然保持為 `false`。 7. 當 `AuthenticationProvider` 返回成功驗證的 `Authentication` 對象後,`authenticationManager.authenticate` 方法會將該對象保存到 `SecurityContextHolder` 中,供後續的安全處理過程使用。 關於 `userDetailsService`,它通常用於在執行身份驗證之前查詢用戶的詳細信息,以便進行驗證。在某些情況下,需要根據用戶提供的用戶名查詢用戶的相關信息,例如用戶名、密碼、角色等。`userDetailsService` 是 Spring Security 中的一個接口,用於執行這個查詢操作。在 `authenticationManager.authenticate` 方法中,`userDetailsService` 可以被用來查詢用戶的信息,然後進行驗證。但是,是否使用 `userDetailsService` 取決於具體的身份驗證策略和實現。有些情況下,可能不需要使用 `userDetailsService`,而是直接使用自定義的驗證邏輯進行驗證。 ### Authentication `Authentication`(身份驗證)是 Spring Security 中用於表示用戶身份驗證結果的一個接口。當用戶試圖登入時,Spring Security 會進行身份驗證,並最終生成一個 `Authentication` 對象,表示用戶的身份驗證結果。 `Authentication` 接口是 Spring Security 的核心之一,它包含了以下重要的屬性和方法: 1. `getPrincipal()`:返回表示已驗證用戶的主體(Principal)的對象。通常情況下,這是一個實現了 `UserDetails` 接口的對象,包含用戶的基本信息(如用戶名、密碼等)以及安全相關的屬性(如角色、權限等)。 2. `getAuthorities()`:返回表示用戶所擁有的權限(角色)的集合。這些權限通常是 `GrantedAuthority` 的實例,表示用戶被授予的權限。 3. `getCredentials()`:返回用戶的憑證(通常是密碼)。由於安全原因,這通常是被隱藏的。 4. `isAuthenticated()`:返回用戶是否已經通過驗證。如果用戶已經通過驗證,則返回 true;否則返回 false。 5. `setAuthenticated(boolean authenticated)`:設定用戶是否已經通過驗證。通常情況下,這個方法是由 Spring Security 自動設定的,表示用戶的身份是否通過驗證。 在進行身份驗證時,Spring Security 會根據用戶提供的用戶名(通常是用戶輸入的用戶名或電子郵件)以及密碼進行驗證。一旦驗證成功,Spring Security 會創建一個 `Authentication` 對象,其中包含了用戶的身份信息和權限信息,並將其保存在 `SecurityContextHolder` 中,供後續的安全處理過程使用。 在你之前提供的程式碼片段中,`Authentication` 對象表示了用戶的身份驗證結果,並通過 `SecurityContextHolder.getContext().setAuthentication(authentication)` 方法將其設定到安全上下文中,以便在後續處理中使用。 ### userDetailsService `UserDetailsService` 是 Spring Security 提供的一個用戶信息查詢服務接口,用於從特定的數據源(例如數據庫)中查詢用戶的詳細信息。當用戶提交身份驗證請求時,Spring Security 將使用 `UserDetailsService` 從數據源中獲取用戶的詳細信息,並構建一個實現了 `UserDetails` 接口的對象,用於進行身份驗證。 `UserDetailsService` 接口只有一個方法: ```java public interface UserDetailsService { UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; } ``` - `loadUserByUsername(String username)` 方法用於根據用戶名(username)查詢用戶的詳細信息。如果找到了相應的用戶,則返回一個實現了 `UserDetails` 接口的對象,其中包含了用戶的詳細信息。如果找不到用戶,則應該拋出 `UsernameNotFoundException` 異常。 通常,在實現自定義的 `UserDetailsService` 時,你需要根據用戶名查詢數據庫或其他數據源,獲取用戶的詳細信息,並返回一個實現了 `UserDetails` 接口的對象。這個對象包含了用戶的基本資訊、權限(角色)等信息,並將被用於身份驗證和授權操作。 在 Spring Boot 的配置中,你可以通過實現 `UserDetailsService` 接口來提供自己的用戶信息查詢服務,並將其注入到 Spring Security 中。例如: ```java @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found with username: " + username); } return new CustomUserDetails(user); } } ``` 在這個例子中,我們實現了一個自定義的 `UserDetailsService`,它通過 `UserRepository` 查詢數據庫中的用戶信息,並返回一個 `CustomUserDetails` 對象,其中 `CustomUserDetails` 是我們自己定義的實現了 `UserDetails` 接口的用戶詳細信息類。 注意:在實際應用中,還需要為用戶密碼進行加密和解密操作,以確保安全性。通常,密碼應該是加密保存在數據庫中的。在使用自定義的 `UserDetailsService` 時,你需要在返回的 `UserDetails` 對象中設置正確的密碼,Spring Security 會自動幫你進行密碼的校驗和比對。 ### UserDetails `UserDetails` 是 Spring Security 中表示用戶詳細信息的接口。它提供了用戶的基本資訊,如用戶名、密碼、角色、帳號是否過期、帳號是否被鎖定、帳號是否被禁用等等。`UserDetails` 接口定義如下: ```java public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities(); String getPassword(); String getUsername(); boolean isAccountNonExpired(); boolean isAccountNonLocked(); boolean isCredentialsNonExpired(); boolean isEnabled(); } ``` `UserDetails` 中的方法包含了用戶的基本屬性和權限信息,其中: - `getAuthorities()` 方法返回一個表示用戶所擁有的權限(角色)的集合。這些權限通常用 `GrantedAuthority` 接口表示。 - `getPassword()` 方法返回用戶的加密後的密碼。 - `getUsername()` 方法返回用戶名,用戶名在身份驗證中通常被用來識別用戶。 - `isAccountNonExpired()` 方法返回一個布爾值,表示用戶的帳號是否未過期。 - `isAccountNonLocked()` 方法返回一個布爾值,表示用戶的帳號是否未被鎖定。 - `isCredentialsNonExpired()` 方法返回一個布爾值,表示用戶的認證(密碼)是否未過期。 - `isEnabled()` 方法返回一個布爾值,表示用戶是否啟用(非禁用)。 通常,在自定義的 `UserDetailsService` 中,會根據用戶名查詢用戶的詳細信息並返回一個實現了 `UserDetails` 接口的對象,用於進行身份驗證。在驗證成功後,這個對象會被保存到 `Authentication` 對象中,供 Spring Security 在後續的安全處理過程中使用。 ### authenticationManager.authenticate vs userDetailsService 在 `authenticationManager.authenticate` 方法的執行流程中,會用到 `userDetailsService` 的地方是在身份驗證的過程中。當 `authenticate` 方法被調用時,它會根據用戶提交的身份驗證請求,提取用戶名(通常是用戶輸入的帳號)以及密碼等認證信息。 接下來,`authenticate` 方法會使用 `userDetailsService` 從數據源中查詢用戶的詳細信息。它會調用 `userDetailsService` 的 `loadUserByUsername` 方法,並將用戶名作為參數傳遞進去。 `userDetailsService` 的 `loadUserByUsername` 方法的實現會根據用戶名查詢數據庫或其他數據源,獲取用戶的詳細信息,例如用戶名、密碼、權限(角色)等。然後,它會將這些用戶詳細信息封裝成一個實現了 `UserDetails` 接口的對象。 接著,`authenticate` 方法會將用戶提交的密碼和從 `userDetailsService` 獲取的密碼進行比對,確保用戶提交的密碼是正確的。如果密碼驗證成功,則表示用戶身份驗證通過,`authenticate` 方法會返回一個已驗證的 `Authentication` 對象,其中包含用戶的詳細信息和權限等。 如果密碼驗證失敗,則 `authenticate` 方法會拋出 `BadCredentialsException` 異常,表示用戶身份驗證失敗。 總結來說,`userDetailsService` 在 `authenticationManager.authenticate` 方法的執行流程中的作用是從數據源中獲取用戶詳細信息,用於進行身份驗證操作。它將用戶詳細信息封裝成 `UserDetails` 對象,並交由 `authenticate` 方法進行密碼驗證和用戶身份的確認。 ### SecurityContextHolder 在 Spring Security 中,`SecurityContextHolder` 是一個用於存儲當前身份驗證信息的靜態類。它使用 `ThreadLocal` 來確保每個執行緒中都有獨立的身份驗證信息。當用戶成功進行身份驗證後,我們可以使用 `SecurityContextHolder` 將驗證信息存儲在上下文中,以便後續在應用程式的其他地方可以方便地獲取和使用這些信息。 在前面提到的例子中,我們使用了 `authenticationManager.authenticate()` 方法來進行身份驗證。如果驗證成功,則會返回一個 `Authentication` 對象,表示該用戶的身份驗證成功。 接下來,我們將這個成功的 `Authentication` 對象存儲在 `SecurityContextHolder` 中,以便在應用程式的其他地方可以訪問它,比如在控制器或服務中需要知道當前已驗證用戶的信息。 這是一個將驗證成功的 `Authentication` 對象存儲在 `SecurityContextHolder` 中的示例: ```java @RestController public class AuthController { @Resource private AuthenticationManager authenticationManager; @PostMapping("/login") public ResponseEntity<String> login(@RequestBody LoginRequest loginRequest) { try { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) ); SecurityContextHolder.getContext().setAuthentication(authentication); // 身份驗證成功,返回 JWT Token 或其他相關操作 return ResponseEntity.ok("Login successful!"); } catch (AuthenticationException e) { // 身份驗證失敗,返回相應的錯誤提示 return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Authentication failed"); } } @GetMapping("/profile") public ResponseEntity<String> getProfile() { // 從 SecurityContextHolder 中獲取當前驗證的用戶信息 Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null) { String username = authentication.getName(); // 根據用戶名查詢用戶資料或其他相關操作 return ResponseEntity.ok("Hello, " + username + "!"); } else { // 未驗證用戶,返回相應提示 return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Not authenticated"); } } } ``` 在上面的例子中,`getProfile()` 方法中,我們從 `SecurityContextHolder` 中獲取了當前驗證的用戶信息,並根據這些信息進行相應的操作。這樣我們就可以在應用程式的其他地方使用已驗證的用戶信息,而不需要再次進行身份驗證。 :::info @Resource 是一個用於依賴注入的 Spring 框架的標記,用於將相關的資源(例如 Bean、Service 或 Component)注入到另一個類中。 在 Spring 中,有多種方式來實現依賴注入,其中包括 @Autowired、@Resource 和 @Inject。這些註解的作用都是一樣的,都是用於自動將相依的資源注入到類中。 ::: ### 使用者登入成功後,記錄登入相關的資訊 1. `AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));` 這一行程式碼使用 `AsyncManager` 來異步執行 `AsyncFactory.recordLogininfor(...)` 方法,該方法用於記錄使用者的登入資訊到"日誌系統"。傳入的參數包括使用者名稱 `username`、登入成功的訊息 `MessageUtils.message("user.login.success")`,以及一個常數 `Constants.LOGIN_SUCCESS`,它可能是用於標記登入成功的常數。 2. `LoginUser loginUser = (LoginUser) authentication.getPrincipal();` 這一行程式碼獲取使用者登入成功後的身份驗證資訊,並將其轉換為 `LoginUser` 對象。`authentication` 參數應該是從某處獲取的,可能是通過 Spring Security 的身份驗證機制獲取的,其中包含使用者的驗證資訊。 3. `recordLoginInfo(loginUser.getUserId());` 這一行程式碼調用了 `recordLoginInfo(...)` 方法,傳入的參數是使用者的ID (`loginUser.getUserId()`)。根據方法名稱看起來,這個方法可能用於記錄使用者的登入相關資訊,例如登入時間、登入IP等等。 總的來說,這段程式碼的作用是在使用者登入成功後,記錄登入相關的資訊,並進行一些後續處理。在詳細了解 `AsyncManager` 和 `AsyncFactory` 的功能之前,很難確定這段程式碼的實際作用。如果需要更多的資訊,您可能需要查看 `AsyncManager` 和 `AsyncFactory` 的程式碼,以及 `recordLoginInfo(...)` 方法的實現。 4. ` tokenService.createToken(loginUser)` 這行程式碼是回傳一個令牌(Token)給使用者。根據程式碼內容來看,tokenService 是一個服務(Service)或組件(Component),它應該負責處理令牌相關的邏輯。在這裡,它呼叫了 createToken(...) 方法並將 loginUser 作為參數傳入。這個令牌通常用於驗證使用者的身份,當使用者登入成功後,伺服器可以為其建立一個令牌,並將此令牌返回給客戶端(通常是前端網頁或應用程式)。之後,客戶端可以在每次向伺服器發送請求時攜帶此令牌,伺服器就可以透過令牌來驗證使用者的身份。 ## TokenService 開始之前先介紹一下JWT [圖解JWT](https://www.iocoder.cn/Fight/understanding-jwt/?self) ### JWT JWT(JSON Web Token)是一種簡單且輕量的身份驗證和授權機制,通常用於在客戶端和伺服器之間傳遞安全的信息。它使用 JSON 格式來描述要傳遞的信息,並使用簽名或加密來確保其完整性和安全性。 JWT 由三部分組成,它們使用點(.)分隔: 1. **Header(頭部)**:描述 JWT 的元數據,包括所使用的加密算法(例如 HMAC SHA256 或 RSA)和 token 的類型(例如 JWT)。 2. **Payload(有效載荷)**:存放實際的要傳遞的數據,這些數據是以 JSON 格式編碼的,可以包含一些標準的 Claim(聲明)或自定義的 Claim。 3. **Signature(簽名)**:使用指定的算法和秘鑰對 Header 和 Payload 進行簽名,確保其未被竄改。 JWT 的工作流程如下: 1. 用戶進行身份驗證,伺服器生成一個包含用戶信息的 JWT,並將其返回給客戶端。 2. 客戶端將 JWT 存儲,通常是在 Cookie 或 Authorization Header 中。 3. 在每次發送請求時,客戶端將 JWT 包含在請求中,以供伺服器驗證。 4. 伺服器在接收到請求時,使用存儲的秘鑰對 JWT 進行驗證和解密,確保其合法性和完整性。 5. 如果 JWT 驗證通過,伺服器根據其中的信息進行授權和處理請求。 JWT 的優點包括: - 輕量:由於使用 JSON 格式,JWT 很輕巧且易於處理。 - 自包含:Payload 中包含了所有必要的信息,減少了對伺服器的查詢次數。 - 可擴展:Payload 可以包含自定義的 Claim,滿足各種應用場景需求。 - 跨平台:由於使用 JSON,JWT 可以在不同的編程語言和環境中輕松解析。 然而,JWT 也有一些注意事項: - 安全性:只有在使用適當的加密和簽名機制的情況下,JWT 才能確保數據的安全性。 - 處理過期:JWT 本身不提供自動過期處理,需要手動實現或配合其他機制來處理 Token 的過期。 - 敏感信息:避免將敏感信息直接存儲在 JWT 中,建議只存儲不敏感的元數據。 JWT 是一種靈活且可擴展的身份驗證和授權機制,它適用於各種 Web 應用程序和服務之間的身份驗證和信息傳遞。 ### Cookie、Session、Token與JWT Cookie、Session、Token和JWT(JSON Web Token)是在 Web 應用程序中常用的身份驗證和授權機制。以下是它們之間的主要區別: 1. **Cookie**: - Cookie 是在客戶端(瀏覽器)保存的小型文本文件,由伺服器通過 HTTP 響應設置在客戶端。 - 用於在客戶端和伺服器之間保持狀態,常用於保存會話信息、登錄狀態等。 - 客戶端每次發送請求時都會自動附帶相關的 Cookie,以便伺服器識別用戶。 2. **Session**: - Session 是在伺服器端保存的用戶會話信息。 - 通常使用一個唯一的 Session ID 标識用戶,該 ID 會存儲在 Cookie 中或者通過 URL 參數傳遞。 - Session 用於在多個請求之間保持狀態,伺服器使用 Session ID 查找對應的會話數據。 3. **Token**: - Token 是一種輕量級的身份驗證機制,通常以數據的形式傳遞,可以是 JSON 格式或其他格式。 - Token 可以由伺服器簽發,用於識別和驗證用戶,客戶端在每次請求時都需要攜帶 Token。 - 常見的 Token 有 JWT、OAuth Token 等。 4. **JWT(JSON Web Token)**: - JWT 是一種常見的 Token 格式,使用 JSON 格式來表示 Token。 - JWT 包含用戶信息、到期時間(Expiration Time)等聲明(Claim),並使用密鑰簽名進行驗證,確保 Token 的真實性。 - 由於 JWT 自包含,伺服器無需保存會話狀態,因此可以實現無狀態的身份驗證,有助於伺服器的擴展性。 總體來說,Cookie 和 Session 是傳統的身份驗證和會話管理機制,需要在伺服器端保存狀態;而 Token 和 JWT 是較新的無狀態身份驗證機制,適用於分佈式系統和 API 調用等場景,可以實現更好的伸縮性和安全性。 ### createToken ```java /** * 创建令牌 * * @param loginUser 用户信息 * @return 令牌 */ public String createToken(LoginUser loginUser) { String token = IdUtils.fastUUID(); loginUser.setToken(token); setUserAgent(loginUser); refreshToken(loginUser); Map<String, Object> claims = new HashMap<>(); claims.put(Constants.LOGIN_USER_KEY, token); return createToken(claims); } ``` ## h2 [hutool](https://github.com/dromara/hutool/)