目录
基于角色权限控制
1.1 自定义 UserDetailsService
在 Spring Security 中,用户信息通常需要实现 UserDetails
接口。这里,我们定义了一个自定义的 TUser
类,并实现了 UserDetails
接口。
/**
* 用户表
* t_user
*/
@Data
public class TUser implements UserDetails, Serializable {
/**
* 主键,自动增长,用户ID
*/
private Integer id;
/**
* 登录账号
*/
private String loginAct;
/**
* 登录密码
*/
private String loginPwd;
/**
* 用户姓名
*/
private String name;
/**
* 用户手机
*/
private String phone;
/**
* 用户邮箱
*/
private String email;
/**
* 账户是否没有过期,0已过期 1正常
*/
private Integer accountNoExpired;
/**
* 密码是否没有过期,0已过期 1正常
*/
private Integer credentialsNoExpired;
/**
* 账号是否没有锁定,0已锁定 1正常
*/
private Integer accountNoLocked;
/**
* 账号是否启用,0禁用 1启用
*/
private Integer accountEnabled;
/**
* 创建时间
*/
private Date createTime;
/**
* 创建人
*/
private Integer createBy;
/**
* 编辑时间
*/
private Date editTime;
/**
* 编辑人
*/
private Integer editBy;
/**
* 最近登录时间
*/
private Date lastLoginTime;
private static final long serialVersionUID = 1L;
/**
* 用户的角色list
*/
@JsonIgnore
private List<TRole> tRoleList;
/**
* 用户的权限标识符
*/
@JsonIgnore
private List<TPermission> tPermissionsList;
//-----------------实现UserDetails当中相关的方法(7个)--------------------
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
// 角色权限控制
for (TRole tRole : this.tRoleList) {
// 角色的名称必须以ROLE_开头
authorities.add(new SimpleGrantedAuthority("ROLE_"+tRole.getRole()));
}
return authorities;
}
@JsonIgnore
@Override
public String getPassword() {
return this.loginPwd;
}
@Override
public String getUsername() {
return this.loginAct;
}
@Override
public boolean isAccountNonExpired() {
return this.accountNoExpired == 1;
}
@Override
public boolean isEnabled() {
return this.accountEnabled == 1;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNoExpired == 1;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNoLocked == 1;
}
}
通过自定义的 CustomUserDetailsService
,我们可以在用户登录时,从数据库中加载用户信息:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库查询用户
TUser user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
// 返回自定义的 UserDetails 对象
return user;
}
}
说明:此处的 TUser
类需要实现 Spring Security 的 UserDetails
接口,并重写其中的方法,其中最关键的便是 getAuthorities()
方法。
1.2 加载用户角色
用户的角色通常保存在数据库中(例如通过 t_role
、t_user_role
和 t_user
三个表的多对多关系实现)。在用户成功认证后,我们需要为其加载角色信息,并在 TUser
对象中设置角色列表(例如 tRoleList
)。
SQL 查询语句如下:
SELECT tr.*
FROM t_role tr
LEFT JOIN t_user_role tur ON tr.id = tur.role_id
LEFT JOIN t_user tu ON tu.id = tur.user_id
WHERE tu.id = #{userId}
在获取角色列表后,需要重写 getAuthorities()
方法,将角色转换为 Spring Security 的 GrantedAuthority
对象。由于 Spring Security 要求角色名称以 "ROLE_"
为前缀,我们在转换时进行拼接:
// TUser类继承了UserDetails
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (TRole tRole : this.tRoleList) {
// 角色名称必须以 "ROLE_" 为前缀
authorities.add(new SimpleGrantedAuthority("ROLE_" + tRole.getRole()));
}
return authorities;
}
在这里,我们通过 SimpleGrantedAuthority
将每个角色的名称转化为 Spring Security 所要求的权限标识。 (SimpleGrantedAuthority
是GrantedAuthority的一个实现类)
在下面的示例中,我们进一步展示了如何在用户登录时加载角色并返回完整的用户信
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final TUserMapper userMapper;
private final TRoleMapper tRoleMapper;
/**
* 该方法会在 Spring Security 框架登录时调用
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 通过用户名查询数据库
TUser user = userMapper.selectByLoginAct(username);
if (user == null){
throw new UsernameNotFoundException("用户不存在");
}
// 查询该用户的角色列表(一个用户可能有多个角色)
List<TRole> tRoleList = tRoleMapper.selectByUserId(user.getId());
user.setTRoleList(tRoleList);
return user; // TUser 实现了 UserDetails 接口,包含所有必须字段
}
}
当你调用了user.setTRoleList(tRoleList)
之后,其实只是将查询到的角色列表赋值给了TUser
对象的属性tRoleList
。而在getAuthorities()
方法中,Spring Security会调用此方法来获取该用户所拥有的权限。具体过程如下:
赋值角色列表
当调用setTRoleList(tRoleList)
后,TUser
对象内部的tRoleList
属性中保存了所有该用户对应的角色信息(例如:角色名、角色标识等)。转换为GrantedAuthority
在getAuthorities()
方法中,通过遍历tRoleList
中的每一个TRole
对象,并使用new SimpleGrantedAuthority(tRole.getRole())
将每个角色转换成一个GrantedAuthority
对象。这里的SimpleGrantedAuthority
是Spring Security中用于表示权限的一个简单实现。返回权限集合
最后将所有转换后的GrantedAuthority
对象添加到一个集合中,并返回这个集合。Spring Security在后续的认证和授权过程中,会根据这个集合来判断用户是否具备相应的访问权限。
这个转换一般发生在用户成功认证后,Spring Security 构建认证对象(例如 UsernamePasswordAuthenticationToken
)的时候。在这个过程中:
- 认证阶段:当用户通过
loadUserByUsername
方法加载用户信息后,返回的TUser
对象中包含了角色列表。 - 构建 Authentication 对象:Spring Security 在构造认证对象时会调用
getAuthorities()
方法,从而将用户的角色转换为权限集合。 - 授权检查时:在后续的请求处理中,系统会根据这个权限集合进行授权判断,决定是否允许访问特定资源
1.3. 给角色配置能访问的资源(使用切面拦截,使用注解)
在 Spring Security 中,角色权限控制常通过注解来实现。我们可以在方法上使用 @PreAuthorize
或 @PostAuthorize
注解来进行权限控制:
@PreAuthorize
:在方法调用前进行权限验证,常用。@PostAuthorize
:在方法调用后进行权限验证,较少使用。
首先,确保启用方法级安全性。在 Spring Boot 配置类中添加 @EnableMethodSecurity
注解。
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
// 配置其他安全设置
}
然后,你可以使用 @PreAuthorize
和 @PostAuthorize
来控制方法的访问:
@RestController
public class ClueController {
@RequestMapping("/api/clue/index")
public String index(){
return "index";
}
@RequestMapping("/api/clue/menu")
@PreAuthorize(value = "hasRole('saler')")
public String clueMenu(){
return "clueMenu";
}
@RequestMapping("/api/clue/child")
@PreAuthorize(value = "hasRole('saler')")
public String clueMenuChild(){
return "clueMenuChild";
}
@RequestMapping("/api/clue/del")
@PreAuthorize(value = "hasRole('admin')")
public String clueDel(){
return "clueDel";
}
/**
* 'admin','manager' 其中任意一个角色都可以访问
* @return
*/
@RequestMapping("/api/clue/export")
@PreAuthorize(value = "hasAnyRole('admin','manager')")
public String clueExport(){
return "clueExport";
}
}
在方法上添加这些注解后,Spring Security 会自动根据当前用户的角色来进行验证,确保只有符合条件的用户能够访问指定的资源。
总结
- 查询用户:通过自定义
UserDetailsService
从数据库加载用户信息。 - 配置用户角色:通过 SQL 查询联合
t_role
、t_user_role
和t_user
表来获取用户的角色。 - 配置资源访问权限:使用
@PreAuthorize
和@PostAuthorize
注解来控制方法访问权限。
通过以上步骤,你可以实现基于角色的权限控制,从而确保不同角色的用户能够访问他们有权限访问的资源。
资源权限控制
资源是什么?
资源就是我们的controller的http接口;
2.2. 需要有一个用户;(从数据库查询用户)
TUser user = userMapper.selectByLoginAct(username);
2.2 基于权限标识符的资源控制
除了角色控制之外,还可以通过权限标识符(例如 clue:list
、clue:add
等)来实现更细粒度的资源控制。具体实现步骤如下:
扩展用户信息:在 TUser
类中增加权限列表属性,例如:
private List<TPermission> tPermissionsList;
加载用户权限:通过 SQL 查询语句,从关联的权限表中获取用户的权限标识符。示例 SQL 如下:
<select id="selectByUserId" resultType="com.gege.codepermission.entity.TPermission"
parameterType="java.lang.Integer">
select tp.*
from t_permission tp
left join t_role_permission trp on tp.id = trp.permission_id
left join t_role tr on tr.id = trp.role_id
left join t_user_role tur on tur.role_id = tr.id
left join t_user tu on tu.id = tur.user_id
where tu.id = #{userId} and tp.type = 'button'
</select>
基于权限标识符配置资源访问:
使用 @PreAuthorize 和 @PostAuthorize 注解
首先,在 Spring Boot 配置类中开启方法级安全控制:
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
// 其他安全配置
}
通过 @PreAuthorize
注解检查用户是否具备某一权限:
@RestController
public class ClueController {
/**
* 资源权限控制:访问线索列表需要具备权限标识符 clue:list
* 权限标识符通常采用 “模块名:功能名” 的格式
*/
@RequestMapping("/api/clue/list")
@PreAuthorize("hasAuthority('clue:list')")
public String clueList(){
return "clueList";
}
/**
* 资源权限控制:访问添加线索页面需要具备权限标识符 clue:add
*/
@RequestMapping("/api/clue/input")
@PreAuthorize("hasAuthority('clue:add')")
public String clueInput(){
return "clueInput";
}
/**
* 资源权限控制:删除线索需要具备权限标识符 clue:del
*/
@RequestMapping("/api/clue/del")
@PreAuthorize("hasAuthority('clue:del')")
public String clueDel(){
return "clueDel";
}
/**
* 资源权限控制:导出线索时,具备任意一个权限即可,例如:
* - clue:export
* - clue:download
*/
@RequestMapping("/api/clue/export")
@PreAuthorize("hasAnyAuthority('clue:export','clue:download')")
public String clueExport(){
return "clueExport";
}
}
注意:与 hasRole
不同,hasAuthority
不会自动添加前缀,因此权限标识符应与数据库中配置的保持一致。
自定义无权限页面:
直接在resources/static/error下面配置页面即可
Spring Boot 内置了一个默认的错误处理机制。当应用返回错误状态码(例如 403)时,Spring Boot 会自动查找与该状态码匹配的错误页面。如果在 resources/static/error
下存在对应名称(如 403.html
)的页面,系统就会直接返回该静态页面,而无需额外配置或编写代码。这种设计符合 Spring Boot “约定优于配置” 的理念,简化了错误处理的配置过程。