# 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/)