查看原文
其他

Spring Security权限框架理论与实战(二)

芥末无疆sss 云时代架构 2019-05-09
    选择“置顶公众号”,精品文章第一时间送达!


1权限缓存



CachingUserDetailsService


Spring Security提供了一个实现了可以缓存UserDetails的UserDetailsService实现类,CachingUserDetailsService

该类的构造接收一个用于真正加载UserDetails的UserDetailsService实现类
当需要加载UserDetails时,其首先会从缓存中获取,如果缓存中没有对应的UserDetails存在,则使用持有的UserDetailsService实现类进行加载,然后将加载后的结果存放在缓存中。

UserDetails与缓存的交互是通过UserCache接口来实现的

CachingUserDetailsService默认拥有UserCache的一个空实现引用NullUserCache


当缓存中不存在对应的UserDetails时将使用引用的UserDetailsService类型的delegate进行加载

加载后再把它存放到Cache中并进行返回

除了NullUserCache之外,Spring Security还为我们提供了一个基于Ehcache的UserCache实现类

public class EhCacheBasedUserCache implements UserCache, InitializingBean {    // ~ Static fields/initializers    // =====================================================================================    private static final Log logger = LogFactory.getLog(EhCacheBasedUserCache.class);    // ~ Instance fields    // ================================================================================================    private Ehcache cache;    // ~ Methods    // ========================================================================================================    public void afterPropertiesSet() throws Exception {        Assert.notNull(cache, "cache mandatory");    }    public Ehcache getCache() {        return cache;    }    public UserDetails getUserFromCache(String username) {        Element element = cache.get(username);        if (logger.isDebugEnabled()) {            logger.debug("Cache hit: " + (element != null) + "; username: " + username);        }        if (element == null) {            return null;        }        else {            return (UserDetails) element.getValue();        }    }    public void putUserInCache(UserDetails user) {        Element element = new Element(user.getUsername(), user);        if (logger.isDebugEnabled()) {            logger.debug("Cache put: " + element.getKey());        }        cache.put(element);    }    public void removeUserFromCache(UserDetails user) {        if (logger.isDebugEnabled()) {            logger.debug("Cache remove: " + user.getUsername());        }        this.removeUserFromCache(user.getUsername());    }    public void removeUserFromCache(String username) {        cache.remove(username);    }    public void setCache(Ehcache cache) {        this.cache = cache;    } }
2自定义决策
AbstractAccessDecisionManager




核心方法


其中的决策类类型-投票器


看一下最常见的投票器



定义了权限前缀


核心方法自然为选举方法

三大投票器

AffirmativeBased

一票通过

/**
* Simple concrete implementation of
* {@link org.springframework.security.access.AccessDecisionManager} that grants access if
* any <code>AccessDecisionVoter</code> returns an affirmative response.
*/

public class AffirmativeBased extends AbstractAccessDecisionManager {

   public AffirmativeBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
       super(decisionVoters);
   }

   // ~ Methods
   // ========================================================================================================

   /**
    * This concrete implementation simply polls all configured
    * {@link AccessDecisionVoter}s and grants access if any
    * <code>AccessDecisionVoter</code> voted affirmatively. Denies access only if there
    * was a deny vote AND no affirmative votes.
    * <p>
    * If every <code>AccessDecisionVoter</code> abstained from voting, the decision will
    * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to
    * false).
    * </p>
    *
    * @param authentication the caller invoking the method
    * @param object the secured object
    * @param configAttributes the configuration attributes associated with the method
    * being invoked
    *
    * @throws AccessDeniedException if access is denied
    */

   public void decide(Authentication authentication, Object object,
           Collection<ConfigAttribute> configAttributes) throws AccessDeniedException 
{
       int deny = 0;

       for (AccessDecisionVoter voter : getDecisionVoters()) {
           int result = voter.vote(authentication, object, configAttributes);

           if (logger.isDebugEnabled()) {
               logger.debug("Voter: " + voter + ", returned: " + result);
           }

           switch (result) {
           case AccessDecisionVoter.ACCESS_GRANTED:
               return;

           case AccessDecisionVoter.ACCESS_DENIED:
               deny++;

               break;

           default:
               break;
           }
       }

       if (deny > 0) {
           throw new AccessDeniedException(messages.getMessage(
                   "AbstractAccessDecisionManager.accessDenied""Access is denied"));
       }

       // To get this far, every AccessDecisionVoter abstained
       checkAllowIfAllAbstainDecisions();
   }
}
ConsensusBased

一半以上功能选举通过

/**
* Simple concrete implementation of
* {@link org.springframework.security.access.AccessDecisionManager} that uses a
* consensus-based approach.
* <p>
* "Consensus" here means majority-rule (ignoring abstains) rather than unanimous
* agreement (ignoring abstains). If you require unanimity, please see
* {@link UnanimousBased}.
*/

public class ConsensusBased extends AbstractAccessDecisionManager {
   // ~ Instance fields
   // ================================================================================================

   private boolean allowIfEqualGrantedDeniedDecisions = true;

