前面簡單的提到過這兩個注解的區別,那只是從配置以及原理上做的說明,今天,將從使用即代碼層面加以說明這兩個的使用注意事項!
首先, 若是自己實現用戶信息數據庫存儲的話,需要注意UserDetails的函數(下面代碼來自於Spring boot 1.2.7 Release的依賴 Spring security 3.2.8):
1 /** 2 * Returns the authorities granted to the user. Cannot return <code>null</code>. 3 * 4 * @return the authorities, sorted by natural key (never <code>null</code>) 5 */ 6 Collection<? extends GrantedAuthority> getAuthorities();
在我的MUEAS項目中,這個接口函數的實現是下面這個樣子的:
1 public class SecuredUser extends User implements UserDetails{ 2 3 private static final long serialVersionUID = -1501400226764036054L; 4 5 private User user; 6 public SecuredUser(User user){ 7 if(user != null){ 8 this.user = user; 9 this.setUserId(user.getId()); 10 this.setUserId(user.getUserId()); 11 this.setUsername(user.getUsername()); 12 this.setPassword(user.getPassword()); 13 this.setRole(user.getRole().name()); 14 //this.setDate(user.getDate()); 15 16 this.setAccountNonExpired(user.isAccountNonExpired()); 17 this.setAccountNonLocked(user.isAccountNonLocked()); 18 this.setCredentialsNonExpired(user.isCredentialsNonExpired()); 19 this.setEnabled(user.isEnabled()); 20 } 21 } 22 23 public void setUser(User user){ 24 this.user = user; 25 } 26 27 public User getUser(){ 28 return this.user; 29 } 30 31 @Override 32 public Collection<? extends GrantedAuthority> getAuthorities() { 33 Collection<GrantedAuthority> authorities = new ArrayList<>(); 34 Preconditions.checkNotNull(user, "user在使用之前必須給予賦值"); 35 Role role = user.getRole(); 36 37 if(role != null){ 38 SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.name()); 39 authorities.add(authority); 40 } 41 return authorities; 42 } 43 }
注意,我在創建SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.name());的時候沒有添加“ROLE_”這個rolePrefix前綴,也就是說,我沒有像下面這個樣子操作:
1 @Override 2 public Collection<? extends GrantedAuthority> getAuthorities() { 3 Collection<GrantedAuthority> authorities = new ArrayList<>(); 4 Preconditions.checkNotNull(user, "user在使用之前必須給予賦值"); 5 Role role = user.getRole(); 6 7 if(role != null){ 8 SimpleGrantedAuthority authority = new SimpleGrantedAuthority(“ROLE_”+role.name()); 9 authorities.add(authority); 10 } 11 return authorities; 12 }
不要小看這一區別,這個將會影響后面權限控制的編碼方式。
具體說來, 若采用@EnableGlobalMethodSecurity(securedEnabled = true)注解,對函數訪問進行控制,那么,就會有一些問題(不加ROLE_),因為,這個時候,AccessDecissionManager會選擇RoleVoter進行vote,但是RoleVoter默認的rolePrefix是“ROLE_”。
當函數上加有@Secured(),我的項目中是@Secured({"ROLE_ROOT"})
1 @RequestMapping(value = "/setting/username", method = RequestMethod.POST) 2 @Secured({"ROLE_ROOT"}) 3 @ResponseBody 4 public Map<String, String> userName(User user, @RequestParam(value = "username") String username){ 5 Map<String, String> modelMap = new HashMap<String, String>(); 6 System.out.println(username); 7 8 user.setUsername(username); 9 userService.update(user); 10 11 12 modelMap.put("status", "ok"); 13 return modelMap; 14 }
而RoleVoter選舉時,會檢測是否支持。如下函數(來自Spring Security 3.2.8 Release默認的RoleVoter類)
1 public boolean supports(ConfigAttribute attribute) { 2 if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getRolePrefix())) { 3 return true; 4 } 5 else { 6 return false; 7 } 8 }
上面的函數會返回true,因為傳遞進去的attribute是來自於@Secured({"ROLE_ROOT"})注解。不幸的時,當進入RoleVoter的vote函數時,就失敗了:
1 public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { 2 int result = ACCESS_ABSTAIN; 3 Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication); 4 5 for (ConfigAttribute attribute : attributes) { 6 if (this.supports(attribute)) { 7 result = ACCESS_DENIED; 8 9 // Attempt to find a matching granted authority 10 for (GrantedAuthority authority : authorities) { 11 if (attribute.getAttribute().equals(authority.getAuthority())) { 12 return ACCESS_GRANTED; 13 } 14 } 15 } 16 } 17 18 return result; 19 }
原因在於,authority.getAuthority()返回的將是ROOT,而並不是ROLE_ROOT。然而,即使將@Secured({"ROLE_ROOT"})改為@Secured({"ROOT"})也沒有用, 所以,即使當前用戶是ROOT權限用戶,也沒有辦法操作,會放回403 Access Denied Exception.
解決的辦法:有兩個。
第一個: 就是將前面提到的UserDetails的接口函數getAuthorities()的實現中,添加前綴,如上面提到的,紅色"ROLE_"+role.name()
第二個: 就是不用@Secured()注解,采用@PreAuthorize():
1 /** 2 * Method Security Configuration. 3 */ 4 @EnableGlobalMethodSecurity(prePostEnabled = true) //替換掉SecuredEnabled = true 5 @Configuration 6 public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { 7 8 }
上面的修改,將會實現AccessDecissionManager列表中AccessDecisionVoter,多出一個voter,即PreInvocationAuthorizationAdviceVoter.
並且修改函數上的注解:
1 @RequestMapping(value = "/setting/username", method = RequestMethod.POST) 2 @PreAuthorize("hasRole('ROOT')") //或則@PreAuthorize("hasAuthority('ROOT')") 3 @ResponseBody 4 public Map<String, String> userName(User user, @RequestParam(value = "username") String username){ 5 Map<String, String> modelMap = new HashMap<String, String>(); 6 System.out.println(username); 7 8 user.setUsername(username); 9 userService.update(user); 10 11 12 modelMap.put("status", "ok"); 13 return modelMap; 14 }
這樣的話,就可以正常實現函數級別的權限控制了。
是不是有點繞?反正這個問題折騰了我差不多一上午。。。。