Spring Security簡介、使用與最佳實(shí)踐
一、如何理解 Spring Security?—— 核心思想
Spring Security 的核心是一個(gè)基于過濾器鏈(Filter Chain)的認(rèn)證和授權(quán)框架。不要把它想象成一個(gè)黑盒,而是一個(gè)可以高度定制和擴(kuò)展的安全衛(wèi)士。
- 認(rèn)證 (Authentication - “你是誰?”)
- 你進(jìn)入大樓,保安(Spring Security)要求你出示工牌和驗(yàn)證指紋(用戶名和密碼)。
- 保安檢查工牌和指紋庫,確認(rèn)你是員工“張三”(驗(yàn)證憑證)。
- 驗(yàn)證通過后,保安給你一張臨時(shí)門禁卡(Security Context / Token)。之后你在大樓里的活動(dòng),就靠這張卡來證明身份。
- 授權(quán) (Authorization - “你能做什么?”)
- 你拿著門禁卡,想進(jìn)入“財(cái)務(wù)室”。
- 財(cái)務(wù)室門前的讀卡器(Spring Security 的授權(quán)管理器)檢查你的卡權(quán)限。
- 發(fā)現(xiàn)你的卡只有“技術(shù)部”權(quán)限(角色:ROLE_DEV),而進(jìn)入“財(cái)務(wù)室”需要“財(cái)務(wù)部”權(quán)限(角色:ROLE_FINANCE)。
- 讀卡器亮起紅燈,拒絕訪問(拋出 AccessDeniedException)。
- 過濾器鏈 (Filter Chain) - “安保流程”
- 整個(gè)大廈有一套嚴(yán)密的安保流程,每個(gè)環(huán)節(jié)都有一個(gè)專門的保安負(fù)責(zé):
- 保安A:檢查你有沒有攜帶危險(xiǎn)品(CSRF 防護(hù)過濾器)。
- 保安B:檢查你的門禁卡是否有效(認(rèn)證過濾器)。
- 保安C:根據(jù)你的卡權(quán)限,決定你能去哪個(gè)房間(授權(quán)過濾器)。
- 你的請(qǐng)求(你想進(jìn)入某個(gè)房間)必須按順序經(jīng)過所有這些保安的檢查,任何一個(gè)環(huán)節(jié)失敗都會(huì)被拒絕。Spring Security 就是這一整套保安流程的集合。
二、如何在 Java 項(xiàng)目中使用?—— 實(shí)戰(zhàn)步驟
我們以一個(gè)最常見的場(chǎng)景為例:使用數(shù)據(jù)庫存儲(chǔ)用戶信息,并實(shí)現(xiàn)基于角色(Role)的頁面訪問控制。
環(huán)境準(zhǔn)備
- 創(chuàng)建項(xiàng)目:使用 Spring Initializr 創(chuàng)建一個(gè)新的 Spring Boot 項(xiàng)目,添加以下依賴:
- Spring Web
- Spring Security
- Spring Data JPA
- MySQL Driver (或 H2 Database 用于測(cè)試)
- Thymeleaf (可選,用于前端頁面)
- 配置數(shù)據(jù)庫:在 application.properties中配置數(shù)據(jù)源。
- spring.datasource.url=jdbc:mysql://localhost:3306/your_database spring.datasource.username=root spring.datasource.password=your_password spring.jpa.hibernate.ddl-auto=update
步驟一:定義用戶和角色實(shí)體 (Entity)
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name; // e.g., "ROLE_USER", "ROLE_ADMIN"
// Constructors, getters, setters...
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private Boolean enabled;
@ManyToMany(fetch = FetchType.EAGER) // 急加載,獲取用戶時(shí)立刻獲取角色
@JoinTable(
name = "users_roles",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles = new HashSet<>();
// Constructors, getters, setters...
}步驟二:實(shí)現(xiàn) UserDetailsService - 連接數(shù)據(jù)庫和Security的橋梁
這是最關(guān)鍵的接口。Spring Security 會(huì)調(diào)用它的 loadUserByUsername 方法來根據(jù)用戶名獲取用戶信息。
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository; // 你的JPA Repository
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 從數(shù)據(jù)庫查詢用戶
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
// 2. 將數(shù)據(jù)庫中的 User 對(duì)象,轉(zhuǎn)換為 Spring Security 認(rèn)識(shí)的 UserDetails 對(duì)象
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.disabled(!user.getEnabled())
.authorities(getAuthorities(user.getRoles())) // 這里設(shè)置權(quán)限/角色
.build();
}
// 將數(shù)據(jù)庫中的 Role 集合轉(zhuǎn)換為 Spring Security 認(rèn)識(shí)的 GrantedAuthority 集合
private Collection<? extends GrantedAuthority> getAuthorities(Set<Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}步驟三:安全配置類 (Security Configuration) - 核心配置
這是你定義安全規(guī)則的地方:哪些URL需要保護(hù)?誰可以訪問?登錄/登出怎么處理?
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private MyUserDetailsService userDetailsService;
// 配置密碼編碼器。Spring Security 強(qiáng)制要求密碼必須加密。
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 授權(quán)配置:定義哪些請(qǐng)求需要什么權(quán)限
.authorizeHttpRequests(authz -> authz
.requestMatchers("/", "/home", "/public/**").permitAll() // 允許所有人訪問
.requestMatchers("/admin/**").hasRole("ADMIN") // 只有ADMIN角色可以訪問
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER或ADMIN角色可以訪問
.anyRequest().authenticated() // 所有其他請(qǐng)求都需要認(rèn)證(登錄)
)
// 表單登錄配置
.formLogin(form -> form
.loginPage("/login") // 自定義登錄頁面路徑
.permitAll() // 允許所有人訪問登錄頁面
.defaultSuccessUrl("/dashboard") // 登錄成功后的默認(rèn)跳轉(zhuǎn)頁面
)
// 登出配置
.logout(logout -> logout
.permitAll()
.logoutSuccessUrl("/login?logout") // 登出成功后跳轉(zhuǎn)的頁面
)
// 記住我功能
.rememberMe(remember -> remember
.key("uniqueAndSecret") // 用于對(duì) token 進(jìn)行哈希的密鑰
.tokenValiditySeconds(86400) // 記住我有效期為1天
)
// 異常處理:權(quán)限不足時(shí)
.exceptionHandling(handling -> handling
.accessDeniedPage("/access-denied")
)
// 關(guān)鍵:配置自定義的 UserDetailsService
.userDetailsService(userDetailsService);
return http.build();
}
}步驟四:控制器和視圖 (Controller & View)
@Controller
public class HomeController {
@GetMapping("/")
public String home() {
return "home"; // home.html
}
@GetMapping("/admin/dashboard")
public String adminDashboard() {
return "admin-dashboard";
}
@GetMapping("/user/dashboard")
public String userDashboard() {
return "user-dashboard";
}
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/access-denied")
public String accessDenied() {
return "access-denied";
}
}在 templates/login.html 中,你需要一個(gè)符合 Spring Security 約定的表單:
<form th:action="@{/login}" method="post">
<input type="text" name="username" placeholder="Username"/>
<input type="password" name="password" placeholder="Password"/>
<input type="checkbox" name="remember-me"/> Remember Me
<button type="submit">Login</button>
</form>注意:th:action="@{/login}"、name="username"、name="password" 都是默認(rèn)的,不能隨意更改。
步驟五:在視圖和控制器中獲取用戶信息
// 在Controller中獲取當(dāng)前用戶
@GetMapping("/profile")
public String profile(Model model) {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails) principal).getUsername();
model.addAttribute("username", username);
}
return "profile";
}
// 更優(yōu)雅的方式:使用 Principal 對(duì)象直接注入
@GetMapping("/profile2")
public String profile2(Principal principal, Model model) {
model.addAttribute("username", principal.getName());
return "profile";
}在 Thymeleaf 模板中,可以直接使用 Securty 表達(dá)式:
<div th:if="${#authorization.expression('isAuthenticated()')}">
<p>Welcome, <span th:text="${#authentication.name}">User</span>!</p>
<p>You have roles: <span th:text="${#authentication.authorities}">[]</span></p>
</div>總結(jié)與最佳實(shí)踐
- 密碼必須編碼:永遠(yuǎn)不要用明文存儲(chǔ)密碼。
BCryptPasswordEncoder是當(dāng)前的首選。 - 最小權(quán)限原則:只授予用戶完成其工作所必需的最小權(quán)限。
- 縱深防御:不要只依賴 Spring Security。在服務(wù)層方法上也可以使用
@PreAuthorize注解進(jìn)行二次校驗(yàn)。
@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")
public User getUserById(Long userId) { ... }- 保持更新:Spring Security 本身和其依賴庫可能會(huì)發(fā)現(xiàn)漏洞,定期更新版本。
到此這篇關(guān)于Spring Security使用與最佳實(shí)踐的文章就介紹到這了,更多相關(guān)Spring Security使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java中Spring Security的使用及最佳實(shí)踐
- springSecurity使用實(shí)戰(zhàn)指南
- Spring?Security6.3.x的使用指南與注意事項(xiàng)
- SpringSecurity?鑒權(quán)與授權(quán)的具體使用
- Spring Security常用配置的使用解讀
- SpringSecurity當(dāng)中的CSRF防范使用詳解
- springboot?security使用jwt認(rèn)證方式
- springboot security快速使用示例詳解
- 使用多個(gè)servlet時(shí)Spring security需要指明路由匹配策略問題
相關(guān)文章
SpringBoot 使用Mongo的GridFs實(shí)現(xiàn)分布式文件存儲(chǔ)操作
這篇文章主要介紹了Spring Boot 使用Mongo的GridFs實(shí)現(xiàn)分布式文件存儲(chǔ)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
200行Java代碼如何實(shí)現(xiàn)依賴注入框架詳解
依賴注入對(duì)大家來說應(yīng)該都不陌生,下面這篇文章主要給大家介紹了關(guān)于利用200行Java代碼如何實(shí)現(xiàn)依賴注入框架的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-05-05
Java基礎(chǔ)將Bean屬性值放入Map中的實(shí)例
這篇文章主要介紹了Java基礎(chǔ)將Bean屬性值放入Map中的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-07-07
Spring Boot Admin郵件警報(bào)整合過程解析
這篇文章主要介紹了Spring Boot Admin郵件警報(bào)整合過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
SpringBoot中的統(tǒng)一異常處理詳細(xì)解析
這篇文章主要介紹了SpringBoot中的統(tǒng)一異常處理詳細(xì)解析,該注解可以把異常處理器應(yīng)用到所有控制器,而不是單個(gè)控制器,借助該注解,我們可以實(shí)現(xiàn):在獨(dú)立的某個(gè)地方,比如單獨(dú)一個(gè)類,定義一套對(duì)各種異常的處理機(jī)制,需要的朋友可以參考下2024-01-01
SpringBoot整合MyBatis和MyBatis-Plus請(qǐng)求后不打印sql日志的問題解決
本文主要介紹了SpringBoot整合MyBatis和MyBatis-Plus請(qǐng)求后不打印sql日志的問題解決文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07