   public ConsensusBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
       super(decisionVoters);
   }

   // ~ Methods
   // ========================================================================================================

   /**
    * This concrete implementation simply polls all configured
    * {@link AccessDecisionVoter}s and upon completion determines the consensus of
    * granted against denied responses.
    * <p>
    * If there were an equal number of grant and deny votes, the decision will be based
    * on the {@link #isAllowIfEqualGrantedDeniedDecisions()} property (defaults to true).
    * <p>
    * If every <code>AccessDecisionVoter</code> abstained from voting, the decision will
    * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to
    * false).
    *
    * @param authentication the caller invoking the method
    * @param object the secured object
    * @param configAttributes the configuration attributes associated with the method
    * being invoked
    *
    * @throws AccessDeniedException if access is denied
    */

   public void decide(Authentication authentication, Object object,
           Collection<ConfigAttribute> configAttributes) throws AccessDeniedException 
{
       int grant = 0;
       int deny = 0;
       int abstain = 0;

       for (AccessDecisionVoter voter : getDecisionVoters()) {
           int result = voter.vote(authentication, object, configAttributes);

           if (logger.isDebugEnabled()) {
               logger.debug("Voter: " + voter + ", returned: " + result);
           }

           switch (result) {
           case AccessDecisionVoter.ACCESS_GRANTED:
               grant++;

               break;

           case AccessDecisionVoter.ACCESS_DENIED:
               deny++;

               break;

           default:
               abstain++;

               break;
           }
       }

       if (grant > deny) {
           return;
       }

       if (deny > grant) {
           throw new AccessDeniedException(messages.getMessage(
                   "AbstractAccessDecisionManager.accessDenied""Access is denied"));
       }

       if ((grant == deny) && (grant != 0)) {
           if (this.allowIfEqualGrantedDeniedDecisions) {
               return;
           }
           else {
               throw new AccessDeniedException(messages.getMessage(
                       "AbstractAccessDecisionManager.accessDenied""Access is denied"));
           }
       }

       // To get this far, every AccessDecisionVoter abstained
       checkAllowIfAllAbstainDecisions();
   }

   public boolean isAllowIfEqualGrantedDeniedDecisions() {
       return allowIfEqualGrantedDeniedDecisions;
   }

   public void setAllowIfEqualGrantedDeniedDecisions(
           boolean allowIfEqualGrantedDeniedDecisions) 
{
       this.allowIfEqualGrantedDeniedDecisions = allowIfEqualGrantedDeniedDecisions;
   }
}
UnanimousBased

全票通过才可

 

/**
* Simple concrete implementation of
* {@link org.springframework.security.access.AccessDecisionManager} that requires all
* voters to abstain or grant access.
*/

public class UnanimousBased extends AbstractAccessDecisionManager {

   public UnanimousBased(List<AccessDecisionVoter<? extends Object>> decisionVoters) {
       super(decisionVoters);
   }

   // ~ Methods
   // ========================================================================================================

   /**
    * This concrete implementation polls all configured {@link AccessDecisionVoter}s for
    * each {@link ConfigAttribute} and grants access if <b>only</b> grant (or abstain)
    * votes were received.
    * <p>
    * Other voting implementations usually pass the entire list of
    * <tt>ConfigAttribute</tt>s to the <code>AccessDecisionVoter</code>. This
    * implementation differs in that each <code>AccessDecisionVoter</code> knows only
    * about a single <code>ConfigAttribute</code> at a time.
    * <p>
    * If every <code>AccessDecisionVoter</code> abstained from voting, the decision will
    * be based on the {@link #isAllowIfAllAbstainDecisions()} property (defaults to
    * false).
    *
    * @param authentication the caller invoking the method
    * @param object the secured object
    * @param attributes the configuration attributes associated with the method being
    * invoked
    *
    * @throws AccessDeniedException if access is denied
    */

   public void decide(Authentication authentication, Object object,
           Collection<ConfigAttribute> attributes) throws AccessDeniedException 
{

       int grant = 0;
       int abstain = 0;

       List<ConfigAttribute> singleAttributeList = new ArrayList<>(1);
       singleAttributeList.add(null);

       for (ConfigAttribute attribute : attributes) {
           singleAttributeList.set(0, attribute);

           for (AccessDecisionVoter voter : getDecisionVoters()) {
               int result = voter.vote(authentication, object, singleAttributeList);

               if (logger.isDebugEnabled()) {
                   logger.debug("Voter: " + voter + ", returned: " + result);
               }

               switch (result) {
               case AccessDecisionVoter.ACCESS_GRANTED:
                   grant++;

                   break;

               case AccessDecisionVoter.ACCESS_DENIED:
                   throw new AccessDeniedException(messages.getMessage(
                           "AbstractAccessDecisionManager.accessDenied",
                           "Access is denied"));

               default:
                   abstain++;

                   break;
               }
           }
       }

       // To get this far, there were no deny votes
       if (grant > 0) {
           return;
       }

       // To get this far, every AccessDecisionVoter abstained
       checkAllowIfAllAbstainDecisions();
   }
}

       


3基于SpringBoot环境快速搭建与验证

             

生成


启动


结果

新建 Config 类

Config


http://localhost:8080/hello


访问受限, SS 起作用了!

常见 Case 实现

只要能登录即可

添加用户权限



logout接口测试

角色登录


要使用 ROLE 前缀



指定多角色用户


无管理员权限用户登录失败

数据库管理用户


实现类


由于密码机制的特殊性,需要自定义密码验证机制






(本文版权归原作者所有。转载文章仅为传播更多信息之目的,如有侵权请与我们联系,我们将及时处理。)


- End -

推荐阅读

  1. Elasticsearch搜索引擎性能调优看懂这一篇就够了

  2. 【双11狂欢的背后】微服务注册中心如何承载大型系统的千万级访问?

  3. Spring AOP就是这么简单啦

  4. 可能是全网把 ZooKeeper 概念讲的最清楚的一篇文章

  5. 微服务之服务调用与安全控制

  6. 支付系统

  7. 支付公司账务系统的那些事

  8. 对称加密及AES加密算法

  9. 支付对账系统怎么设计?

  10. 如何健壮你的后端服务

  11. 拜托,面试中请不要让我再写单例模式,小视频全程指导


【推荐书籍】




     


  扫码购买


做互联网时代最适合的架构
开放、分享、协作






    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存