美文网首页
分布式无状态服务下的业务与数据权限治理

分布式无状态服务下的业务与数据权限治理

作者: geweixinerr | 来源:发表于2020-02-29 11:09 被阅读0次

久未写博客,文字略有生疏。这篇主要是关于分布式集群架构下,无状态应用服务器基于Shiro与Jwt的集成解决业务与数据权限治理的技术方案。本文不涉及任何权限数据模型。

在叙述之前,首先先来了解下何为无状态应用服务器,一般来说Web应用或多或少都会涉及到状态这个概念,何为状态?通俗点说,即对服务器的访问产生了留存数据。客户端的访问行为依赖于这些数据,且这些数据会随着请求的动作而产生变化,如Java当中的HttpSession,此即为状态。
我们先看下常规的应用服务器集群架构,如下图:


架构图.png

假设我们的应用服务器是存在状态的,使用HttpSession或者其他。此刻由于访问量激增需要弹性扩容,那么我们是否可以保证客户端的访问不受影响呢?譬如:保持登录状。客观的说这不难,一些硬件级别的设备如F5或软负载Nginx都提供了会话保持的粘滞策略,但都有其局限性。就举一个场景来表述下这个局限性:路由的目标服务器扩容期间崩溃了,那所有的状态数据就都丢失了,显然无法满足高可用特性。那我们再看看无状态服务器,其不存储任何状态信息。所有的集群节点下任意一台服务器都是一样的,任何一台宕机都不影响服务的可用性,完全满足高可用性的设计指标。

于此,一些基础知识的普及已经完成,有想更深入了解的同学推荐一本书<大型网站技术架构>。再回过头看我们需要解决的问题:1.无状态下的业务与数据权限治理,安全这块侧重于解决token防窃持。 我们采用的技术栈是SpringBoot + Shiro + Jwt, 这个技术栈当中Shrio的权限治理默认是基于用户会话的与HttpSession类似。显然这个默认行为违背了我们设计无状态应用服务器的初衷。我们需要加以改造,核心代码如下:

    @Bean(value = "securityManager")
    public SecurityManager securityManager(@Qualifier(value = "userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);
        // 无状态应用服务器禁止session创建
        DefaultSubjectDAO subjectDao = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator webSubjectDao = (DefaultSessionStorageEvaluator) subjectDao
                .getSessionStorageEvaluator();
        webSubjectDao.setSessionStorageEnabled(false);
        securityManager.setSubjectDAO(subjectDao);
        securityManager.setSubjectFactory(new StatelessDefaultSubjectFactory());
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        defaultWebSessionManager.setSessionValidationSchedulerEnabled(false);
        securityManager.setSessionManager(defaultWebSessionManager);

        return securityManager;
    }

完成了这块的改造后,Shiro也就完成了无状态的改造。

那如何与Jwt集成呢?首先我们需要了解什么是Jwt,根据百度百科描述:Jwt是轻量级的客户端与服务器认证通信解决方案。Jwt数值存储于客户端,介质可以是cookie/localStorage或者其它,每次请求时需要将Jwt签名数值传递至服务器, 服务器主要完成对Jwt的验签即可完成对访问请求的准确性认证。部分集成代码如下:

    /**
     * jwt 拦截具体动作
     * 
     * @author gewx
     **/
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        // 从请求头或者URL当中获取token
        String token = ObjectUtils.defaultIfNull(req.getHeader(AUTH_TOKEN), req.getParameter(AUTH_TOKEN));
        if (StringUtils.isBlank(token)) {
            String responseJson = JSONObject
                    .toJSONString(Response.FAIL.newBuilder().addGateWayCode(GateWayCode.E0001).toResult());
            outFail(resp, responseJson);
            return false;
        }

        try {
            try {
                               //本地鉴权/远程鉴权
                boolean bool = JwtUtils.verifyToken(token);
                if (!bool) {
                    String responseJson = JSONObject.toJSONString(
                            Response.FAIL.newBuilder().addGateWayCode(GateWayCode.E0002).out("鉴权失败~").toResult());
                    outFail(resp, responseJson);
                    return false;
                }
            } finally {
                /**
                 * create new token 无论认证通过与否,token必须具备一次消费属性
                 **/
                Jwt.JwtBean bean = JwtUtils.parseToken(token);
                JwtToken jwtToken = new JwtToken(
                        Jwt.create().setUserName(bean.getUserName()).setExpires(30).build().sign());
                getSubject(request, response).login(jwtToken);
                resp.setHeader(AUTH_TOKEN, jwtToken.getToken());
            }
        } catch (Exception ex) {
            String responseJson = JSONObject.toJSONString(
                    Response.FAIL.newBuilder().addGateWayCode(GateWayCode.E9999).out("token 认证失败~").toResult());
            outFail(resp, responseJson);
            return false;
        }
        return true;
    }

