登錄相關
- 權限管理模塊(基礎版)
- 模塊設計與實現
- 優化點:
- 前后端用戶驗證
- 實現方式
- 常見的攻擊手段及防御手段
權限管理模塊(基礎版)
RBAC(Role-Base Access Control,基于角色的訪問控制):是權限管理的常用方案。
核心:通過用戶 - 角色 - 權限的三層關聯,靈活分配和管理權限。并不將用戶和全新啊直接綁定。適合多用戶多權限場景。
模塊設計與實現
以Java + Spring Boot + Spring Security為例
1、實體設計(庫表設計)
需要具備3個實體(用戶、角色、權限),以及2個關系表(用戶-角色、角色-權限)。
// 用戶實體
@Data
public class User {private Long id;private String username;private String password; // 加密存儲(如BCrypt)private Integer status; // 1-啟用,0-禁用// 關聯角色(一對多,通過user_role表)private List<Role> roles;
}// 角色實體
@Data
public class Role {private Long id;private String name; // 角色名稱(如“系統管理員”)private String code; // 角色標識(如“ROLE_ADMIN”,用于Spring Security)// 關聯權限(一對多,通過role_permission表)private List<Permission> permissions;
}// 權限實體
@Data
public class Permission {private Long id;private String name; // 權限名稱(如“刪除用戶”)private String code; // 權限標識(如“user:delete”,用于權限校驗)private Integer type; // 1-菜單,2-按鈕(接口)private String url; // 接口URL(如“/api/user/delete”)private Long parentId; // 父權限ID(用于菜單層級)
}
-- 用戶-角色關聯表
CREATE TABLE `user_role` (`id` bigint NOT NULL AUTO_INCREMENT,`user_id` bigint NOT NULL,`role_id` bigint NOT NULL,PRIMARY KEY (`id`),KEY `idx_user` (`user_id`),KEY `idx_role` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 角色-權限關聯表
CREATE TABLE `role_permission` (`id` bigint NOT NULL AUTO_INCREMENT,`role_id` bigint NOT NULL,`perm_id` bigint NOT NULL,PRIMARY KEY (`id`),KEY `idx_role` (`role_id`),KEY `idx_perm` (`perm_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2、Mapper層設計,提供數據庫基本操作。
public interface UserMapper {// 查詢用戶User selectByUsername(String username);// 查詢用戶的角色List<Role> selectRolesByUserId(Long userId);// 查詢角色的權限List<Permission> selectPermissionsByRoleId(Long roleId);
}
public Interface RolePermissionMapper() {// CRUD
}
3、Service實現,提供查詢用戶權限,用戶與角色,角色與權限的分配與修改。
用戶登錄時,查詢權限,用于后續權限校驗。
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<String> login(String username,String password) {// 校驗邏輯,判斷用戶是否存在return 用戶信息(包含權限列表)}// 根據用戶名查詢用戶及其關聯的角色和權限public User getUserWithRolesAndPermissions(String username) {User user = userMapper.selectByUsername(username);if (user == null) {throw new UsernameNotFoundException("用戶不存在");}// 查詢用戶關聯的角色List<Role> roles = userMapper.selectRolesByUserId(user.getId());// 為每個角色查詢關聯的權限for (Role role : roles) {List<Permission> permissions = userMapper.selectPermissionsByRoleId(role.getId());role.setPermissions(permissions);}user.setRoles(roles);return user;}
}
// 管理用戶-角色 角色-權限
@Service
public class RolePermissionService {@Autowiredprivate RolePermissionMapper rolePermissionMapper;// 給角色分配權限(先刪除舊關聯,再插入新關聯)@Transactionalpublic void assignPermissions(Long roleId, List<Long> permIds) {// 1. 刪除該角色已有的所有權限關聯rolePermissionMapper.deleteByRoleId(roleId);// 2. 插入新的權限關聯if (permIds != null && !permIds.isEmpty()) {List<RolePermission> list = permIds.stream().map(permId -> {RolePermission rp = new RolePermission();rp.setRoleId(roleId);rp.setPermId(permId);return rp;}).collect(Collectors.toList());rolePermissionMapper.batchInsert(list);}}
}
3、權限校驗(限制到接口層面)
結合Spring Security實現接口訪問時單獨權限校驗。
原理:
- 將用戶權限加載到上下文中。并通過注解或配置文件進行攔截。
- Spring Security的授權流程通過 http.authorizeRequests() 對web請求進行授權保護。授權決策由 AccessDecisionManager 進行,它會對比當前訪問資源所需的權限信息和用戶信息中的權限信息。
步驟:
- 加載用戶到Spring Security:
實現UserDetailsService,將用戶信息(包含角色和權限)轉換為Security可識別的userDetails。
UserDetailsService:從數據庫中取出用戶的賬號密碼。
@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserService userService;// 將用戶信息以及對應的角色,權限交由Security管理@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 查詢用戶及其角色、權限User user = userService.getUserWithRolesAndPermissions(username);if (user == null) {throw new UsernameNotFoundException("用戶不存在");}// 提取角色(需加前綴"ROLE_",符合Security規范)List<String> roleCodes = user.getRoles().stream().map(role -> "ROLE_" + role.getCode()).collect(Collectors.toList());// 提取權限標識(如"user:delete")List<String> permCodes = user.getRoles().stream().flatMap(role -> role.getPermissions().stream()).map(Permission::getCode).collect(Collectors.toList());// 合并角色和權限(Security將其統一視為"權限")List<String> authorities = new ArrayList<>();authorities.addAll(roleCodes);authorities.addAll(permCodes);// 返回Security用戶對象。提供參數由Security轉換用戶與權限return org.springframework.security.core.userdetails.User.withUsername(user.getUsername()).password(user.getPassword()).authorities(authorities) // 角色和權限都作為authority.accountLocked(user.getStatus() == 0) // 狀態為0時鎖定.build();}
}
- 權限校驗
- 注解方式校驗:在Controller方法上使用@PerAuthorize注解,指定所需要的權限。
@RestController
@RequestMapping("/api/user")
public class UserController {// 需擁有"user:delete"權限才能訪問@PreAuthorize("hasAuthority('user:delete')")@DeleteMapping("/{id}")public Result deleteUser(@PathVariable Long id) {// 業務邏輯return Result.success();}// 需擁有"ROLE_ADMIN"角色才能訪問@PreAuthorize("hasRole('ADMIN')") // 自動拼接"ROLE_"前綴@GetMapping("/list")public Result getUserList() {// 業務邏輯return Result.success();}
}
- 配置列中通過URL匹配指定權限:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // 開啟@PreAuthorize注解
public class SecurityConfig {@Autowiredprivate CustomUserDetailsService userDetailsService;@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth -> auth// 訪問"/api/admin/**"需ADMIN角色.requestMatchers("/api/admin/**").hasRole("ADMIN")// 訪問"/api/user/delete"需"user:delete"權限.requestMatchers("/api/user/delete/**").hasAuthority("user:delete")// 其他接口允許認證用戶訪問.anyRequest().authenticated());return http.build();}
}
4、前端權限適配(動態加載)
根據用戶權限展示菜單和按鈕。
原理:
- 登陸后,獲取當前用戶權限列表(后端返回)
- 渲染菜單/按鈕時,只顯示用戶權限內的菜單/按鈕。
// 1. 登錄后獲取用戶權限
async function login(username, password) {const res = await api.login({ username, password });const { permissions } = res.data; // 后端返回的權限列表(如["user:delete", "menu:user"])localStorage.setItem('permissions', JSON.stringify(permissions));
}// 2. 權限判斷工具函數
function hasPermission(permission) {const permissions = JSON.parse(localStorage.getItem('permissions') || '[]');return permissions.includes(permission);
}// 3. 動態渲染按鈕(使用自定義指令)
Vue.directive('perm', {inserted(el, binding) {if (!hasPermission(binding.value)) {el.remove(); // 無權限則移除按鈕}}
});// 4. 在模板中使用
<template><button v-perm="'user:delete'">刪除用戶</button>
</template>
優化點:
- 角色繼承:支持角色間的父子關系,需要在role表中添加Parent_id字段。
- 數據權限:在功能權限基礎上,控制數據可見范圍(員工只能查看本人數據)。可通過在權限表中添加data_scope字段(限制全部/本部門/個人),結合SQL攔截器實現。
- 權限緩存:可以將用戶權限緩存到Redis中(key用戶名,value權限列表),減少數據庫壓力,提高性能。(注意每次修改權限后需要考慮緩存一致性)。
前后端用戶驗證
交互中保證同一用戶的核心:身份標識與驗證機制。通過不同技術保證每次請求來自同一個用戶,防止身份假冒以及會話混亂。
實現方式
1、 Cookie + Session的傳統方案(適用于Web端)
最早且最成熟的方案,依賴服務器的會話存儲和客戶端的Cookie傳遞標識。
核心流程:
- 用戶登錄驗證:
- 登錄驗證通過后,在服務器內存/數據庫中創建一個session(包含用戶標識登錄狀態等信息),生成唯一的SESSIONID。
- 后端通過Set-Cookie響應頭返回客戶端,客戶端自動將其保存到Cookie中(通常設置HttpOnly和Secure屬性)。
- 后續請求身份確認
- 客戶端每次請求時,瀏覽器自動子啊請求頭中Cookie字段按中攜帶SessionID。
- 后端通過SessionID查詢服務器的Session數據,若存在且有效(未過期),則任務是同一用戶。
相關配置:
- HttpOnly: true:禁止 JavaScript 讀取 Cookie,防止 XSS 攻擊竊取SessionID;
- Secure: true:僅在 HTTPS 協議下傳輸 Cookie,避免明文泄露;
- SameSite: Strict/Lax:限制 Cookie 跨域發送,防止 CSRF 攻擊;
- 會話超時機制:如 30 分鐘無操作自動失效,降低SessionID被盜用的風險。
2、基于Token的無狀態方案(適用于多端應用)
Token方案不依賴服務器存儲會話,而是通過加密令牌傳遞用戶信息,更適合前后端分離,移動端、小程序等場景。
核心流程:
- 生成Token:
- 用戶登陸驗證通過后,后端使用密鑰生成一個加密token(常見為JWT格式),包含用戶Id、過期時間、簽名等信息(不包含敏感數據)。
- 后端將Token返回給客戶端,客戶端存儲在LocalStorage、SessionStorage或App本地存儲。
- 攜帶Token請求
- 客戶端每次請求時,在HTTP請求頭中(如Authorization:Bearer<.token>) 中攜帶token。
- 后端驗證Token簽名(確保未篡改)和過期時間,解析出用戶Id,確認是同一用戶。
JWT(JSON Web Token)示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOjEsIm5hbWUiOiJKb2huIiwiZXhwIjoxNzIwMDAwMDAwfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
第一部分:算法聲明(HS256);
第二部分:用戶信息(用戶 ID、過期時間);
第三部分:簽名(用密鑰生成,確保內容未被篡改)。
相關配置:
- 短期有效期:Token 過期時間設為 15-30 分鐘,降低被盜用后的風險;
- 刷新 Token 機制:同時返回access_token(短期)和refresh_token(長期,如 7 天),過期后用refresh_token重新獲取access_token,避免頻繁登錄;
- Token 黑名單:用戶登出或密碼修改時,將舊 Token 加入黑名單(Redis 存儲),強制失效。
3、基于設備指紋與生物識別的增強方案(高安全性場景)
在金融、支付等安全性高的場景中,需要結合設備信息或生物特性輔助驗證"同一用戶”。
- 設備指紋驗證:
- 后端收集客戶端信息(CPU信息、操作系統等),生成唯一“設備指紋”。
- 登錄時,將用戶ID+設備指紋綁定,后續請求若設備指紋不符(如同一賬號在新設備登錄),觸發二次驗證(如短信驗證)。
- 生物識別輔助:
- 移動端App可結合指紋識別,面部識別,通過后才允許攜帶Token請求敏感接口。
- 即使Token泄露,沒有生物特征同樣無法完成操作。
4、跨域場景下身份有效。
當前后端域名不同,需要配置確保身份標識能跨域傳遞。
- Cookie跨域:
- 后端設置Access-Control-Allow-Credentials: true響應頭;
- 前端請求時攜帶credentials: 'include’參數(如 Axios 配置);
- Cookie 設置domain: .parent.com(主域名一致時),允許子域名共享。
- Token跨域
- 無跨域限制,只在請求投中正確攜帶Token即可(不受Cookie同源策略影響)。
補充:
Cookie同源策略:是瀏覽器的一種安全機制,用于防止不同源的網頁之間相互訪問數據,從而保護用戶信息的安全。所謂“同源”,指的是兩個網頁的協議、域名和端口都相同。
作用:
同源策略的主要目的是防止惡意網站竊取用戶數據。例如,如果用戶登錄了一個銀行網站A,然后又訪問了另一個網站B,如果沒有同源策略,網站B可以讀取網站A的Cookie,從而獲取用戶的敏感信息。
常見的攻擊手段及防御手段
針對身份盜用的典型攻擊(XSS、CSRF、重放攻擊等),需要針對性防護。
1、防御XSS攻擊(防止標識被竊取)
XSS攻擊通過注入惡意腳本竊取前端存儲的標識(如LocalStorage中的Token,document.cookie)。
預防:
- 前端輸入過濾:對用戶輸入內容(評論、表單)進行HTML轉義(如>轉義為& lt/ ); 使用框架自帶的安全渲染(如React的JSX自動轉義,VUE的v-text)。
- 后端輸出編碼:返回給前端的數據中,對HTML/JS特殊字符編碼,避免直接渲染未處理的用戶輸入。
- 啟用CSP(內容安全策略):
2、防御CSRF攻擊(防止身份被濫用)
CSRF攻擊:利用用戶已登錄的身份,誘導用戶在第三方網站上發起惡意請求(如轉賬),防護措施:
- SameSite Cookie:通過SameSite-Strict限制Cookie僅在同域請求中攜帶,徹底阻止跨域CSRF。
- CSFR Token:對敏感操作(如表單操作、轉賬),后端生成隨機CSFR Token(綁定Session),前端表單攜帶該Token,后端驗證Token的有效性后才處理請求。
例:前端表單隱藏字段,后端對比 Session 中的 Token。
3、防止重放攻擊(防止標識被重復使用)
攻擊者竊取Token后重復發送請求(如重復下單),防護措施:
- Token短期有效+刷新時間
- assess_token(訪問令牌)有效期15~20分鐘,用于日常請求;
- refresh token(刷新令牌)有效期7天,用于過期后獲取access_token,且刷新時驗證設備信息(如設備信息);
- 每次刷新后,舊access_token立即失效,refresh_token采用“一次性”機制(使用后立即失效,返回新的refresh_token)。
- 請求時間戳+nonce
前端請求時攜帶timestamp(當前時間戳)和nonce(隨機字符串,僅用一次)后端驗證:- 時間戳與服務器時間 不差5分鐘(防止過期請求)。
- nonce在Redis記錄,已使用過的nonce直接拒絕(防止重復請求)。