大型项目基于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);  
    }  
}  

八 、 核 心 流 程 总 结

  1. ** 用 户 登 录 ** : Controller 接 收 用 户 名 密 码 → AuthenticationManager 认 证 → JwtUtils 生 成 Token
  2. ** 请 求 处 理 ** : JwtAuthFilter 提 取 Token → 设 置 SecurityContext → Controller 处 理 业 务
  3. ** 权 限 校 验 ** : @PreAuthorize 注 解 → PermissionChecker 检 查 → DataScopeService 计 算 数 据 范 围
  4. ** 数 据 过 滤 ** : DataScopeSqlInterceptor 拦 截 SQL → 注 入 权 限 条 件 → 返 回 过 滤 后 数 据

此 方 案 实 现 了 完 整 的 登 录 鉴 权 与 数 据 权 限 控 制 , 支 持 大 型 项 目 的 复 杂 权 限 需 求 , 各 模 块 职 责 清 晰 , 便 于 维 护 和 扩 展 。

posted @ 2025-11-26 09:28  佛祖让我来巡山  阅读(10)  评论(0)    收藏  举报

佛祖让我来巡山博客站 - 创建于 2018-08-15

开发工程师个人站,内容主要是网站开发方面的技术文章,大部分来自学习或工作,部分来源于网络,希望对大家有所帮助。

Bootstrap中文网