基于request的授权
HttpSecurity 权限配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> {
authorize
// 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
.requestMatchers("/users/get").hasAuthority("admin")
// 放行请求:针对含有 tourist 权限的用户放行 /get 接口
.requestMatchers("/test/auth").hasAuthority("tourist")
// 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
.requestMatchers("/users/list").hasRole("ROLE_admin")
// 对所有用户放行
.requestMatchers("/login", "/test/**").permitAll()
// 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
.anyRequest()
// 以认证的会自动授权
.authenticated();
});
http.exceptionHandling(exc -> {
// 用户未认证
exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
// 请求被拒绝
exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
}
);
// 开启跨域请求
http.cors(withDefaults());
return http.build();
}
在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username:" + username);
// 用户名称不唯一,这里的账号名account,不是用户名
// 这里user实现了Security的UserDetails对象
UsersDO user = usersMapper.selectByAccount(username);
if (user == null || StringUtils.isEmpty(user.getPassword())) {
throw new UsernameNotFoundException(username + ":用户不存在");
}
// 获取用户角色列表
List<RoleDO> list = usersMapper.selectByAccountRole(username);
user.setList(list);
return user;
}
获取用户信息
@Mapper
public interface UsersMapper extends BaseMapper<Users> {
/**
* 分页查询
*/
List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);
@Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user where ACCOUNT =#{account}")
UsersDO selectByAccount(@Param("account") String account);
@Select("select t1.ACCOUNT ,t2.role_name from tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account \n" +
"left join tm_role t3 on t2.role_name =t3.role_name where t1.ACCOUNT =#{account}")
List<RoleDO> selectByAccountRole(@Param("account") String username);
}
实体类:
@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {
/**
* 账号
*/
private String id;
/**
* 账号
*/
private String account;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 逻辑删除
*/
@TableLogic
@TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
private String deleted;
/**
* 用户角色列表
*/
private List<RoleDO> list;
/**
* 权限列表
*/
private Set<GrantedAuthority> authorities =new HashSet<>();
@Override
public boolean isEnabled() {
// 通过逻辑删除字段判断是否启用
return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
}
@Override
public boolean isAccountNonExpired() {
//用户是否未过期
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// 用户凭证是否未过期
return true;
}
@Override
public boolean isAccountNonLocked() {
// 用户是否没有被锁定
return true;
}
/**
* 获取权限列表,暂时以角色名代表权限
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 权限列表
for (RoleDO roleDO : list){
authorities.add(()->roleDO.getRoleName());
}
return authorities;
}
/**
* 设置角色 , 这里参考了 Security 的源码
* @param roles
*/
public void roles(String... roles) {
for (String role : roles) {
// 这里要加上前缀 “ROLE_”
Assert.isTrue(!role.startsWith("ROLE_"),
() -> role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
}
}
用户登录后,如果没有对应权限,默认会抛出403异常
基于request的授权
HttpSecurity 权限配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> {
authorize
// 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
.requestMatchers("/users/get").hasAuthority("admin")
// 放行请求:针对含有 tourist 权限的用户放行 /get 接口
.requestMatchers("/test/auth").hasAuthority("tourist")
// 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
.requestMatchers("/users/list").hasRole("ROLE_admin")
// 对所有用户放行
.requestMatchers("/login", "/test/**").permitAll()
// 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
.anyRequest()
// 以认证的会自动授权
.authenticated();
});
http.exceptionHandling(exc -> {
// 用户未认证
exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
// 请求被拒绝
exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
}
);
// 开启跨域请求
http.cors(withDefaults());
return http.build();
}
在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username:" + username);
// 用户名称不唯一,这里的账号名account,不是用户名
// 这里user实现了Security的UserDetails对象
UsersDO user = usersMapper.selectByAccount(username);
if (user == null || StringUtils.isEmpty(user.getPassword())) {
throw new UsernameNotFoundException(username + ":用户不存在");
}
// 获取用户角色列表
List<RoleDO> list = usersMapper.selectByAccountRole(username);
user.setList(list);
return user;
}
获取用户信息
@Mapper
public interface UsersMapper extends BaseMapper<Users> {
/**
* 分页查询
*/
List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);
@Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user where ACCOUNT =#{account}")
UsersDO selectByAccount(@Param("account") String account);
@Select("select t1.ACCOUNT ,t2.role_name from tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account \n" +
"left join tm_role t3 on t2.role_name =t3.role_name where t1.ACCOUNT =#{account}")
List<RoleDO> selectByAccountRole(@Param("account") String username);
}
实体类:
@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {
/**
* 账号
*/
private String id;
/**
* 账号
*/
private String account;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 逻辑删除
*/
@TableLogic
@TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
private String deleted;
/**
* 用户角色列表
*/
private List<RoleDO> list;
/**
* 权限列表
*/
private Set<GrantedAuthority> authorities =new HashSet<>();
@Override
public boolean isEnabled() {
// 通过逻辑删除字段判断是否启用
return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
}
@Override
public boolean isAccountNonExpired() {
//用户是否未过期
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// 用户凭证是否未过期
return true;
}
@Override
public boolean isAccountNonLocked() {
// 用户是否没有被锁定
return true;
}
/**
* 获取权限列表,暂时以角色名代表权限
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 权限列表
for (RoleDO roleDO : list){
authorities.add(()->roleDO.getRoleName());
}
return authorities;
}
/**
* 设置角色 , 这里参考了 Security 的源码
* @param roles
*/
public void roles(String... roles) {
for (String role : roles) {
// 这里要加上前缀 “ROLE_”
Assert.isTrue(!role.startsWith("ROLE_"),
() -> role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
}
}
用户登录后,如果没有对应权限,默认会抛出403异常
基于request的授权
HttpSecurity 权限配置
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorize -> {
authorize
// 放行请求:针对含有 admin 权限的用户放行 /user/get 接口
.requestMatchers("/users/get").hasAuthority("admin")
// 放行请求:针对含有 tourist 权限的用户放行 /get 接口
.requestMatchers("/test/auth").hasAuthority("tourist")
// 放行请求:针对 ROLE_admin 角色,放行/users/list 接口
.requestMatchers("/users/list").hasRole("ROLE_admin")
// 对所有用户放行
.requestMatchers("/login", "/test/**").permitAll()
// 所有的请求都需要授权保护,不授权保护的请求要写在anyRequest之前
.anyRequest()
// 以认证的会自动授权
.authenticated();
});
http.exceptionHandling(exc -> {
// 用户未认证
exc.authenticationEntryPoint(new MyAuthenticationSuccessHandler());
// 请求被拒绝
exc.accessDeniedHandler(new MyAuthenticationSuccessHandler());
}
);
// 开启跨域请求
http.cors(withDefaults());
return http.build();
}
在 loadUserByUsername 方法,在查询数据库用户信息的时候,同时查询出用户的权限,这里以角色名代指权限
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("username:" + username);
// 用户名称不唯一,这里的账号名account,不是用户名
// 这里user实现了Security的UserDetails对象
UsersDO user = usersMapper.selectByAccount(username);
if (user == null || StringUtils.isEmpty(user.getPassword())) {
throw new UsernameNotFoundException(username + ":用户不存在");
}
// 获取用户角色列表
List<RoleDO> list = usersMapper.selectByAccountRole(username);
user.setList(list);
return user;
}
获取用户信息
@Mapper
public interface UsersMapper extends BaseMapper<Users> {
/**
* 分页查询
*/
List<ResUsers> listByPage(Page<ResUsers> page, @Param("param") ReqUsersQuery req);
@Select("select ID,ACCOUNT ,USERNAME,PASSWORD ,SEX ,AGE ,PHONE_NUMBER ,DEPARTMENT_NO,DELETED from tm_user where ACCOUNT =#{account}")
UsersDO selectByAccount(@Param("account") String account);
@Select("select t1.ACCOUNT ,t2.role_name from tm_user t1 left join tm_user_role t2 on t1.ACCOUNT =t2.account \n" +
"left join tm_role t3 on t2.role_name =t3.role_name where t1.ACCOUNT =#{account}")
List<RoleDO> selectByAccountRole(@Param("account") String username);
}
实体类:
@Data
@Slf4j
@TableName("tm_user")
// 实现了Security的UserDetails对象
public class UsersDO implements UserDetails {
/**
* 账号
*/
private String id;
/**
* 账号
*/
private String account;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 逻辑删除
*/
@TableLogic
@TableField(value = "DELETED", fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)
private String deleted;
/**
* 用户角色列表
*/
private List<RoleDO> list;
/**
* 权限列表
*/
private Set<GrantedAuthority> authorities =new HashSet<>();
@Override
public boolean isEnabled() {
// 通过逻辑删除字段判断是否启用
return Integer.valueOf(this.getDeleted()) == 0 ? true : false;
}
@Override
public boolean isAccountNonExpired() {
//用户是否未过期
return true;
}
@Override
public boolean isCredentialsNonExpired() {
// 用户凭证是否未过期
return true;
}
@Override
public boolean isAccountNonLocked() {
// 用户是否没有被锁定
return true;
}
/**
* 获取权限列表,暂时以角色名代表权限
* @return
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 权限列表
for (RoleDO roleDO : list){
authorities.add(()->roleDO.getRoleName());
}
return authorities;
}
/**
* 设置角色 , 这里参考了 Security 的源码
* @param roles
*/
public void roles(String... roles) {
for (String role : roles) {
// 这里要加上前缀 “ROLE_”
Assert.isTrue(!role.startsWith("ROLE_"),
() -> role + " cannot start with ROLE_ (it is automatically added)");
authorities.add(new SimpleGrantedAuthority("ROLE_" + role));
}
}
}
用户登录后,如果没有对应权限,默认会抛出403异常
可以自定义请求被拒绝的返回结果:
@Configuration
@Slf4j
public class MyAuthenticationSuccessHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.info("请求被拒绝-------------->");
response.setContentType("application/json;charset=UTF-8");
Map<String, Object> res = new HashMap<>();
res.put("code", "-1");
res.put("message", "请求被拒绝");
Map<String, Object> data = new HashMap<>();
res.put("data", data);
response.getWriter().println(JSONUtil.toJsonStr(res));
}
}
RBAC模型
权限模型
- RBAC(Role Based Access Controll):基于角色的权限控制,简单来说就是:用户-角色-权限-资源,涉及的表有:
- 用户(t_user)
- 用户_角色(t_user_role)【N对N关系需要中间表】
- 角色(t_role)
- 角色_权限(t_role_perm)
- 权限(t_permission)
- ACL(Access Controll List):基于用户的权限控制,涉及的表有:
- 用户(t_user)
- 用户_权限(t_user_perm)
- 权限(t_permission)
通过 RBAC 获取到用户的具体权限后,再通过 Security 的 用户-权限-资源 来进行权限控制
// 针对某个资源,对某个权限来放行
.requestMatchers("/users/get").hasAuthority("function_id")
基于方法的授权
使用注解,针对某个方法开启授权保护
开启配置:
@Configuration
// 开启spring Security的自定义配置,在springBoot项目中可以省略此注解,因为Security-stater默认开启了自定义配置
@EnableWebSecurity
// 开启基于方法的授权
@EnableMethodSecurity
public class SecurityConfig {
}
给方法增加权限保护
@PreAuthorize("hasRole('admin')")
@GetMapping("/apply")
public BaseResultModel apply(){
return BaseResultModel.success("授权返回成功");
}
注解及含义
- @PostAuthorize 在目标方法执行之后进行权限校验
- @PostFilter 在目标方法执行之后对返回结果进行过滤
- @PreAuthorize 在目标方法执行之前进行权限校验
- @PreFilter 在目标方法执行之前对方法参数进行过滤
- @Secured 访问目标方法必须具备对应的角色
- @DenyAll 拒绝所有访问
- @PermitAll 允许所有访问
- @RolesAll 访问目标方法必须具备对应的角色
授权的原理分析
- ConfigAttribute在springsecurity中,用户请求一个资源(通常是一个接口或者Java方法)需要的角色会被封装成一个ConfigAttribute对象,在ConfigAttribute中只有一个getAttribute方法,该方法赶回一个String字符串(角色名称)。一般的角色名称都带有一个ROLE_前缀,投票器AccessDecisionVoter所做的事情,其实就是比较用户所具有的角色和请求某个资源所需要的ConfigAttribute之间的关系。
- AccessDecisionVoter和AccessDecisionManager都有众多实现类。在AccessDecisionManager中会挨个遍历AccessDecisionVoter,进而决定是否允许用户方法,因而AccessDecisionVoter和AccessDecisionManager俩者关系类似于AuthenticationProvicder和ProviderManager的关系。