@Secured(),@PreAuthorize()
前⾯简单的提到过这两个注解的区别,那只是从配置以及原理上做的说明,今天,将从使⽤即代码层⾯加以说明这两个的使⽤注意事项!
⾸先, 若是⾃⼰实现⽤户信息数据库存储的话,需要注意UserDetails的函数(下⾯代码来⾃于Spring boot 1.2.7 Release的依赖 Springsecurity 3.2.8):
1 /**
2 * Returns the authorities granted to the user. Cannot return null.3 *
4 * @return the authorities, sorted by natural key (never null)5 */
6 Collection extends GrantedAuthority> getAuthorities();
在我的MUEAS项⽬中,这个接⼝函数的实现是下⾯这个样⼦的:
1 public class SecuredUser extends User implements UserDetails{ 2
3 private static final long serialVersionUID = -15014002267036054L; 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 authorities = new ArrayList<>();34 Preconditions.checkNotNull(user, \"user在使⽤之前必须给予赋值\");35 Role role = user.getRole();3637 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 authorities = new ArrayList<>(); 4 Preconditions.checkNotNull(user, \"user在使⽤之前必须给予赋值\"); 5 Role role = user.getRole(); 67 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\ 2 @Secured({\"ROLE_ROOT\ 3 @ResponseBody
4 public Map userName(User user, @RequestParam(value = \"username\") String username){ 5 Map modelMap = new HashMap(); 6 System.out.println(username); 78 user.setUsername(username); 9 userService.update(user);10 11
12 modelMap.put(\"status\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 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 authority10 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 = true5 @Configuration
6 public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {7 8 }
上⾯的修改,将会实现AccessDecissionManager列表中AccessDecisionVoter,多出⼀个voter,即PreInvocationAuthorizationAdviceVoter.并且修改函数上的注解:
1 @RequestMapping(value = \"/setting/username\ 2 @PreAuthorize(\"hasRole('ROOT')\") //或则@PreAuthorize(\"hasAuthority('ROOT')\") 3 @ResponseBody
4 public Map userName(User user, @RequestParam(value = \"username\") String username){ 5 Map modelMap = new HashMap();6 System.out.println(username); 7
8 user.setUsername(username); 9 userService.update(user);10 11
12 modelMap.put(\"status\13 return modelMap;14 }
这样的话,就可以正常实现函数级别的权限控制了。
是不是有点绕?反正这个问题折腾了我差不多⼀上午。。。。