通过自定义springSecurity的授权逻辑,参考上篇博客 :springSecurity授权简单分析 ,你会发现它的使用场景是 基于角色的访问控制,网上很多文章写的也是基于角色的权限控制。但是,我很觉得很诧异,为什么很少有人思考 基于资源的访问控制?毕竟,在shiro中是可以实现的。为什么要思考基于资源的访问控制? 我的出发点是,即使是同样的角色,也可以拥有不用的权限,可以动态的给用户添加权限,而不是给角色绑定死了。思考之后,觉得 扩展access()的SpEL表达式,或许是可行的,思路如下:
(1)在返回认证信息的时候,把代表资源的字符串放入权限集合中,这里就用resUrl来表示吧,这里觉得最好用url表示,方便后面 的判断。
(2)在扩展access()的SpEL表达式的判断方法中,通过遍历已认证用户的权限集合,判断是否包含请求所需要的资源,如果包 含,则表示有权限,反之,则无。
接下来看看代码是如何实现的:
1. 在返回认证信息的时候,把代表资源的字符串放入权限集合中,这里就用resUrl来表示吧,这里觉得最好用url表示,方便后面 的判断。
(1)在User类中定义权限信息
//测试基于 资源的访问控制
private List<Permission> permissions = new ArrayList<>();
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// return getRoles();
//下面是为了测试 基于 资源的访问控制
List <SimpleGrantedAuthority> list = null;
List<Permission> permissionList = getPermissions();
if (permissionList !=null && permissionList.size() > 0){
list = new ArrayList<>(permissionList.size());
SimpleGrantedAuthority simpleGrantedAuthority = null;
for (Permission p : permissionList){
simpleGrantedAuthority = new SimpleGrantedAuthority(p.getUrl());
list.add(simpleGrantedAuthority);
}
}
return list;
}
(2)在UserServiceImpl中,添加权限信息:
@Service
public class UserServiceImpl implements UserService, UserDetailsService{
@Autowired
private UserMapper userMapper;
@Autowired
private RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.getUserByUsername(username);
if (user != null){
user.setRoles(roleMapper.getRoleListByUserId(user.getId()));
}
//下面是为了测试 基于 资源的访问控制,这里为了方便,就不查数据库了
List<Permission> permissions = new ArrayList<>(2);
Permission p = new Permission(11,"/user/me");
permissions.add(p);
if ("qxf".equals(username)){
Permission p2 = new Permission(22,"/student/list");
permissions.add(p2);
}
user.setPermissions(permissions);
return user;
}
@Override
public List<User> getAllUser(){
return userMapper.getAllUser();
}
}
这里就完成了第一步
2. 在扩展access()的SpEL表达式的判断方法中,通过遍历已认证用户的权限集合,判断是否包含请求所需要的资源,如果包 含,则表示有权限,反之,则无。
(1)自定义权限判断逻辑
@Component
public class BaseResourceControlTest {
/**
* 判断请求的url是否有权访问
*/
public boolean canAccess(HttpServletRequest request, Authentication authentication) {
boolean flag = false;
//如果没有通过认证,则不能访问, anonymousUser是springSecurity放入的匿名用户
Object principal = authentication.getPrincipal();
if(principal == null || "anonymousUser".equals(principal)) {
return flag;
}
//匿名访问的url,在之前配置了,所以这里不通过
if(authentication instanceof AnonymousAuthenticationToken){
return flag;
}
//用户拥有的权限集合,也就是在UserServiceImpl放入的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//URL规则匹配,判断用户是否权限集合中,是否包含请求的url
AntPathRequestMatcher matcher;
String resUrl = "";
if (authorities != null && authorities.size() > 0){
for(GrantedAuthority grantedAuthority : authorities) {
resUrl = grantedAuthority.getAuthority();
matcher = new AntPathRequestMatcher(resUrl);
if(matcher.matches(request)) {
//匹配成功,返回true
flag = true;
break;
}
}
}
return flag;
}
}
(2)使自定义的权限判断逻辑生效
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //允许基于使用HttpServletRequest限制访问
// .antMatchers(HttpMethod.GET,"/user/*").hasAnyRole("USER","ADMIN")
// .antMatchers(HttpMethod.POST,"/user/*").hasRole("ADMIN")
// .antMatchers(HttpMethod.DELETE,"/user/*").hasRole("ADMIN")
// .antMatchers("/admin/*").hasRole("ADMIN")
//有3中方式可以实现动态权限控制:(1)扩展access()的SpEL表达式(2)自定义AccessDecisionManager
// (3)自定义Filter:MyFilterSecurityInterceptor
//(1)扩展access()的SpEL表达式:自定义授权逻辑myAuthService是自定义的类,canAccess是它的方法
// .anyRequest().access("@myAuthService.canAccess(request,authentication)")
//(2)自定义AccessDecisionManager
// .withObjectPostProcessor(new MyObjectPostProcessor())
// .anyRequest().authenticated()
//测试 基于资源的访问控制
.anyRequest().access("@baseResourceControlTest.canAccess(request,authentication)")
.and()
//表单登录
.formLogin()
//登录页面和处理接口
.loginPage("/login.html")
//认证成功后的处理器
.successHandler(myAuthenticationSuccessHandler)
//认证失败后的处理器
.failureHandler(myAuthenticationFailureHandler)
.permitAll()
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
//记住我有效时间为7天
.tokenValiditySeconds(60*60*24*7)
.userDetailsService(userServiceImpl)
.and()
//关闭跨站请求伪造的防护,这里是为了前期开发方便
.csrf().disable()
//访问被拒绝处理器
.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler);
}
其实重点就是一句:
//测试 基于资源的访问控制
.anyRequest().access("@baseResourceControlTest.canAccess(request,authentication)")
这样子,自定义的权限判断逻辑就会生效了。
3.测试一下:
根据UserServiceImpl中:
//下面是为了测试 基于 资源的访问控制,这里为了方便,就不查数据库了
List<Permission> permissions = new ArrayList<>(2);
Permission p = new Permission(11,"/user/me");
permissions.add(p);
if ("qxf".equals(username)){
Permission p2 = new Permission(22,"/student/list");
permissions.add(p2);
}
user.setPermissions(permissions);
可以判断,用户qxf,比sam对了一个对资源的访问权限,即 /student/list
(1)首先,用户sam访问 /user/me接口,是有权限访问的
{"authorities":[{"authority":"/user/me"}],"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":"EA7CC5401ED33975FA2697B138AB26A3"},"authenticated":true,"principal":{"id":3,"username":"sam","password":"$2a$10$.kjQ5.h3jFFbRcECJl7zxu26DCFMpTIhcm3zWTTd5PIdyurWfvoAy","enable":1,"roles":[],"permissions":[{"id":11,"name":null,"url":"/user/me","description":null,"pid":null,"roles":[]}],"enabled":true,"authorities":[{"authority":"/user/me"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true},"credentials":null,"name":"sam"}
(2)用户sam,接着访问 /student/list 接口,则会返回没有权限
权限不足:Access is denied
(3)用户qxf,访问/student/list 接口,是有权访问的:
有权限访问学生列表
经过简单的测试,理论上,是可行的。