SpringBoot 整合 Spring Security 完整登录 + 角色 + 权限 + 操作权限实战
一、整体架构说明
- 认证(Authentication):登录校验账号密码,生成登录凭证
- 授权(Authorization) :
- 角色:
ROLE_ADMIN、ROLE_USER粗粒度分组 - 权限:
sys:user:list、sys:user:add细粒度操作权限
- 角色:
- 核心组件:
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:
- 自定义
OncePerRequestFilter拦截请求解析 Token - 校验 token 后手动构建
UsernamePasswordAuthenticationToken放入上下文 - 移除表单登录,全部通过 Header 传递 token
十三、核心流程总结
- 前端 POST
/doLogin携带 username/password - Security 自动拦截,调用
UserDetailServiceImpl查询用户、角色、所有操作权限 PasswordEncoder对比密码,成功执行登录成功处理器- 访问接口时:
- 未登录:401
- 登录但缺少权限 / 角色:403
- 权限匹配正常访问接口
常见问题
ROLE_前缀:角色 key 必须带ROLE_,hasRole("ADMIN")会自动补前缀匹配- 权限标识
sys:user:list使用hasAuthority()判断 - 方法权限不生效:必须加
@EnableGlobalMethodSecurity(prePostEnabled = true) - 前后端分离跨域:在 Security 配置添加
.cors()放行跨域