Spring Security 的权限控制是通过 SecurityContextHolder 中保存的认证信息(Authentication)进行的。权限信息来自 UserDetails 的 getAuthorities() 方法。
只要保证返回的 Collection<? extends GrantedAuthority> 包含 ROLE_xxx,就可以进行角色控制。
数据库设计
CREATE TABLE t_user (
id INT PRIMARY KEY AUTO_INCREMENT,
login_act VARCHAR(50),
login_pwd VARCHAR(100),
name VARCHAR(50),
...
);
CREATE TABLE t_role (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50), -- 角色名,例如 ADMIN、USER
role_key VARCHAR(50) -- 用于权限控制,例如 ROLE_ADMIN、ROLE_USER
);
CREATE TABLE t_user_role (
user_id INT,
role_id INT,
PRIMARY KEY (user_id, role_id)
);
对应实体类如下:
@Data
@TableName("t_user")
public class TUser {
private Integer id;
private String loginAct;
private String loginPwd;
private String name;
// 不加 roles 字段到数据库映射,但需要接收它
@TableField(exist = false)
private List<TRole> roles;
}
@Data
@TableName("t_role")
public class TRole {
private Integer id;
private String name;
private String roleKey; // 例如 ROLE_ADMIN
}
Mapper
@Mapper
public interface TUserMapper extends BaseMapper<TUser> {
TUser findByLoginAct(String loginAct);
}
@Mapper
public interface TRoleMapper extends BaseMapper<TRole> {
@Select("SELECT r.* FROM t_role r JOIN t_user_role ur ON r.id = ur.role_id WHERE ur.user_id = #{userId}")
List<TRole> selectByUserId(@Param("userId") Integer userId);
}
LoginUser 实现 UserDetails
ok 封装这里在我上一篇博客说了,你想获得登录用户的信息就得去实现UserDetails接口,所以不多赘述了
public class LoginUser implements UserDetails {
private final TUser user;
public LoginUser(TUser user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getRoleKey()))
.collect(Collectors.toList());
}
@Override public String getPassword() { return user.getLoginPwd(); }
@Override public String getUsername() { return user.getLoginAct(); }
@Override public boolean isAccountNonExpired() { return user.getAccountNoExpired() == 1; }
@Override public boolean isAccountNonLocked() { return user.getAccountNoLocked() == 1; }
@Override public boolean isCredentialsNonExpired() { return user.getCredentialsNoExpired() == 1; }
@Override public boolean isEnabled() { return user.getAccountEnabled() == 1; }
public TUser getUser() { return user; }
}
实现 UserDetailsService
依旧在我上一篇博客说了,Spring Security就是通过UserDetailService的loadUserByUsername方法来获取用户登录信息的,只不过这里我们相比于上一篇博客额外set了一下roles这个属性
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired private TUserMapper userMapper;
@Autowired private TRoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
TUser user = userMapper.findByLoginAct(username);
if (user == null) throw new UsernameNotFoundException("用户不存在");
List<TRole> roles = roleMapper.selectByUserId(user.getId());
user.setRoles(roles);
return new LoginUser(user);
}
}
controller
你数据库中 role_key 是 ROLE_ADMIN,Spring Security 会自动把 hasRole(“ADMIN”) 转为 hasAuthority(“ROLE_ADMIN”) 匹配权限。所以你只需要:
数据库中角色字段保存如 ROLE_ADMIN
LoginUser 中返回 SimpleGrantedAuthority(“ROLE_ADMIN”)
就可以和controller层的注释 @PreAuthorize(“hasRole(‘ADMIN’)”)匹配上了
@RestController
public class UserController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/dashboard")
public String admin() {
return "管理员页面";
}
@GetMapping("/me")
public String me(@AuthenticationPrincipal LoginUser user) {
return "当前用户:" + user.getUser().getName();
}
}
安全配置类别SecurityConfig
别忘了配置这个类
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}