SpringSecurity_02_授权与高级功能

【Spring Security】授权与高级功能:权限控制、CSRF 与异常处理

🔑 关键词:Java、Spring Security、授权、@PreAuthorize、CSRF、Remember-Me、异常处理


一、URL 级别权限控制

1.1 基于角色

java 复制代码
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
        .requestMatchers("/public/**").permitAll()           // 公开
        .requestMatchers("/user/**").hasRole("USER")         // 需要 USER 角色
        .requestMatchers("/admin/**").hasRole("ADMIN")       // 需要 ADMIN 角色
        .requestMatchers("/admin/user/**").hasAnyRole("ADMIN", "SUPER_ADMIN") // 多角色
        .anyRequest().authenticated()                        // 其他需认证
    );
    return http.build();
}

注意 :hasRole("ADMIN") 实际匹配的是 ROLE_ADMIN,Spring Security 自动添加 ROLE_ 前缀。

1.2 基于权限

java 复制代码
http.authorizeHttpRequests(auth -> auth
    .requestMatchers("/user/add").hasAuthority("user:add")
    .requestMatchers("/user/delete").hasAuthority("user:delete")
    .requestMatchers("/order/**").hasAnyAuthority("order:read", "order:write")
    .anyRequest().authenticated()
);

角色 vs 权限:

对比 hasRole() hasAuthority()
前缀 自动加 ROLE_ 不加前缀,原样匹配
层级 角色有层级关系 权限是扁平的
适用场景 粗粒度(管理员/普通用户) 细粒度(具体操作权限)

二、方法级别权限控制

2.1 开启方法安全

java 复制代码
@Configuration
@EnableMethodSecurity  // Spring Security 6.x
public class SecurityConfig { }

2.2 @PreAuthorize(方法执行前校验)

java 复制代码
@Service
public class UserService {

    // 需要 user:read 权限才能调用
    @PreAuthorize("hasAuthority('user:read')")
    public List<User> findAll() {
        return userMapper.selectAll();
    }

    // 需要 ADMIN 角色
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteById(Long id) {
        userMapper.deleteById(id);
    }

    // 只能查看自己的信息(动态参数校验)
    @PreAuthorize("#id == authentication.principal.id")
    public User findById(Long id) {
        return userMapper.selectById(id);
    }

    // 自定义权限校验逻辑
    @PreAuthorize("@permissionService.hasPermission(authentication, 'user:delete')")
    public void delete(User user) { }
}

2.3 常用注解对比

注解 执行时机 说明
@PreAuthorize 方法执行前 ✅ 最常用,可访问方法参数
@PostAuthorize 方法执行后 可访问方法返回值
@PreFilter 方法执行前 过滤集合参数
@PostFilter 方法执行后 过滤返回的集合
@Secured 方法执行前 旧版注解,不支持 SpEL
java 复制代码
// @PostAuthorize 示例:校验返回值
@PostAuthorize("returnObject.owner == authentication.name")
public Order getOrder(Long id) {
    return orderMapper.selectById(id);
}

三、CSRF 防护

3.1 什么是 CSRF?

CSRF(Cross-Site Request Forgery)跨站请求伪造:攻击者诱导用户访问恶意页面,利用用户已登录的身份发送恶意请求。

3.2 Spring Security 的 CSRF 防护

默认开启,要求每个 POST/PUT/DELETE 请求携带 CSRF Token。

表单场景(传统 Web):

html 复制代码
<form action="/transfer" method="post">
    <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
    <button type="submit">转账</button>
</form>

前后端分离场景:通常关闭 CSRF,改用 Token(JWT)认证。

java 复制代码
http.csrf(csrf -> csrf.disable());

为什么前后端分离可以关闭 CSRF? JWT 放在请求头(Authorization)中,不会被浏览器自动携带,因此不受 CSRF 攻击影响。


四、Remember-Me(记住我)

4.1 配置

java 复制代码
http.rememberMe(remember -> remember
    .tokenValiditySeconds(86400 * 7)  // 记住7天
    .userDetailsService(myUserDetailsService)
);

4.2 登录表单

html 复制代码
<form action="/doLogin" method="post">
    <input name="username" placeholder="用户名"/>
    <input name="password" type="password" placeholder="密码"/>
    <label>
        <input type="checkbox" name="remember-me"/> 记住我
    </label>
    <button type="submit">登录</button>
</form>

4.3 原理

勾选"记住我"后,服务端生成持久化 Token 写入 Cookie,下次访问自动恢复认证状态。Token 默认有效期 2 周。


五、自定义异常处理

5.1 认证异常(401)与授权异常(403)

java 复制代码
http.exceptionHandling(ex -> ex
    // 未认证:返回 401
    .authenticationEntryPoint((request, response, authException) -> {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(401);
        response.getWriter().write(
            "{\"code\":401,\"message\":\"未登录,请先登录\"}"
        );
    })
    // 无权限:返回 403
    .accessDeniedHandler((request, response, accessDeniedException) -> {
        response.setContentType("application/json;charset=UTF-8");
        response.setStatus(403);
        response.getWriter().write(
            "{\"code\":403,\"message\":\"权限不足,无法访问\"}"
        );
    })
);

