Spring Security

SpringBoot 整合 Spring Security 完整登录 + 角色 + 权限 + 操作权限实战

一、整体架构说明

  1. 认证(Authentication):登录校验账号密码,生成登录凭证
  2. 授权(Authorization)
    • 角色:ROLE_ADMINROLE_USER 粗粒度分组
    • 权限:sys:user:listsys:user:add 细粒度操作权限
  3. 核心组件:
    • UserDetailsService:加载数据库用户、角色、权限
    • PasswordEncoder:密码加密
    • SecurityFilterChain:安全拦截规则、登录页、放行地址
    • @PreAuthorize:接口方法级权限控制

二、依赖引入(Maven)

xml

复制代码
<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- web -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis/数据库根据自己选用 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.3.0</version>
</dependency>

三、数据库表设计(5 张核心表)

1. 用户表 sys_user

sql

复制代码
CREATE TABLE sys_user (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
    password VARCHAR(100) NOT NULL COMMENT '加密密码',
    nickname VARCHAR(50),
    status TINYINT DEFAULT 1 COMMENT '0禁用 1正常'
);

2. 角色表 sys_role

sql

复制代码
CREATE TABLE sys_role (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
    role_key VARCHAR(50) NOT NULL UNIQUE COMMENT '角色标识 ROLE_ADMIN'
);

3. 用户角色关联 sys_user_role

sql

复制代码
CREATE TABLE sys_user_role (
    user_id BIGINT,
    role_id BIGINT,
    PRIMARY KEY(user_id,role_id)
);

4. 权限表 sys_permission(操作权限)

sql

复制代码
CREATE TABLE sys_permission (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    perm_key VARCHAR(100) NOT NULL UNIQUE COMMENT '权限标识 sys:user:add',
    perm_name VARCHAR(100) COMMENT '权限名称'
);

5. 角色权限关联 sys_role_perm

sql

复制代码
CREATE TABLE sys_role_perm (
    role_id BIGINT,
    perm_id BIGINT,
    PRIMARY KEY(role_id,perm_id)
);

四、实体类

1. SysUser

java

运行

复制代码
@Data
public class SysUser implements UserDetails {
    private Long id;
    private String username;
    private String password;
    private String nickname;
    private Integer status;

    // 存放当前用户所有权限(角色+操作权限)
    private Set<GrantedAuthority> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return status == 1;
    }
}

2. SysRole

java

运行

复制代码
@Data
public class SysRole {
    private Long id;
    private String roleName;
    private String roleKey;
}

3. SysPermission

java

运行

复制代码
@Data
public class SysPermission {
    private Long id;
    private String permKey;
    private String permName;
}

五、Mapper 层(查询用户 + 角色 + 权限)

UserMapper

java

运行

复制代码
@Mapper
public interface UserMapper {
    SysUser selectUserByUsername(String username);

    // 根据用户ID查询角色
    List<SysRole> selectRolesByUserId(Long userId);

    // 根据角色ID查询权限标识
    List<String> selectPermsByRoleId(Long roleId);
}

六、自定义 UserDetailsService(核心:加载用户权限)

java

运行

复制代码
@Service
public class UserDetailServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 1. 查询用户
        SysUser user = userMapper.selectUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在");
        }

        // 2. 查询用户所有角色
        List<SysRole> roleList = userMapper.selectRolesByUserId(user.getId());
        // 3. 封装角色权限 ROLE_xxx
        Set<GrantedAuthority> authorities = new HashSet<>();
        for (SysRole role : roleList) {
            authorities.add(new SimpleGrantedAuthority(role.getRoleKey()));
            // 4. 根据角色查操作权限 sys:xxx:xxx
            List<String> permList = userMapper.selectPermsByRoleId(role.getId());
            for (String perm : permList) {
                authorities.add(new SimpleGrantedAuthority(perm));
            }
        }
        user.setAuthorities(authorities);
        return user;
    }
}

七、SpringSecurity 配置类

java

运行

复制代码
@Configuration
@EnableWebSecurity
// 开启方法级权限校验 @PreAuthorize
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    @Autowired
    private UserDetailServiceImpl userDetailService;

    // 密码加密器
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // 认证管理器
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    // 安全过滤链
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                // 关闭csrf(前后端分离接口项目)
                .csrf(csrf -> csrf.disable())
                // 放行静态资源、登录接口
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/login").permitAll()
                        .requestMatchers("/static/**").permitAll()
                        // 其余接口必须登录
                        .anyRequest().authenticated()
                )
                // 表单登录配置
                .formLogin(form -> form
                        .loginProcessingUrl("/doLogin") // 登录提交地址(不需要写接口)
                        .usernameParameter("username")
                        .passwordParameter("password")
                        // 登录成功返回JSON
                        .successHandler(new JsonLoginSuccessHandler())
                        // 登录失败返回JSON
                        .failureHandler(new JsonLoginFailHandler())
                )
                // 退出登录
                .logout(logout -> logout
                        .logoutUrl("/logout")
                        .logoutSuccessHandler(new JsonLogoutSuccessHandler())
                )
                // 未登录、无权限统一返回JSON
                .exceptionHandling(ex -> ex
                        .authenticationEntryPoint(new JsonUnauthEntryPoint())
                        .accessDeniedHandler(new JsonAccessDeniedHandler())
                );
        return http.build();
    }

    // 关联自定义用户查询
    @Bean
    public UserDetailsService userDetailsService() {
        return userDetailService;
    }
}

