大型项目基于Spring Security的登录鉴权与数据权限控制完整方案
一 、 整 体 架 构 设 计
1.1 架 构 分 层 说 明
本 方 案 采 用 五 层 架 构 设 计 , 从 上 到 下 逻 辑 连 贯 :
- ** 表 现 层 ** : Controller 接 口 ( REST API )
- ** 业 务 层 ** : Service 服 务 类 ( 业 务 逻 辑 + 权 限 校 验 )
- ** 数 据 层 ** : DAO/Mapper 接 口 ( 数 据 访 问 )
- ** 权 限 层 ** : Spring Security 配 置 + 自 定 义 权 限 组 件
- ** 工 具 层 ** : Util 工 具 类 ( JWT 、 加 密 、 缓 存 )
- ** 插 件 层 ** : Filter 过 滤 器 、 Interceptor 拦 截 器
1.2 权 限 模 型 设 计
- ** RBAC 模 型 ** : 用 户 - 角 色 - 权 限 三 层 结 构
- ** 数 据 权 限 ** : 按 组 织 层 级 控 制 ( 本 人 / 本 部 门 / 本 部 门 及 下 属 / 全 部 )
- ** 动 态 权 限 ** : 运 行 时 计 算 用 户 数 据 访 问 范 围
二 、 数 据 库 设 计
-- 用户表
CREATE TABLE sys_user (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT 'BCrypt加密密码',
real_name VARCHAR(50) NOT NULL COMMENT '真实姓名',
dept_id BIGINT NOT NULL COMMENT '所属部门ID',
status TINYINT DEFAULT 1 COMMENT '状态:0禁用 1启用',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 部门表(树形结构)
CREATE TABLE sys_dept (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL COMMENT '部门名称',
parent_id BIGINT DEFAULT 0 COMMENT '父部门ID',
level INT DEFAULT 1 COMMENT '层级',
manager_id BIGINT COMMENT '部门经理ID',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 角色表
CREATE TABLE sys_role (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE COMMENT '角色名称',
code VARCHAR(50) NOT NULL UNIQUE COMMENT '角色编码',
data_scope TINYINT DEFAULT 1 COMMENT '数据权限范围:1本人 2本部门 3本部门及下属 4全部'
);
-- 用户角色关联表
CREATE TABLE sys_user_role (
user_id BIGINT,
role_id BIGINT,
PRIMARY KEY (user_id, role_id)
);
-- 权限表
CREATE TABLE sys_permission (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL COMMENT '权限名称',
code VARCHAR(100) NOT NULL UNIQUE COMMENT '权限标识',
type TINYINT NOT NULL COMMENT '类型:1菜单 2按钮 3接口'
);
-- 角色权限关联表
CREATE TABLE sys_role_permission (
role_id BIGINT,
permission_id BIGINT,
PRIMARY KEY (role_id, permission_id)
);
-- 订单表
CREATE TABLE biz_order (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_no VARCHAR(50) NOT NULL UNIQUE COMMENT '订单编号',
amount DECIMAL(12,2) NOT NULL COMMENT '订单金额',
creator_id BIGINT NOT NULL COMMENT '创建人ID',
dept_id BIGINT NOT NULL COMMENT '所属部门ID',
status TINYINT DEFAULT 0 COMMENT '订单状态',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
三 、 核 心 模 块 实 现
3.1 Entity 实 体 类
3.1.1 UserPo 用 户 实 体 ( 实 现 UserDetails 接 口 )
@Data
@TableName("sys_user")
public class UserPo implements UserDetails {
private Long id;
private String username;
private String password;
private String realName;
private Long deptId;
private Integer status;
private LocalDateTime createdAt;
@TableField(exist = false)
private DeptPo dept;
@TableField(exist = false)
private Set<RolePo> roles;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<>();
for (RolePo role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));
for (PermissionPo perm : role.getPermissions()) {
authorities.add(new SimpleGrantedAuthority(perm.getCode()));
}
}
return authorities;
}
@Override public boolean isAccountNonExpired() { return true; }
@Override public boolean isAccountNonLocked() { return status == 1; }
@Override public boolean isCredentialsNonExpired() { return true; }
@Override public boolean isEnabled() { return status == 1; }
}
3.1.2 DeptPo 部 门 实 体
@Data
@TableName("sys_dept")
public class DeptPo {
private Long id;
private String name;
private Long parentId;
private Integer level;
private Long managerId;
private LocalDateTime createdAt;
}
3.1.3 RolePo 角 色 实 体
@Data
@TableName("sys_role")
public class RolePo {
private Long id;
private String name;
private String code;
private Integer dataScope;
@TableField(exist = false)
private Set<PermissionPo> permissions;
}
3.1.4 OrderPo 订 单 实 体
@Data
@TableName("biz_order")
public class OrderPo {
private Long id;
private String orderNo;
private BigDecimal amount;
private Long creatorId;
private Long deptId;
private Integer status;
private LocalDateTime createdAt;
}
3.2 DAO 层 ( Mapper 接 口 )
3.2.1 UserMapper 接 口
@Mapper
public interface UserMapper extends BaseMapper<UserPo> {
UserPo selectUserWithFullInfo(Long userId);
List<UserPo> findUserIdsByDeptId(Long deptId);
}
3.2.2 DeptMapper 接 口
@Mapper
public interface DeptMapper extends BaseMapper<DeptPo> {
List<DeptPo> findByParentId(Long parentId);
}
3.2.3 OrderMapper 接 口
@Mapper
public interface OrderMapper extends BaseMapper<OrderPo> {
IPage<OrderPo> selectPage(IPage<OrderPo> page, @Param("ew") Wrapper<OrderPo> wrapper);
}
3.3 Service 层
3.3.1 UserService 接 口
public interface UserService {
UserPo loadUserByUsername(String username);
UserPo selectUserWithFullInfo(Long userId);
}
3.3.2 UserServiceImpl 服 务 实 现 类
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
@Override
public UserPo loadUserByUsername(String username) {
return userMapper.selectOne(new QueryWrapper<UserPo>().eq("username", username));
}
@Override
public UserPo selectUserWithFullInfo(Long userId) {
return userMapper.selectUserWithFullInfo(userId);
}
}
3.3.3 DataScopeService 数 据 权 限 服 务 类 ( 核 心 )
@Service
@RequiredArgsConstructor
public class DataScopeService {
private final UserMapper userMapper;
private final DeptMapper deptMapper;
private final RoleMapper roleMapper;
private final OrderMapper orderMapper;
/**
* 获 取 当 前 用 户 的 数 据 权 限 范 围
*/
public DataScope getCurUserDataScope() {
UserPo user = getCurrentUser();
Set<RolePo> roles = user.getRoles();
// 查找最大数据权限范围
int maxScope = roles.stream()
.mapToInt(RolePo::getDataScope)
.max()
.orElse(1);
// 获取部门及子部门ID
List<Long> deptIds = new ArrayList<>();
if (maxScope >= 2) {
DeptPo dept = deptMapper.selectById(user.getDeptId());
deptIds.add(dept.getId());
if (maxScope >= 3) {
List<Long> subDeptIds = findSubDeptIds(dept.getId());
deptIds.addAll(subDeptIds);
}
}
return new DataScope(maxScope, user.getId(), deptIds);
}
/**
* 递 归 查 询 所 有 子 部 门 ID
*/
private List<Long> findSubDeptIds(Long deptId) {
List<Long> deptIds = new ArrayList<>();
List<DeptPo> subDepts = deptMapper.findByParentId(deptId);
for (DeptPo subDept : subDepts) {
deptIds.add(subDept.getId());
deptIds.addAll(findSubDeptIds(subDept.getId()));
}
return deptIds;
}
private UserPo getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return (UserPo) auth.getPrincipal();
}
@Data
@AllArgsConstructor
public static class DataScope {
private int scopeType; // 1:本人 2:本部门 3:本部门及下属 4:全部
private Long userId;
private List<Long> deptIds;
}
}
3.3.4 OrderService 接 口
public interface OrderService {
PageResult<OrderVo> queryOrders(OrderQuery query);
OrderDetailVo getOrderDetail(Long id);
}
3.3.5 OrderServiceImpl 服 务 实 现 类
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final OrderMapper orderMapper;
private final DataScopeService dataScopeService;
@Override
public PageResult<OrderVo> queryOrders(OrderQuery query) {
Page<OrderPo> page = new Page<>(query.getPageNum(), query.getPageSize());
IPage<OrderPo> orderPage = dataScopeService.findOrdersWithDataScope(page, query);
return convertPageResult(orderPage);
}
@Override
public OrderDetailVo getOrderDetail(Long id) {
OrderPo order = orderMapper.selectById(id);
DataScopeService.DataScope scope = dataScopeService.getCurUserDataScope();
if (scope.getScopeType() != 4 &&
!scope.getDeptIds().contains(order.getDeptId()) &&
!order.getCreatorId().equals(scope.getUserId())) {
throw new AccessDeniedException("无权限查看此订单");
}
return convertToDetailVo(order);
}
}
3.4 Controller 层 接 口
3.4.1 OrderController 订 单 控 制 器
@RestController
@RequestMapping("/api/order")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping
@PreAuthorize("hasAuthority('order:view')")
public PageResult<OrderVo> listOrders(OrderQuery query) {
return orderService.queryOrders(query);
}
@GetMapping("/{id}")
public OrderDetailVo getDetail(@PathVariable Long id) {
return orderService.getOrderDetail(id);
}
}
四 、 权 限 控 制 模 块
4.1 Util 工 具 类
4.1.1 JwtUtils JWT 工 具 类
@Component
public class JwtUtils {
@Value("${app.jwt.secret}") private String secret;
@Value("${app.jwt.expiration}") private long expiration;
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
public String extractUsername(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
}
4.1.2 PermissionChecker 权 限 检 查 器
@Component
public class PermissionChecker {
public boolean hasPermission(String permissionCode) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth.getAuthorities().stream()
.anyMatch(a -> a.getAuthority().equals(permissionCode));
}
}
4.2 Spring Security 配 置 类
4.2.1 SecurityConfig 配 置 类 ( 位 置 : @Configuration 类 )
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsServiceImpl userDetailsService;
private final JwtAuthFilter jwtAuthFilter;
private final PasswordEncoder passwordEncoder;
@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/**").permitAll()
.requestMatchers("/api/order/**").hasAuthority("order:view")
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
五 、 插 件 配 置 与 使 用
5.1 Filter 过 滤 器
5.1.1 JwtAuthFilter JWT 认 证 过 滤 器
- ** 类 型 ** : OncePerRequestFilter 插 件
- ** 配 置 位 置 ** : SecurityConfig 中 通 过 addFilterBefore 添 加
- ** 作 用 ** : 提 取 JWT Token 并 设 置 认 证 信 息
@Component
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtUtils jwtUtils;
private final UserDetailsServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String jwt = parseJwt(request);
if (jwt != null && jwtUtils.validateToken(jwt)) {
String username = jwtUtils.extractUsername(jwt);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
chain.doFilter(request, response);
}
private String parseJwt(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (headerAuth != null && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
}
}
5.2 Interceptor 拦 截 器
5.2.1 DataScopeSqlInterceptor SQL 拦 截 器
- ** 类 型 ** : MyBatis Interceptor 插 件
- ** 配 置 位 置 ** : @Component 注 册 到 Spring 容 器
- ** 作 用 ** : 动 态 注 入 数 据 权 限 SQL 条 件
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
@Component
@RequiredArgsConstructor
public class DataScopeSqlInterceptor implements Interceptor {
private final DataScopeService dataScopeService;
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
String originalSql = boundSql.getSql();
DataScopeService.DataScope scope = dataScopeService.getCurUserDataScope();
if (scope.getScopeType() != 4) {
String newSql = addDataScopeCondition(originalSql, scope);
metaObject.setValue("delegate.boundSql.sql", newSql);
}
return invocation.proceed();
}
private String addDataScopeCondition(String sql, DataScopeService.DataScope scope) {
String condition = "dept_id IN (" + String.join(",", scope.getDeptIds().stream().map(String::valueOf).collect(Collectors.toList())) + ")";
if (sql.toLowerCase().contains("where")) {
return sql.replaceFirst("(?i)where", "WHERE " + condition + " AND ");
} else {
return sql + " WHERE " + condition;
}
}
}
六 、 前 端 集 成 示 例
6.1 Vue.js 订 单 列 表 页 面
<template>
<div>
<el-table :data="orderList">
<el-table-column prop="orderNo" label="订单编号"></el-table-column>
<el-table-column prop="amount" label="金额"></el-table-column>
<el-table-column prop="creatorName" label="创建人"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button v-if="hasPermission('order:detail')" @click="viewDetail(scope.row.id)">查看</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() { return { orderList: [] }; },
methods: {
async loadOrders() {
const res = await this.$http.get('/api/order');
this.orderList = res.data.list;
},
hasPermission(code) { return this.$store.getters.hasPermission(code); }
},
mounted() { this.loadOrders(); }
};
</script>
七 、 部 署 与 配 置
7.1 application.yml 配 置
app:
jwt:
secret: your-secret-key
expiration: 86400000 # 24小时
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_db
username: root
password: password
7.2 启 动 类 配 置
@SpringBootApplication
@MapperScan("com.example.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
八 、 核 心 流 程 总 结
- ** 用 户 登 录 ** : Controller 接 收 用 户 名 密 码 → AuthenticationManager 认 证 → JwtUtils 生 成 Token
- ** 请 求 处 理 ** : JwtAuthFilter 提 取 Token → 设 置 SecurityContext → Controller 处 理 业 务
- ** 权 限 校 验 ** : @PreAuthorize 注 解 → PermissionChecker 检 查 → DataScopeService 计 算 数 据 范 围
- ** 数 据 过 滤 ** : DataScopeSqlInterceptor 拦 截 SQL → 注 入 权 限 条 件 → 返 回 过 滤 后 数 据
此 方 案 实 现 了 完 整 的 登 录 鉴 权 与 数 据 权 限 控 制 , 支 持 大 型 项 目 的 复 杂 权 限 需 求 , 各 模 块 职 责 清 晰 , 便 于 维 护 和 扩 展 。
❤️ 如果你喜欢这篇文章,请点赞支持! 👍 同时欢迎关注我的博客,获取更多精彩内容!
本文来自博客园,作者:佛祖让我来巡山,转载请注明原文链接:https://www.cnblogs.com/sun-10387834/p/19267450

浙公网安备 33010602011771号