
文章目录
-
- 前言
- 一、权限系统为什么容易越做越乱
-
- [1. 角色和权限混为一谈](#1. 角色和权限混为一谈)
- [2. 只做前端权限,不做后端权限](#2. 只做前端权限,不做后端权限)
- [3. 权限粒度设计不清晰](#3. 权限粒度设计不清晰)
- [4. 登录认证和权限授权耦合严重](#4. 登录认证和权限授权耦合严重)
- [5. JWT 用了,但没有打通权限模型](#5. JWT 用了,但没有打通权限模型)
- 二、权限系统要解决的核心问题
-
- [1. 确认当前用户是谁](#1. 确认当前用户是谁)
- [2. 确认当前用户能做什么](#2. 确认当前用户能做什么)
- [3. 接口必须有真正的安全边界](#3. 接口必须有真正的安全边界)
- [4. 支持后续角色和权限扩展](#4. 支持后续角色和权限扩展)
- [5. 前后端规则要一致](#5. 前后端规则要一致)
- [三、为什么大多数后台系统都适合用 RBAC](#三、为什么大多数后台系统都适合用 RBAC)
- 四、权限点应该怎么设计
-
- [1. 语义清晰](#1. 语义清晰)
- [2. 前后端都能共用](#2. 前后端都能共用)
- [3. 适合数据库持久化](#3. 适合数据库持久化)
- [4. 便于接口级权限控制](#4. 便于接口级权限控制)
- [五、JWT 在权限系统中的作用](#五、JWT 在权限系统中的作用)
- [六、Spring Boot + JWT + Spring Security 的整体实现流程](#六、Spring Boot + JWT + Spring Security 的整体实现流程)
-
- 第一步:用户登录
- 第二步:服务端校验身份
- 第三步:查询用户角色和权限
- [第四步:签发 JWT](#第四步:签发 JWT)
- [第五步:前端保存 token](#第五步:前端保存 token)
- [第六步:后端过滤器解析 token](#第六步:后端过滤器解析 token)
- 第七步:接口做权限校验
- 七、权限系统的数据模型怎么设计
-
- [1. 用户表 `sys_user`](#1. 用户表
sys_user) - [2. 角色表 `sys_role`](#2. 角色表
sys_role) - [3. 权限表 `sys_permission`](#3. 权限表
sys_permission) - [4. 用户角色关联表 `sys_user_role`](#4. 用户角色关联表
sys_user_role) - [5. 角色权限关联表 `sys_role_permission`](#5. 角色权限关联表
sys_role_permission) - [6. 菜单表 `sys_menu`](#6. 菜单表
sys_menu)
- [1. 用户表 `sys_user`](#1. 用户表
- 八、登录接口设计
- [九、JWT 工具类实现](#九、JWT 工具类实现)
- [十、JWT 过滤器接入 Spring Security](#十、JWT 过滤器接入 Spring Security)
- [十一、Spring Security 配置](#十一、Spring Security 配置)
- [十二、接口级权限控制为什么推荐 `@PreAuthorize`](#十二、接口级权限控制为什么推荐
@PreAuthorize) - [十三、RESTful 用户管理接口设计](#十三、RESTful 用户管理接口设计)
- 十四、统一返回结构的必要性
- [十五、Swagger 为什么应该尽早接入](#十五、Swagger 为什么应该尽早接入)
- 十六、前后端权限协同怎么做
- 十七、权限系统常见坑点
-
- [1. 只做菜单权限,不做接口权限](#1. 只做菜单权限,不做接口权限)
- [2. 把角色判断写死在业务代码里](#2. 把角色判断写死在业务代码里)
- [3. 权限码不统一](#3. 权限码不统一)
- [4. 把全部权限都塞进 JWT](#4. 把全部权限都塞进 JWT)
- [5. 没有 token 失效策略](#5. 没有 token 失效策略)
- [6. 没有区分功能权限和数据权限](#6. 没有区分功能权限和数据权限)
- 十八、数据权限怎么扩展
-
- [1. 在 Service 层加过滤条件](#1. 在 Service 层加过滤条件)
- [2. 在持久层统一注入数据范围条件](#2. 在持久层统一注入数据范围条件)
- [十九、JWT 失效与刷新策略](#十九、JWT 失效与刷新策略)
- 二十、权限系统为什么需要审计日志
- 二十一、一个建议的落地路径
- 总结
- 参考代码片段汇总
- 互动话题

前言
在企业级后台系统开发中,登录认证、接口鉴权、角色管理、权限控制几乎是绕不过去的核心基础能力。
很多项目初期只是简单做了一个登录接口,等业务逐渐复杂之后,就会暴露出一系列问题:
- 前端菜单能隐藏,但后端接口并没有真正做权限控制
- 角色和权限混用,导致后续扩展困难
- 接口命名混乱,权限点设计失控
- 前端和后端权限规则不一致
- JWT 只是"登录态载体",没有真正和权限体系打通
这篇文章就从实际项目角度出发,基于 Spring Boot + Spring Security + JWT + RBAC,带你完整搭建一套可落地的权限系统。
本文主要覆盖以下内容:
- 什么是 RBAC,为什么它适合后台管理系统
- JWT 在权限系统中的角色
- Spring Security 如何接入 JWT
- 如何实现接口级权限控制
- 前后端权限协同如何设计
- 权限系统常见坑点与扩展方向
一、权限系统为什么容易越做越乱
权限系统做乱,往往不是因为不会写代码,而是从建模开始就有问题。
常见错误主要有以下几类。
1. 角色和权限混为一谈
比如代码里直接写死:
- 管理员可以新增用户
- 普通用户只能查看自己的信息
- 审核员可以审批订单
一开始角色少的时候还能勉强维护,但随着角色越来越多,代码里会充满各种 if else,后续每新增一个角色,业务判断都会继续膨胀。
2. 只做前端权限,不做后端权限
很多项目把"权限控制"理解成:
- 菜单隐藏
- 按钮隐藏
- 页面不可见
这只能改善交互体验,不能形成真正的安全边界。只要知道接口地址,攻击者或者越权用户依然可能直接发请求访问接口。
3. 权限粒度设计不清晰
有的系统权限点是中文,有的是 URL,有的是动作名,最后会变成这种情况:
user:adduser_save/sys/user/create新增用户
这种混乱命名一旦出现,权限表、代码注解、前端判断就会越来越难维护。
4. 登录认证和权限授权耦合严重
很多项目登录后返回一堆角色类型,后端再根据角色做硬编码判断,前端也根据角色做页面渲染。角色变更一次,就要同时改多个地方。
5. JWT 用了,但没有打通权限模型
很多系统确实使用了 JWT,但只是为了无状态登录。登录成功之后,token 里只有用户 ID,接口校验也只是"是否登录",并没有真正校验当前用户是否具备某项权限。
所以权限系统的关键,不是用了什么框架,而是是否把"认证"和"授权"分开设计,并且是否真正落实到了后端接口层。
二、权限系统要解决的核心问题
权限系统的目标不是"用户能登录",而是要真正解决下面几个问题。
1. 确认当前用户是谁
也就是认证,Authentication。
用户通过用户名密码、短信验证码、第三方登录等方式登录之后,系统需要确认身份。
2. 确认当前用户能做什么
也就是授权,Authorization。
登录成功只代表身份合法,不代表可以访问所有页面、所有菜单和所有接口。
3. 接口必须有真正的安全边界
菜单隐藏不是权限控制的终点,真正的边界一定是后端接口。
4. 支持后续角色和权限扩展
一个小系统可能一开始只有管理员和普通用户,但后面可能会扩展出:
- 部门管理员
- 运营人员
- 审核专员
- 咨询师
- 租户管理员
如果模型一开始设计得过于简单,后期扩展成本会非常高。
5. 前后端规则要一致
后端负责最终权限校验,前端负责菜单、按钮、页面的动态控制,两者必须基于同一套权限点设计。
三、为什么大多数后台系统都适合用 RBAC
RBAC 的全称是 Role-Based Access Control,也就是基于角色的访问控制。
它最核心的关系链是:
用户 -> 角色 -> 权限
这种模型的好处很明显:
- 用户不直接绑定大量权限
- 角色作为中间层,方便统一管理
- 权限变更时只需要调整角色与权限关系,不必逐个修改用户
举个简单例子。
系统里有三个角色:
- 系统管理员
- 咨询师
- 普通用户
系统管理员拥有全部权限;
咨询师可以查看用户信息、管理咨询记录;
普通用户只能查看自己的资料和订单。
如果没有角色层,就需要给每个用户单独分配权限,维护成本会迅速失控。
RBAC 的价值就在于把"用户"和"能力"解耦了。
四、权限点应该怎么设计
权限点设计建议采用统一命名规则:
text
模块:资源:动作
例如:
text
sys:user:view
sys:user:create
sys:user:update
sys:user:delete
sys:role:view
sys:role:assign
order:refund:approve
这样设计有几个明显优势。
1. 语义清晰
看到权限码就能知道它控制的是什么能力。
2. 前后端都能共用
前端按钮、菜单、路由守卫和后端 @PreAuthorize 可以使用同一套权限码。
3. 适合数据库持久化
权限表里直接维护 permission_code,便于检索、比对和扩展。
4. 便于接口级权限控制
Spring Security 中可以直接写:
java
@PreAuthorize("hasAuthority('sys:user:view')")
这里要强调一个原则:
不要把 URL 当权限码,也不要把中文文案当权限码。
权限码应该是稳定、统一、可维护的系统内部标识。
五、JWT 在权限系统中的作用
JWT 本质上是一种令牌机制,用来在前后端之间传递登录态。
一个典型的 JWT 包括三部分:
- Header
- Payload
- Signature
在权限系统中,JWT 主要承担的是"认证凭证"的角色,而不是权限系统本身。
JWT 的典型使用流程如下:
- 用户提交用户名和密码登录
- 后端验证通过后生成 JWT
- 前端保存 token
- 后续请求在请求头中携带:
http
Authorization: Bearer xxx
- 后端通过过滤器解析 token,确认当前用户身份
- 结合权限信息完成授权判断
JWT 的优势:
- 无状态,适合前后端分离
- 适合 Web、App、小程序统一接入
- 易于和网关、微服务体系整合
但 JWT 也有明显问题:
- token 一旦签发,不容易立即失效
- 如果把太多权限直接塞到 token 中,会带来权限变更延迟问题
- 需要配合黑名单、短期 token、刷新机制做增强
因此,JWT 只是权限体系中的一环,而不是全部。
六、Spring Boot + JWT + Spring Security 的整体实现流程
一个完整的权限系统实现流程一般如下:
第一步:用户登录
前端调用登录接口,提交用户名和密码。
第二步:服务端校验身份
后端校验用户名密码是否正确。
第三步:查询用户角色和权限
根据当前用户查出其角色集合和权限集合。
第四步:签发 JWT
后端把用户身份信息和必要权限信息写入 token,返回给前端。
第五步:前端保存 token
后续请求统一通过请求头携带 token。
第六步:后端过滤器解析 token
JWT 过滤器提取用户名、权限,并构造 Spring Security 的认证上下文。
第七步:接口做权限校验
Controller 层通过 @PreAuthorize 校验权限,决定能否执行接口。
这套流程里,最关键的是两部分:
- JWT 过滤器
- 接口级权限控制
七、权限系统的数据模型怎么设计
最基础的 RBAC 表设计通常包括下面几张表:
1. 用户表 sys_user
保存用户名、密码、昵称、状态、创建时间等基础信息。
2. 角色表 sys_role
保存角色编码、角色名称、状态等信息。
3. 权限表 sys_permission
保存权限编码、权限名称、类型等。
4. 用户角色关联表 sys_user_role
描述用户和角色之间的多对多关系。
5. 角色权限关联表 sys_role_permission
描述角色和权限之间的多对多关系。
如果还需要控制菜单,可以继续增加菜单表:
6. 菜单表 sys_menu
菜单项可关联 permission_code,前端根据权限动态渲染。
注意一点:
菜单不是权限,菜单只是权限的展示入口。
真正的权限控制最终还是要落到接口上。
八、登录接口设计
登录接口的请求参数一般比较简单:
java
public record LoginRequest(String username, String password) {
}
返回值建议包含三类信息:
- token
- 当前用户基础信息
- 当前用户权限集合
例如:
java
public record LoginResponse(
String token,
UserInfo user,
Set<String> permissions
) {
public record UserInfo(Long id, String username, String nickname, Set<String> roles) {
}
}
登录服务逻辑示例:
java
@Service
public class AuthService {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
public LoginResponse login(LoginRequest request) {
User user = userService.findByUsername(request.username())
.orElseThrow(() -> new IllegalArgumentException("用户名或密码错误"));
if (!passwordEncoder.matches(request.password(), user.getPassword())) {
throw new IllegalArgumentException("用户名或密码错误");
}
Set<String> permissions = userService.getPermissions(user.getId());
String token = jwtTokenProvider.generateToken(user.getUsername(), permissions);
return new LoginResponse(
token,
new LoginResponse.UserInfo(user.getId(), user.getUsername(), user.getNickname(), user.getRoles()),
permissions
);
}
}
登录接口设计要点
- 密码必须加密存储,通常使用 BCrypt
- 登录失败提示不要过于具体,避免账号枚举风险
- 登录成功后尽量一次性返回前端所需核心信息,减少额外请求
九、JWT 工具类实现
JWT 工具类主要负责三件事:
- 生成 token
- 解析 token
- 校验 token
示例代码如下:
java
@Component
public class JwtTokenProvider {
private final SecretKey secretKey;
private final long expiration;
public JwtTokenProvider(@Value("${security.jwt.secret}") String secret,
@Value("${security.jwt.expiration}") long expiration) {
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.expiration = expiration;
}
public String generateToken(String username, Set<String> permissions) {
Date now = new Date();
Date expireTime = new Date(now.getTime() + expiration);
return Jwts.builder()
.subject(username)
.claim("permissions", permissions)
.issuedAt(now)
.expiration(expireTime)
.signWith(secretKey)
.compact();
}
public Claims parseToken(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload();
}
public String getUsername(String token) {
return parseToken(token).getSubject();
}
public List<String> getPermissions(String token) {
return parseToken(token).get("permissions", List.class);
}
}
实际项目中的注意点
- JWT 密钥不能太短
- 密钥不能直接硬编码在代码中
- token 有效期不建议过长
- 高安全场景应增加刷新 token 或黑名单机制
十、JWT 过滤器接入 Spring Security
为了让 Spring Security 知道当前请求对应的是哪个用户,需要在请求进入 Controller 之前先经过 JWT 过滤器。
示例代码如下:
java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
try {
String username = jwtTokenProvider.getUsername(token);
List<String> permissions = jwtTokenProvider.getPermissions(token);
List<GrantedAuthority> authorities = permissions.stream()
.map(SimpleGrantedAuthority::new)
.toList();
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (Exception ignored) {
}
}
filterChain.doFilter(request, response);
}
}
这个过滤器的本质就是:
- 把请求头中的 token 解析出来
- 把 token 中的用户信息和权限信息转成 Spring Security 可识别的认证对象
- 放进
SecurityContext
后续接口层的权限校验就能基于这个上下文进行。
十一、Spring Security 配置
核心配置通常包括以下几件事:
- 关闭 CSRF
- 配置无状态 Session
- 放行登录接口和 Swagger
- 所有其他接口默认要求认证
- 注册 JWT 过滤器
- 开启方法级权限控制
示例代码如下:
java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/login", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
这里的核心原则是:
登录接口放行,其他接口默认先要求登录,再在具体接口方法上细分权限。
十二、接口级权限控制为什么推荐 @PreAuthorize
相比把所有权限规则都堆在过滤器里,把权限写在接口方法上更清晰、更可维护。
示例:
java
@PreAuthorize("hasAuthority('sys:user:view')")
@GetMapping("/api/users")
public ApiResponse<List<UserResponse>> list() {
return ApiResponse.ok(userService.findAll());
}
再比如新增用户:
java
@PreAuthorize("hasAuthority('sys:user:create')")
@PostMapping("/api/users")
public ApiResponse<UserResponse> create(@RequestBody @Valid UserRequest request) {
return ApiResponse.ok(userService.create(request));
}
这种方式的优点很明确:
- 权限要求和业务接口天然绑定
- 容易读懂
- 容易审查
- 粒度足够细
- 支持复杂表达式
例如:
java
@PreAuthorize("hasAuthority('sys:role:view') or hasAuthority('sys:menu:view')")
十三、RESTful 用户管理接口设计
用户管理接口建议遵循 RESTful 风格设计:
GET /api/users查询用户列表GET /api/users/{id}查询用户详情POST /api/users新增用户PUT /api/users/{id}更新用户DELETE /api/users/{id}删除用户GET /api/users/me查询当前登录用户信息
示例代码如下:
java
@RestController
@RequestMapping("/api/users")
public class UserController {
@PreAuthorize("hasAuthority('sys:user:view')")
@GetMapping
public ApiResponse<List<UserResponse>> list() {
return ApiResponse.ok(userService.findAll());
}
@PreAuthorize("hasAuthority('sys:user:view')")
@GetMapping("/{id}")
public ApiResponse<UserResponse> detail(@PathVariable Long id) {
return ApiResponse.ok(userService.findById(id));
}
@PreAuthorize("hasAuthority('sys:user:create')")
@PostMapping
public ApiResponse<UserResponse> create(@RequestBody @Valid UserRequest request) {
return ApiResponse.ok(userService.create(request));
}
@PreAuthorize("hasAuthority('sys:user:update')")
@PutMapping("/{id}")
public ApiResponse<UserResponse> update(@PathVariable Long id,
@RequestBody @Valid UserRequest request) {
return ApiResponse.ok(userService.update(id, request));
}
@PreAuthorize("hasAuthority('sys:user:delete')")
@DeleteMapping("/{id}")
public ApiResponse<Void> delete(@PathVariable Long id) {
userService.delete(id);
return ApiResponse.ok(null);
}
}
不要把接口写成:
/getUserList/saveUser/deleteUserById
这种动作式路径不利于规范统一,也不利于接口文档维护。
十四、统一返回结构的必要性
很多项目会对返回值做统一包装,例如:
java
public record ApiResponse<T>(boolean success, String message, T data) {
public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(true, "success", data);
}
public static <T> ApiResponse<T> fail(String message) {
return new ApiResponse<>(false, message, null);
}
}
它的优点是:
- 前端统一处理
- 错误提示风格一致
- 易于后续扩展错误码、traceId、分页信息
但也要注意:
统一返回结构不等于可以忽略 HTTP 状态码。
例如:
- 参数错误返回 400
- 未登录返回 401
- 无权限返回 403
- 服务异常返回 500
HTTP 状态码和业务响应体应该一起使用,而不是互相替代。
十五、Swagger 为什么应该尽早接入
权限系统类项目接口很多,调试频率也很高,所以 Swagger 文档很有必要。
优势主要有三个:
- 便于前后端联调
- 便于测试人员验证接口
- 便于后期维护和交接
Spring Boot 中可以使用 springdoc-openapi,配置完成后访问:
text
/swagger-ui.html
即可查看接口文档。
如果接口需要 Bearer Token,还可以在 OpenAPI 中配置安全方案,让 Swagger 页面直接支持携带 token 测试受保护接口。
十六、前后端权限协同怎么做
前端和后端的职责一定要分清楚:
后端职责
- 登录认证
- 接口鉴权
- 权限边界兜底
前端职责
- 菜单动态渲染
- 按钮动态显示
- 路由守卫
- 提升用户体验
一个典型做法是:
登录成功后,后端返回:
- token
- 用户信息
- 权限集合
然后前端再调用一个权限元数据接口,例如:
text
GET /api/permissions/meta
返回:
- 当前角色
- 权限集合
- 菜单配置
前端根据权限集合判断:
- 哪些菜单显示
- 哪些按钮显示
- 哪些路由允许访问
但需要再次强调:
前端的权限判断只能改善体验,真正的安全校验必须由后端完成。
十七、权限系统常见坑点
这一部分是最容易踩坑的地方。
1. 只做菜单权限,不做接口权限
这不是权限系统,只是 UI 隐藏。
2. 把角色判断写死在业务代码里
例如:
java
if ("admin".equals(role)) {
...
}
这种方式在角色扩展后会迅速失控。
3. 权限码不统一
同一个系统里混用多种命名风格,后续维护会非常痛苦。
4. 把全部权限都塞进 JWT
权限一旦变更,旧 token 中的权限快照可能仍然有效。
5. 没有 token 失效策略
比如用户被禁用、修改密码、角色调整后,旧 token 依然可用。
6. 没有区分功能权限和数据权限
能访问用户列表,不代表能访问所有用户数据。
十八、数据权限怎么扩展
当系统除了控制"功能能不能访问",还要控制"数据能不能看到"时,就进入了数据权限层。
典型数据范围包括:
- 仅本人数据
- 本部门数据
- 本部门及子部门数据
- 全部数据
- 自定义数据范围
实现方式一般有两类:
1. 在 Service 层加过滤条件
例如只能查当前用户自己创建的数据:
sql
creator_id = currentUserId
2. 在持久层统一注入数据范围条件
例如 MyBatis 拦截器、SQL 拼接等。
这里要明确一点:
接口权限和数据权限不是一回事。
你可能有权限访问某个接口,但返回数据范围仍然需要继续过滤。
十九、JWT 失效与刷新策略
JWT 常见的两个问题是:
- 权限变更后旧 token 如何处理
- token 过期后如何续签
一个较成熟的方案是双 token:
- access token:短期有效
- refresh token:长期有效
access token 用于访问接口,refresh token 用于换发新 token。
如果系统规模不大,也可以先做简化版:
- access token 有效期 2 小时
- 重新登录获取新 token
- 配合账号状态校验
- 必要时加入 token 黑名单或 token 版本号机制
核心不是一步做到最复杂,而是设计上要预留升级空间。
二十、权限系统为什么需要审计日志
权限系统如果没有审计能力,后期排查问题会非常被动。
建议至少记录以下行为:
- 登录成功和失败
- 登出
- 用户创建、修改、删除
- 角色分配
- 权限变更
- 高危接口调用
- 审批、退款、导出等关键操作
审计日志建议包含:
- 操作人
- 操作时间
- 请求 IP
- 请求参数
- 操作资源
- 操作结果
- 变更前后内容
- traceId
一旦出现越权、误删、误授权,这些信息就是最直接的排查依据。
二十一、一个建议的落地路径
如果你要从零搭建权限系统,建议按阶段推进。
第一阶段:最小可用版
- 用户登录
- JWT 鉴权
- 接口级
@PreAuthorize - 基础角色和权限表
- 前端菜单和按钮控制
第二阶段:管理能力补齐
- 用户管理
- 角色管理
- 权限管理
- 菜单管理
- Swagger 文档
第三阶段:安全增强
- token 刷新
- 登录失败限制
- 用户禁用
- token 黑名单
- 密码策略
第四阶段:复杂场景扩展
- 数据权限
- 多租户
- 单点登录
- 审计日志
- 网关统一鉴权
这样做的好处是每一阶段都可以形成明确交付,而不是一开始就做成一个难以落地的大系统。
总结
权限系统是后台项目中非常基础、但也非常容易被做偏的一部分。
真正可落地的权限系统,至少要满足下面几个原则:
- 认证和授权分开设计
- 前端权限只负责展示控制
- 后端接口权限才是真正边界
- 角色只是组织方式,权限才是能力边界
- 权限点命名必须统一
- 后续必须考虑数据权限、审计和 token 失效机制
从工程实践角度看,Spring Boot + Spring Security + JWT + RBAC 是一套足够成熟且稳定的方案。只要权限模型清晰、接口层保护到位、前后端协同一致,这套方案完全可以支撑大多数企业后台系统。
参考代码片段汇总
登录接口权限流程
java
@PostMapping("/api/auth/login")
public ApiResponse<LoginResponse> login(@RequestBody LoginRequest request) {
return ApiResponse.ok(authService.login(request));
}
接口级权限控制
java
@PreAuthorize("hasAuthority('sys:user:view')")
@GetMapping("/api/users")
public ApiResponse<List<UserResponse>> list() {
return ApiResponse.ok(userService.findAll());
}
JWT 过滤器
java
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith("Bearer ")) {
String token = header.substring(7);
}
互动话题
你在项目里是怎么做权限控制的?
- 只做了登录,暂时没做权限
- 做了菜单权限,但后端接口没完全拦
- 已经做了完整的 RBAC
- 正在准备做数据权限
欢迎在评论区交流。