八、前后端分离 JSON 处理器(关键)

1. 登录成功处理器 JsonLoginSuccessHandler

java

运行

复制代码
@Component
public class JsonLoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        Map<String,Object> res = new HashMap<>();
        res.put("code",200);
        res.put("msg","登录成功");
        // 可在这里生成JWT返回token(扩展)
        out.write(JSON.toJSONString(res));
        out.flush();
        out.close();
    }
}

2. 登录失败、未登录、无权限处理器(省略,逻辑同上,返回对应 code)

  • JsonLoginFailHandler:账号密码错误 code=500
  • JsonUnauthEntryPoint:未登录 code=401
  • JsonAccessDeniedHandler:无操作权限 code=403

3. 退出登录成功

java

运行

复制代码
@Component
public class JsonLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        Map<String,Object> res = new HashMap<>();
        res.put("code",200);
        res.put("msg","退出成功");
        out.write(JSON.toJSONString(res));
    }
}

九、接口权限控制(两种方式)

方式 1:注解控制(推荐,细粒度操作权限)

@PreAuthorize 支持 hasRole / hasPermission / hasAuthority

java

运行

复制代码
@RestController
@RequestMapping("/sys/user")
public class UserController {

    // 需要权限标识 sys:user:list
    @GetMapping("/list")
    @PreAuthorize("hasAuthority('sys:user:list')")
    public String userList() {
        return "用户列表";
    }

    // 需要角色 ROLE_ADMIN
    @PostMapping("/add")
    @PreAuthorize("hasRole('ADMIN')")
    public String addUser() {
        return "新增用户";
    }

    // 同时满足两个权限
    @DeleteMapping("/delete")
    @PreAuthorize("hasAuthority('sys:user:delete') and hasRole('ADMIN')")
    public String delete() {
        return "删除用户";
    }
}

方式 2:配置类路径拦截(粗粒度角色)

java

运行

复制代码
.authorizeHttpRequests(auth -> auth
        .requestMatchers("/admin/**").hasRole("ADMIN")
        .requestMatchers("/user/**").hasAnyRole("USER","ADMIN")
        .anyRequest().authenticated()
)

十、获取当前登录用户信息

java

运行

复制代码
// 方式1 注入Authentication
@GetMapping("/info")
public Object getUserInfo(Authentication authentication){
    SysUser user = (SysUser) authentication.getPrincipal();
    return user;
}

// 方式2 工具类获取
public static SysUser getLoginUser(){
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if(auth == null) return null;
    return (SysUser) auth.getPrincipal();
}

十一、密码加密测试(新增用户时加密存入数据库)

java

运行

复制代码
@Autowired
private PasswordEncoder encoder;

@Test
void testPwd(){
    // 明文123456加密存入数据库
    String pwd = encoder.encode("123456");
    System.out.println(pwd);
}

十二、扩展:JWT 无状态登录(可选)

上面代码是 Session 模式,分布式项目改用 JWT:

  1. 自定义OncePerRequestFilter拦截请求解析 Token
  2. 校验 token 后手动构建UsernamePasswordAuthenticationToken放入上下文
  3. 移除表单登录,全部通过 Header 传递 token

十三、核心流程总结

  1. 前端 POST /doLogin 携带 username/password
  2. Security 自动拦截,调用UserDetailServiceImpl查询用户、角色、所有操作权限
  3. PasswordEncoder对比密码,成功执行登录成功处理器
  4. 访问接口时:
    • 未登录:401
    • 登录但缺少权限 / 角色:403
    • 权限匹配正常访问接口

常见问题

  1. ROLE_前缀:角色 key 必须带ROLE_hasRole("ADMIN")会自动补前缀匹配
  2. 权限标识 sys:user:list 使用 hasAuthority() 判断
  3. 方法权限不生效:必须加 @EnableGlobalMethodSecurity(prePostEnabled = true)
  4. 前后端分离跨域:在 Security 配置添加.cors()放行跨域
相关推荐
IT笔记1 小时前
【Rust】Rust Match 模式匹配详解
java·开发语言·rust
逝水无殇2 小时前
C# 运算符重载详解
开发语言·后端·c#
我才是银古2 小时前
构建 Java GIS 工具库:从碎片化 API 到统一抽象的设计实践
java·gis·geotools·gdal·esri
苏三说技术2 小时前
2026编程圈很火的10个Skills
后端
TPBoreas2 小时前
配置信息防泄露方案:.env 环境隔离详解(dotenv-java)
java·开发语言
用户8356290780512 小时前
使用 Python 自动化 Excel 公式和函数:完整指南
后端·python
Geek-Chow3 小时前
微服务认证与授权: 12 — JWKS 深入解析
spring·微服务·安全架构
敲代码的嘎仔3 小时前
实习日志day6--实习日志day6--title命名规范化&businessType纠正&补充缺失的@Log注解&报警与通信模块补充&产出阶段总结文档
java·开发语言·人工智能·git·python·实习·大二
编程(变成)小辣鸡3 小时前
Spring事务失效场景
java·后端·spring