通過自定義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 接口,是有權訪問的:
有權限訪問學生列表
經過簡單的測試,理論上,是可行的。