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 接口,是有權訪問的:

有權限訪問學生列表

經過簡單的測試,理論上,是可行的。

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