springSecurity实现基于资源的访问控制

       通过自定义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 接口,是有权访问的:

有权限访问学生列表

经过简单的测试,理论上,是可行的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章