前面简单的提到过这两个注解的区别,那只是从配置以及原理上做的说明,今天,将从使用即代码层面加以说明这两个的使用注意事项!
首先, 若是自己实现用户信息数据库存储的话,需要注意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 }
这样的话,就可以正常实现函数级别的权限控制了。
是不是有点绕?反正这个问题折腾了我差不多一上午。。。。