@Secured(), @PreAuthorize()

前面简单的提到过这两个注解的区别,那只是从配置以及原理上做的说明,今天,将从使用即代码层面加以说明这两个的使用注意事项!

首先, 若是自己实现用户信息数据库存储的话,需要注意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     }

这样的话,就可以正常实现函数级别的权限控制了。

是不是有点绕?反正这个问题折腾了我差不多一上午。。。。

原文地址:https://www.cnblogs.com/shihuc/p/5063583.html