基于此,Shrio与Jwt核心集成部分就算是结束了。那我们接着聊聊Session服务器的作用之一:解决token窃持。先上图:


鉴权.jpg

何为token窃持? 即发送给客户端的token令牌被第三方中间人获取,中间人通过此token模拟合法身份进行恶意请求。那如上架构是如何解决的呢?核心的思路是令牌必须只能消费一次,基于Session服务器利用缓存或其他存储介质完成token认证留痕。凡是存在消费记录的token都会被记录下来,即可判断出是否是重复使用。当然如果请求量大,对于缓存的存储压力也是比较大的。个人设计的Jwt内部结构如下:

//userName为持有人,expiresDate为过期时间
{"expires":30,"expiresDate":1582945397426,"userName":"userName"}

从上图可以看出鉴权分为本地鉴权与远程鉴权:本地鉴权根据expiresDate做判断,集群环境下需要保持时钟一致性。 本地鉴权通过-->远程鉴权,采用Redis作为存储介质可以设置过期时间 > expiresDate即可。如token有效期30分钟,缓存可以设置为35分钟即可,有效解决了token大量堆积问题。

灵魂一问:假设窃持发生在第一次请求时,该如何处理呢?即token未被消费时即被窃持了。好吧,有时间再写下一篇吧。核心思路是:数据防篡改。

PS:题外话1:窃持问题不仅仅是token设计独有的,HttpSession也有。它是同种问题的变种延伸。题外话2:有条件建议全站Https,真的可以省去很多问题。

相关文章

  • 分布式无状态服务下的业务与数据权限治理

    久未写博客,文字略有生疏。这篇主要是关于分布式集群架构下,无状态应用服务器基于Shiro与Jwt的集成解决业务与数...

  • 后台

    0.业务梳理 1.权限控制(权限申请、权限授权、角色)RBAC 2.状态与操作 3.新增数据(字段信息) 4.删除...

  • dubbo常见的一些面试题

    什么是Dubbo? Duubbo是一个RPC远程调用框架, 分布式服务治理框架 什么是Dubbo服务治理? 服务与...

  • 分布式锁

    因为扩展性考虑,分布式的服务一般不能有状态,这个时候我们会将状态数据转移到数据库或分布式缓存中。分布式锁就是与状态...

  • 分布式系统中的必备良药 —— RPC

    一、前言 在上一篇分布式系统系列中《分布式系统中的必备良药 —— 服务治理》中阐述了服务治理的一些概念,那么与服务...

  • 数据治理中的有趣发现(一)

    数据治理,一般来说,涉及数据获取的治理、数据流程治理、数仓模型治理、数据权限治理、指标体系治理、数据应用能力提升、...

  • 15.分布式事务Seata

    在分布式系统下,一个业务跨越多个服务或数据源,每个服务都是一个分支事务,要保证所有分支事务最终状态一致,这样的事务...

  • seata笔记—处理分布式事务

    1.分布式事务的问题 在微服务的架构下,随着业务服务的拆分和数据库的拆分,会存在多个业务对应多个数据库的情况,如下...

  • 谈谈为什么需要服务治理(Dubbo)

    服务治理主要针对于当前分布式架构下多服务、微服务等。 服务是分布式系统下的一个不大不小的部分,有了服务的组成,整个...

  • 什么是ZooKeeper?及其应用场景简介

    概念 一个分布式协调框架,主要用于分布式场景下的数据管理问题,如:统一命名服务,状态同步服务,集群管理,分布式应用...

网友评论

      本文标题:分布式无状态服务下的业务与数据权限治理

      本文链接:https://www.haomeiwen.com/subject/efbuhhtx.html