5.2 前后端分离的完整配置

java 复制代码
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/api/login", "/api/register").permitAll()
            .anyRequest().authenticated()
        )
        .formLogin(form -> form.disable())          // 关闭表单登录
        .httpBasic(basic -> basic.disable())         // 关闭 Basic 认证
        .csrf(csrf -> csrf.disable())               // 关闭 CSRF
        .sessionManagement(session -> session
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)  // 无状态
        )
        .exceptionHandling(ex -> ex
            .authenticationEntryPoint(new JwtAuthenticationEntryPoint())
            .accessDeniedHandler(new JwtAccessDeniedHandler())
        );

    return http.build();
}

六、RBAC 权限模型

6.1 数据库设计

复制代码
用户表(sys_user)         角色表(sys_role)          权限表(sys_permission)
┌─────────┐            ┌─────────┐              ┌─────────────┐
│ id      │            │ id      │              │ id          │
│ username│            │ name    │              │ name        │
│ password│            │ code    │              │ code        │
└─────────┘            └─────────┘              └─────────────┘

用户角色关联表(sys_user_role)     角色权限关联表(sys_role_permission)
┌───────────┬─────────┐        ┌───────────┬───────────────┐
│ user_id   │ role_id │        │ role_id   │ permission_id │
└───────────┴─────────┘        └───────────┴───────────────┘

6.2 查询用户权限

java 复制代码
@Override
public UserDetails loadUserByUsername(String username) {
    // 1. 查用户
    User user = userMapper.selectByUsername(username);
    // 2. 查角色
    List<Role> roles = roleMapper.selectByUserId(user.getId());
    // 3. 查权限
    List<String> permissions = permissionMapper.selectByRoleIds(
        roles.stream().map(Role::getId).collect(Collectors.toList())
    );

    // 4. 组装权限列表
    List<GrantedAuthority> authorities = new ArrayList<>();
    roles.forEach(r -> authorities.add(new SimpleGrantedAuthority("ROLE_" + r.getCode())));
    permissions.forEach(p -> authorities.add(new SimpleGrantedAuthority(p)));

    return new LoginUser(user, authorities);
}

七、面试高频问题

Q1:Spring Security 中角色和权限的区别?

答 :角色(Role)是粗粒度的身份标识,如 ROLE_ADMIN、ROLE_USER;权限(Authority)是细粒度的操作权限,如 user:add、user:delete。hasRole("ADMIN") 匹配 ROLE_ADMIN,hasAuthority("user:add") 匹配 user:add。

Q2:@PreAuthorize 和 @Secured 的区别?

答 :@Secured 是旧版注解,只支持角色判断,不支持 SpEL 表达式;@PreAuthorize 支持 SpEL,可以在表达式中访问方法参数、认证对象,功能更强大。推荐用 @PreAuthorize。

Q3:为什么前后端分离项目要关闭 CSRF?

答:CSRF 攻击依赖浏览器自动携带 Cookie 的机制。前后端分离通常使用 JWT Token 放在请求头中认证,不会被浏览器自动携带,因此不受 CSRF 攻击影响。关闭 CSRF 可以避免额外维护 Token 的开销。


八、总结

功能 实现方式 核心注解/配置
URL 权限 authorizeHttpRequests hasRole / hasAuthority
方法权限 @EnableMethodSecurity @PreAuthorize / @PostAuthorize
CSRF csrf.disable() 前后端分离关闭
Remember-Me rememberMe tokenValiditySeconds
异常处理 exceptionHandling authenticationEntryPoint / accessDeniedHandler
权限模型 RBAC 用户-角色-权限 三表关联

📝 下一篇预告:《Spring Security + JWT 鉴权》,详解 JWT 原理、Token 生成验证、无状态认证完整流程。

相关推荐
码匠许师傅2 小时前
【C++三方组件】cpp-httplib:一个头文件起 HTTP 服务
开发语言·c++·http
摇滚侠2 小时前
《On Java 中文版 基础卷》阅读笔记 对象无处不在 03
java·笔记·python
小小小小钰儿3 小时前
1.5-Python网络编程
开发语言·网络·python·web安全·计算机·网络安全
hiahiahia1233 小时前
实现完整 Tool Dispatcher
开发语言·前端
keke.shengfengpolang3 小时前
2026年金融风控岗技术能力拆解:SQL取数、Python建模、评分卡项目,校招面试到底考什
python
kyrie_sakura3 小时前
大模型学习7 -- 深度学习2 -- 张量
python·深度学习
AC赳赳老秦3 小时前
公开音频转写信息提取:OpenClaw 处理发布会与听证会文本并提取核心决策信息
大数据·开发语言·汇编·数据库·人工智能·deepseek·openclaw