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)過濾器)。
- 你的請求(你想進(jìn)入某個(gè)房間)必須按順序經(jīng)過所有這些保安的檢查,任何一個(gè)環(huán)節(jié)失敗都會被拒絕。Spring Security 就是這一整套保安流程的集合。
二、如何在 Java 項(xiàng)目中使用?—— 實(shí)戰(zhàn)步驟
我們以一個(gè)最常見的場景為例:使用數(shù)據(jù)庫存儲用戶信息,并實(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 用于測試)
- 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 會調(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 對象,轉(zhuǎn)換為 Spring Security 認(rèn)識的 UserDetails 對象
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)識的 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)配置:定義哪些請求需要什么權(quán)限
.authorizeHttpRequests(authz -> authz
.requestMatchers("/", "/home", "/public/**").permitAll() // 允許所有人訪問
.requestMatchers("/admin/**").hasRole("ADMIN") // 只有ADMIN角色可以訪問
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") // USER或ADMIN角色可以訪問
.anyRequest().authenticated() // 所有其他請求都需要認(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") // 用于對 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 對象直接注入
@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)不要用明文存儲密碼。
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 本身和其依賴庫可能會發(fā)現(xiàn)漏洞,定期更新版本。
到此這篇關(guān)于Spring Security使用與最佳實(shí)踐的文章就介紹到這了,更多相關(guān)Spring Security使用內(nèi)容請搜索腳本之家以前的文章或繼續(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)文章
代理角色java設(shè)計(jì)模式之靜態(tài)代理詳細(xì)介紹
查了好多資料,發(fā)現(xiàn)還是不全,干脆自己整理吧,至少保證在我的做法正確的,以免誤導(dǎo)讀者,也是給自己做個(gè)記錄吧!2013-05-05
java實(shí)現(xiàn)輕量型http代理服務(wù)器示例
這篇文章主要介紹了java實(shí)現(xiàn)輕量型http代理服務(wù)器示例,需要的朋友可以參考下2014-04-04
SpringMVC使用ResponseEntity實(shí)現(xiàn)文件上傳下載
這篇文章主要為大家介紹了SpringMVC使用ResponseEntity實(shí)現(xiàn)文件上傳下載,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
Java的HttpClient中使用POST請求傳遞參數(shù)兩種常見方式
Apache HttpClient為開發(fā)者提供了豐富的接口,用于管理HTTP連接并執(zhí)行請求,下面這篇文章主要介紹了Java的HttpClient中使用POST請求傳遞參數(shù)兩種常見方式,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-06-06
如何利用Spring把元素解析成BeanDefinition對象
這篇文章主要介紹了如何利用Spring把元素解析成BeanDefinition對象,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08
Java的字符讀寫類CharArrayReader和CharArrayWriter使用示例
這篇文章主要介紹了Java的字符讀寫類CharArrayReader和CharArrayWriter使用示例,兩個(gè)類分別繼承于Reader和Writer,需要的朋友可以參考下2016-06-06
java.lang.NoClassDefFoundError錯(cuò)誤解決辦法
這篇文章主要介紹了java.lang.NoClassDefFoundError錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-06-06

