• @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     }

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

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

  • 相关阅读:
    【PHP】window系统中设置计划任务,定时调用某接口
    【php】在laravel中使用 easy-wechat实现企业付款到零钱
    【转载】laravel中使用WangEditor及多图上传
    [PHP] curl: (60) SSL certificate problem: unable to get local issuer certificate
    阿里云服务器win10 访问服务器图片资源提示 401
    【PHP】创瑞短信接口
    C#中Lock锁的对象选择问题
    TCP三次握手,四次挥手异常情况(坑)
    C# Hashtable、HashSet和Dictionary的区别
    浅析C# Dictionary实现原理
  • 原文地址:https://www.cnblogs.com/shihuc/p/5063583.html
Copyright © 2020-2023  润新知