Spring Security+MyBatis實(shí)現(xiàn)從數(shù)據(jù)庫(kù)動(dòng)態(tài)查詢權(quán)限的完整實(shí)現(xiàn)方案
一、整體思路
Spring Security 的核心接口:
UserDetailsService:根據(jù)用戶名加載用戶信息(密碼、權(quán)限等)UserDetails:用戶對(duì)象,包含密碼和GrantedAuthorityGrantedAuthority:權(quán)限標(biāo)識(shí)(如ROLE_ADMIN或system:user:delete)
流程:
登錄 → UserDetailsService.loadUserByUsername() → 查數(shù)據(jù)庫(kù) → 返回 UserDetails → Security自動(dòng)比對(duì)密碼 → 把權(quán)限存入SecurityContext。
二、數(shù)據(jù)庫(kù)表設(shè)計(jì)(最簡(jiǎn)但完整)
-- 用戶表 CREATE TABLE `user` ( `id` int PRIMARY KEY AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(255) NOT NULL, -- BCrypt加密 `enabled` tinyint DEFAULT 1 ); -- 角色表 CREATE TABLE `role` ( `id` int PRIMARY KEY AUTO_INCREMENT, `role_code` varchar(50) NOT NULL -- ROLE_ADMIN, ROLE_USER ); -- 權(quán)限表(細(xì)粒度權(quán)限) CREATE TABLE `permission` ( `id` int PRIMARY KEY AUTO_INCREMENT, `perm_code` varchar(100) NOT NULL -- user:add, user:delete, order:view ); -- 用戶-角色關(guān)聯(lián) CREATE TABLE `user_role` ( `user_id` int, `role_id` int ); -- 角色-權(quán)限關(guān)聯(lián) CREATE TABLE `role_permission` ( `role_id` int, `perm_id` int );
示例數(shù)據(jù):
-- 用戶 admin / 123456(實(shí)際用BCrypt) INSERT INTO `user` VALUES (1, 'admin', '$2a$10$NkMwpJ7UoWjQlqWjQlqWjQ', 1); -- 角色 INSERT INTO `role` VALUES (1, 'ROLE_ADMIN'), (2, 'ROLE_USER'); -- 權(quán)限 INSERT INTO `permission` VALUES (1, 'user:list'), (2, 'user:delete'); -- 管理員擁有所有角色和權(quán)限 INSERT INTO `user_role` VALUES (1,1); INSERT INTO `role_permission` VALUES (1,1),(1,2);
三、MyBatis 查詢權(quán)限
1. 實(shí)體類
public class User {
private Integer id;
private String username;
private String password;
private Boolean enabled;
// getter/setter
}2. Mapper 接口
@Mapper
public interface UserMapper {
@Select("SELECT id, username, password, enabled FROM user WHERE username = #{username}")
User findByUsername(String username);
@Select("SELECT r.role_code FROM user_role ur " +
"JOIN role r ON ur.role_id = r.id " +
"WHERE ur.user_id = #{userId}")
List<String> findRolesByUserId(Integer userId);
@Select("SELECT p.perm_code FROM user_role ur " +
"JOIN role_permission rp ON ur.role_id = rp.role_id " +
"JOIN permission p ON rp.perm_id = p.id " +
"WHERE ur.user_id = #{userId}")
List<String> findPermissionsByUserId(Integer userId);
}四、核心:實(shí)現(xiàn) UserDetailsService
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1. 查用戶
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用戶不存在: " + username);
}
// 2. 查角色(ROLE_xxx)
List<String> roles = userMapper.findRolesByUserId(user.getId());
// 3. 查權(quán)限(user:add 等)
List<String> permissions = userMapper.findPermissionsByUserId(user.getId());
// 4. 合并權(quán)限 + 角色
// 注意:Spring Security 中角色需要加 ROLE_ 前綴
Set<GrantedAuthority> authorities = new HashSet<>();
for (String role : roles) {
// 如果role已經(jīng)是 ROLE_ADMIN 格式,直接加;否則自動(dòng)補(bǔ)
if (role.startsWith("ROLE_")) {
authorities.add(new SimpleGrantedAuthority(role));
} else {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
}
for (String perm : permissions) {
authorities.add(new SimpleGrantedAuthority(perm));
}
// 5. 返回 UserDetails
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getEnabled(), // 是否啟用
true, // 賬號(hào)未過(guò)期
true, // 憑證未過(guò)期
true, // 賬號(hào)未鎖定
authorities
);
}
}五、Spring Security 配置類
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // 密碼加密
}
@Bean
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return http.getSharedObject(AuthenticationManagerBuilder.class)
.authenticationProvider(provider)
.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
// 公開(kāi)接口
.requestMatchers("/login", "/register").permitAll()
// 權(quán)限控制:需要具體權(quán)限
.requestMatchers("/user/list").hasAuthority("user:list")
.requestMatchers("/user/delete").hasAuthority("user:delete")
// 角色控制
.requestMatchers("/admin/**").hasRole("ADMIN")
// 其他請(qǐng)求需要認(rèn)證
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll()
)
.logout(LogoutConfigurer::permitAll)
.csrf(csrf -> csrf.disable()); // 根據(jù)實(shí)際情況開(kāi)啟
return http.build();
}
}六、在 Controller 中使用
@RestController
public class UserController {
@GetMapping("/user/list")
public String listUsers() {
return "用戶列表(需要 user:list 權(quán)限)";
}
@GetMapping("/user/delete")
public String deleteUser() {
return "刪除用戶(需要 user:delete 權(quán)限)";
}
@GetMapping("/current-permissions")
public Authentication getCurrentAuth() {
return SecurityContextHolder.getContext().getAuthentication();
}
}七、常見(jiàn)問(wèn)題與優(yōu)化
1. 緩存權(quán)限查詢
不要在每次請(qǐng)求時(shí)重復(fù)查數(shù)據(jù)庫(kù)(Security 本身一次登錄后權(quán)限存session)。
如果一定要?jiǎng)討B(tài)刷新權(quán)限,可以:
// 修改權(quán)限后,重新加載 SecurityContextHolder.getContext().setAuthentication(updatedAuth);
2. 使用 @PreAuthorize 更優(yōu)雅
@PreAuthorize("hasAuthority('user:delete')")
@DeleteMapping("/user/{id}")
public String delete(@PathVariable Long id) {
return "刪除成功";
}需要開(kāi)啟:
@EnableGlobalMethodSecurity(prePostEnabled = true)
3. 性能優(yōu)化:一次查詢返回用戶+角色+權(quán)限
用 MyBatis 聯(lián)表或 ResultMap 一次性查出來(lái),避免 N+1 問(wèn)題。
<resultMap id="UserWithAuth" type="com.example.User">
<id column="id" property="id"/>
<collection property="roles" ofType="string" select="..."/>
</resultMap>八、總結(jié):這套方案的特點(diǎn)
| 方面 | 說(shuō)明 |
|---|---|
| ? 生產(chǎn)可用 | 絕大多數(shù)項(xiàng)目真實(shí)采用 |
| ? 靈活 | 權(quán)限可存數(shù)據(jù)庫(kù),隨時(shí)調(diào)整 |
| ? 標(biāo)準(zhǔn) | 完全遵循 Spring Security 接口設(shè)計(jì) |
| ?? 注意 | 密碼必須用 BCrypt 等單向加密 |
| ?? 注意 | 角色必須加 ROLE_ 前綴(除非自定義) |
以上就是Spring Security+MyBatis實(shí)現(xiàn)從數(shù)據(jù)庫(kù)動(dòng)態(tài)查詢權(quán)限的完整實(shí)現(xiàn)方案的詳細(xì)內(nèi)容,更多關(guān)于Spring Security MyBatis數(shù)據(jù)庫(kù)動(dòng)態(tài)查詢權(quán)限的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- springboot+springsecurity+mybatis+JWT+Redis?實(shí)現(xiàn)前后端離實(shí)戰(zhàn)教程
- springboot整合springsecurity與mybatis-plus的簡(jiǎn)單實(shí)現(xiàn)
- 一篇文章帶你搞定 springsecurity基于數(shù)據(jù)庫(kù)的認(rèn)證(springsecurity整合mybatis)
- JavaWeb開(kāi)發(fā)之Spring+SpringMVC+MyBatis+SpringSecurity+EhCache+JCaptcha 完整Web基礎(chǔ)框架
相關(guān)文章
String.intern()作用與常量池關(guān)系示例解析
這篇文章主要為大家介紹了String.intern()作用與常量池關(guān)系示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
在 Spring Boot 中實(shí)現(xiàn)異常處理最佳實(shí)踐
本文介紹如何在Spring Boot中實(shí)現(xiàn)異常處理,涵蓋核心概念、實(shí)現(xiàn)方法、與先前查詢的集成、性能分析、常見(jiàn)問(wèn)題和最佳實(shí)踐,感興趣的朋友一起看看吧2025-04-04
Spring Bean創(chuàng)建和循環(huán)依賴
這篇文章主要介紹了Spring Bean創(chuàng)建和循環(huán)依賴,講述了Spring容器中?Bean?的創(chuàng)建過(guò)程已經(jīng)主要的方法,另外也著重分析了循環(huán)依賴的問(wèn)題,需要的小伙伴可以參考一下2022-05-05
Java transient關(guān)鍵字與序列化操作實(shí)例詳解
這篇文章主要介紹了Java transient關(guān)鍵字與序列化操作,結(jié)合實(shí)例形式詳細(xì)分析了java序列化操作相關(guān)實(shí)現(xiàn)方法與操作注意事項(xiàng),需要的朋友可以參考下2019-09-09
Mybatis實(shí)現(xiàn)一對(duì)一查詢映射處理
MyBatis是一種流行的Java持久化框架,它提供了靈活而強(qiáng)大的查詢映射功能,本文主要介紹了Mybatis實(shí)現(xiàn)一對(duì)一查詢映射處理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08
解決Java的InputMismatchException異常
這篇文章介紹了解決Java的InputMismatchException異常的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12
java 實(shí)現(xiàn)計(jì)數(shù)排序和桶排序?qū)嵗a
這篇文章主要介紹了java 實(shí)現(xiàn)計(jì)數(shù)排序和桶排序?qū)嵗a的相關(guān)資料,需要的朋友可以參考下2017-02-02

