zoukankan      html  css  js  c++  java
  • ApiAuthValue鉴权机制总结

    一、背景介绍

    1.自动化的配置工具autoconfig介绍

      项目开发过程中,有些配置会随着运行环境的变化而各不相同。如jdbc驱动的配置,在开发环境可能链接到开发本地的数据库,测试环境则有一套测试专用的数据库环境,如果一个应用要部署到多个idc中,那这些配置又有可能各不相同。如果每次上线时候人工的修改一下配置,比较容易出错,而且随着环境的增多成本会线性地增长。

      Autoconfig提供了一种动态替换配置信息的手段。并且Maven的强大插件机制,可以和autoconfig机制结合起来,发挥巨大的威力。pom.xml中配置autoconfig方法如下:

                 <plugin>
                    <groupId>com.alibaba.citrus.tool</groupId>
                    <artifactId>autoconfig-maven-plugin</artifactId>
                    <version>1.2</version>
                    <configuration>
                        <exploding>true</exploding>
                        <includeDescriptorPatterns>
                            <includeDescriptorPattern>autoconf/auto-config.xml</includeDescriptorPattern>
                            <includeDescriptorPattern>autoconf/conf/auto-config.xml</includeDescriptorPattern>
                            <includeDescriptorPattern>autoconf/conf/customize-autoconf/auto-config.xml</includeDescriptorPattern>
                        </includeDescriptorPatterns>
                    </configuration>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>autoconfig</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
    pom.xml

      Convention over Configuration(CoC),约定优于配置,上述配置文件中,充分体现了CoC原则。默认地插件会去扫描autoconf/auto-config.xml或者conf/META-INF/autoconf/auto-config.xml文件。autoconfig使用一套配置模板,为不同的环境生成相应的具体配置。它的核心思想是把一些可变的配置定义为一个模板,在autoconfig运行的时候从这些模板中生成具体的配置文件。

    package test;
    
    import java.io.InputStream;
    import java.util.Properties;
    
    public class Testconfig {
    
        /**
         * 从classpath中读取配置文件component-beans.properties,然后输出到控制台
         */
        public static void main(String[] args) throws Exception {
            InputStream is = Testconfig.class.getClassLoader().getResourceAsStream("component-beans.properties");
            if (is == null) {
                System.err.println("Can not load config resource component-beans.properties in classpath");
            } else {
                Properties prop = new Properties();
                prop.load(is);
                is.close();
                for (String key : prop.stringPropertyNames()) {
                    String value = prop.getProperty(key);
                    if (value != null) {
                        System.out.printf("%s = %s %n", key, value);
                    }
                }
            }
        }
    }
    Testconfig.java

      在Testconfig.java文件中,模拟实现了从classpath中读取配置文件component-beans.properties,接下来创建antoconfig的描述文件auto-config.xml以及component-beans.properties.vm配置文件对应的模板文件(属性名中的点在autoconfig执行的时候会被替换成下划线)。

      通常将配置集中管理,使用中央配置仓库,将可变内容存储在数据库中(文本,DB都可以),然后使用模版引擎(velocity,jsp等)生成最终的配置文件,并且提供http或者其他类型的接口给使用方在应用启动前调用。

    2.Apache加密工具类DigestUtils.md5Hex(String str)

      MD5算法是单向不可逆的,目前只可以通过暴力破解来破解。如果应用中需要采用可逆的加密方法,可以采用DES,3DES,AES,RSA 等。MD5常用于用户的登陆密码加密,每次把用户输入的密码用MD5加密后跟数据库中存储的密码进行比较。可逆加密常用于前端与后端参数交互,后端解密参数,查询数据返回给前端。

      MD5加密原理是散列算法,散列算法也称哈希算法。比如10除以3余数为一,4除以3余数也为一,但余数为一的就不知道这个数是哪个了。所以md5不能解密。

    二、鉴权配置和初始化

      首先,在component-beans.xml.vm文件中配置了bean,并且在容器启动的时候,执行initial方法,将需要权限验证url添加到includeStr集合中。

    <bean id="apiValve" class="com.alibaba.tboss.common.services.login.ApiAuthValve" init-method="initial">
        <!--  需要权限验证url -->
        <property name="includeStr">
            <list>
                <value><![CDATA[^/tboss/web/api/.*.json$]]></value>
            </list>
        </property>
        <!-- 需要验证模块中跳过的url-->
        <property name="excludeStr">
            <list>
            </list>
        </property>
    </bean>
    apiValue定义

      然后在pipeline-web.xml中配置了ApiAuthValue,保证对系统进行API调用请求的时候,执行ApiAuthValue.java中的invoke回调方法。(回调函数的理解:将函数的一部分功能外包给别人。)

    <?xml version="1.0" encoding="UTF-8" ?>
    <beans:beans>
    
        <services:pipeline xmlns="http://www.alibaba.com/schema/services/pipeline/valves">
    
            <!-- 初始化turbine rundata,并在pipelineContext中设置可能会用到的对象(如rundata、utils),以便valve取得。 -->
            <prepareForTurbine />
    
            <!-- 设置日志系统的上下文,支持把当前请求的详情打印在日志中。 -->
            <setLoggingContext />
    
            <!-- 分析URL,取得target。 -->
            <analyzeURL />
    
            <valve class="com.alibaba.dragoon.patrol.webx3.DragoonStatValve" />
    
            <!-- 配置ApiAuthValue API鉴权-->
            <valve class="com.alibaba.tboss.common.services.login.ApiAuthValve" />
            
            <choose>
                <when>
                    <!-- 判断当前的登录类型 -->
                    <pl-conditions:condition class="com.alibaba.tboss.common.services.login.LoginTypeCondition" />
                    <valve class="com.alibaba.tboss.common.services.login.MobileAuthValve" />
                </when>
                
                <otherwise>
                    <valve class="com.alibaba.tboss.common.services.login.TbossAuthValve" />
                </otherwise>
            </choose>
    
            <!-- 检查csrf token,防止csrf攻击和重复提交。假如request和session中的token不匹配,则出错,或显示expired页面。 -->
            <checkCsrfToken />
        </services:pipeline>
    
    </beans:beans>
    pipeline-web.xml

      接着实现了ApiAuthValve.java中的回调方法

    package com.alibaba.tboss.common.services.login;
    
    import static com.alibaba.tboss.common.auth.costants.ApiConstants.API_AUTH_NAME;
    import static com.alibaba.tboss.common.auth.costants.ApiConstants.API_AUTH_SIGNATURE;
    import static com.alibaba.tboss.common.auth.costants.ApiConstants.API_AUTH_TIEMSTAMP;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.regex.Pattern;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import com.alibaba.citrus.extension.rpc.impl.DefaultResultGenerator;
    import com.alibaba.citrus.extension.rpc.validation.DefaultErrorContext;
    import com.alibaba.citrus.extension.rpc.validation.ErrorContext;
    import com.alibaba.citrus.extension.rpc.validation.ErrorItem;
    import com.alibaba.citrus.service.pipeline.PipelineContext;
    import com.alibaba.citrus.service.pipeline.support.AbstractValve;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.tboss.common.auth.bo.ApiUserBo;
    
    public class ApiAuthValve extends AbstractValve {
    
        private static Logger          logger   = LoggerFactory.getLogger(ApiAuthValve.class);
    
        @Resource
        ApiUserBo                      apiUserBo;
        @Autowired
        private HttpServletRequest     request;
        @Autowired
        private HttpServletResponse    response;
    
        // 必须定义成 protected static ,否则注入不进来
        protected static String[]      includeStr;
        protected static List<Pattern> includes = new ArrayList<Pattern>();
    
        protected static String[]      excludeStr;
        protected static List<Pattern> excludes = new ArrayList<Pattern>();
    
        public void initial() {
            if (includeStr != null) {
                for (int i = 0; i < includeStr.length; i++) {
                    includes.add(Pattern.compile(includeStr[i].toLowerCase()));
                }
            }
            if (excludeStr != null) {
                for (int i = 0; i < excludeStr.length; i++) {
                    excludes.add(Pattern.compile(excludeStr[i].toLowerCase()));
                }
            }
        }
    
        @Override
        public void invoke(PipelineContext pipelineContext) throws Exception {
            String uri = request.getRequestURI();
            try {
                // isInControl=true表示请求链接需要鉴权
                if (isInControl(uri)) {
    
                    // 外部调用,必须添加的三个参数:apiName、timestamp、signature
                    String apiName = request.getParameter(API_AUTH_NAME);
                    String timestamp = request.getParameter(API_AUTH_TIEMSTAMP);
                    String signature = request.getParameter(API_AUTH_SIGNATURE);
    
                    // 没有从api_user表匹配权限
                    apiUserBo.checkAuthorization(apiName, timestamp, signature, uri);
                }
            } catch (Exception e) {
                logger.error(uri + "fail - " + e.getMessage(), e);
                // 折衷方案,依赖rpc extention
                ErrorContext errorContext = new DefaultErrorContext();
                errorContext.addError(ErrorItem.create("rpc_500",
                                                       "500",
                                                       String.format("API auth fail : " + e.getMessage(),
                                                                     request.getRequestURI())));
                Object result = new DefaultResultGenerator.GenericWebRPCResult("API auth fail", null, errorContext);
                response.getWriter().print(JSON.toJSONString(result));
                return;
            }
            pipelineContext.invokeNext();
        }
    
        private boolean isInControl(String uri) {
            boolean control = false;
            for (Pattern pattern : includes) {
                if (pattern.matcher(uri.toLowerCase()).matches()) {
                    control = true;
                    break;
                }
            }
            if (control) {
                for (Pattern pattern : excludes) {
                    if (pattern.matcher(uri.toLowerCase()).matches()) {
                        control = false;
                        break;
                    }
                }
            }
            return control;
    
        }
    
        public void setRequest(HttpServletRequest request) {
            this.request = request;
        }
    
        public void setResponse(HttpServletResponse response) {
            this.response = response;
        }
    
        public String[] getIncludeStr() {
            return includeStr;
        }
    
        public void setIncludeStr(String[] includeStr) {
            this.includeStr = includeStr;
        }
    
        public String[] getExcludeStr() {
            return excludeStr;
        }
    
        public void setExcludeStr(String[] excludeStr) {
            this.excludeStr = excludeStr;
        }
    
    }
    ApiAuthValue.java

      最后,在执行相应的业务逻辑。附鉴权方法的实现类如下:

    import org.apache.commons.lang3.time.DateUtils;
    import com.alibaba.common.lang.MathUtil;
    import org.apache.commons.codec.digest.DigestUtils;
    
    public void checkAuthorization(String apiName, String timestamp, String signature, String uri) {
            if(StringUtils.isBlank(apiName) || StringUtils.isBlank(timestamp) || StringUtils.isBlank(signature)) {
                throw new ApiAuthException("apiName, timestamp, signature is missing");
            }
            Date gmtRequest = null;
            try {
                gmtRequest = DateUtils.parseDate(timestamp, API_AUTH_TIMEFORMAT);
            } catch (ParseException e) {
                throw new ApiAuthException("Unsupported timestamp format, suggestting as " + API_AUTH_TIMEFORMAT);
            }
            if (MathUtil.abs(System.currentTimeMillis() - gmtRequest.getTime()) >= 15 * 60 * 1000) {
                throw new ApiAuthException("Request has been expired");
            }
            
            ApiUser user = getApiUserByName(apiName);
            if(user == null) {
                throw new ApiAuthException("Api user is not exist");
            }
    
            if(!isAuthorized(user, uri)) {
                throw new ApiAuthException(String.format("%s has no permission to access uri %s", apiName, uri));
            }
    
            String realCode = decode(user.getCode());
            String format = String.format("%s%s%s", apiName, timestamp, realCode);
            if(!signature.equals(DigestUtils.md5Hex(format))) {
                throw new ApiAuthException("Signature is not match");
            }
        }

    配合鉴权的表单提交测试Demo如下:首先需要md5.js和jquery-1.11.3.min.js两个js仓库。表单提交代码如下:

    <!DOCTYPE html>
    <html lang="zh-cn">
    <head>
        <title>测试Demo</title>
        <script src="jquery-1.11.3.min.js"></script>
        <script src="md5.js"></script>
        <script>
        function DateFormat(pattern, formatSymbols)
        {
            if(pattern == null || pattern == undefined)
            {
                pattern = "yyyy-MM-dd HH:mm:ss SSS";
            }
    
            if(formatSymbols == null || formatSymbols == undefined)
            {
                formatSymbols = "yMdHmsS";
            }
    
            this.pattern = pattern;
            this.formatSymbols = formatSymbols;
        }
    
        DateFormat.prototype.format = function(date)
        {
            var time = getTime(date);
    
            // 标记存入数组
            var cs = this.formatSymbols.split("");
    
            // 格式存入数组
            var fs = this.pattern.split("");
    
            // 构造数组
            var ds = time.split("");
    
            // 标志年月日的结束下标
            var y = 3;
            var M = 6;
            var d = 9;
            var H = 12;
            var m = 15;
            var s = 18;
            var S = 22;
    
            // 逐位替换年月日时分秒和毫秒
            for(var i = fs.length - 1; i > -1; i--)
            {
                switch (fs[i])
                {
                    case cs[0]:
                    {
                        fs[i] = ds[y--];
                        break;
                    }
                    case cs[1]:
                    {
                        fs[i] = ds[M--];
                        break;
                    }
                    case cs[2]:
                    {
                        fs[i] = ds[d--];
                        break;
                    }
                    case cs[3]:
                    {
                        fs[i] = ds[H--];
                        break;
                    }
                    case cs[4]:
                    {
                        fs[i] = ds[m--];
                        break;
                    }
                    case cs[5]:
                    {
                        fs[i] = ds[s--];
                        break;
                    }
                    case cs[6]:
                    {
                        fs[i] = ds[S--];
                        break;
                    }
                }
            }
    
            return fs.join("");
        }
    
        DateFormat.prototype.parse = function(date)
        {
            var y = "";
            var M = "";
            var d = "";
            var H = "";
            var m = "";
            var s = "";
            var S = "";
    
            // 标记存入数组
            var cs = this.formatSymbols.split("");
    
            // 格式存入数组
            var ds = this.pattern.split("");
    
            // date   = "2005-08-22 12:12:12 888";
            // format = "yyyy-MM-dd HH:mm:ss SSS";
            // sign   = "yMdHmsS";
            var size = Math.min(ds.length, date.length);
    
            for(var i=0; i<size; i++)
            {
                switch (ds[i])
                {
                    case cs[0]:
                    {
                        y += date.charAt(i);
                        break;
                    }
                    case cs[1]:
                    {
                        M += date.charAt(i);
                        break;
                    }
                    case cs[2]:
                    {
                        d += date.charAt(i);
                        break;
                    }
                    case cs[3]:
                    {
                        H += date.charAt(i);
                        break;
                    }
                    case cs[4]:
                    {
                        m += date.charAt(i);
                        break;
                    }
                    case cs[5]:
                    {
                        s += date.charAt(i);
                        break;
                    }
                    case cs[6]:
                    {
                        S += date.charAt(i);
                        break;
                    }
                }
            }
    
            if(y.length < 1) y = 0; else y = parseInt(y);
            if(M.length < 1) M = 0; else M = parseInt(M);
            if(d.length < 1) d = 0; else d = parseInt(d);
            if(H.length < 1) H = 0; else H = parseInt(H);
            if(m.length < 1) m = 0; else m = parseInt(m);
            if(s.length < 1) s = 0; else s = parseInt(s);
            if(S.length < 1) S = 0; else S = parseInt(S);
    
            var d = new Date(y, M - 1, d, H, m, s, S);
    
            return d;
        }
    
        // 返回当前时间
        function getTime(date)
        {
            if(date == null)
            {
                date = new Date();
            }
    
            var y = date.getFullYear();
            var M = date.getMonth() + 1;
            var d = date.getDate();
            var h = date.getHours();
            var m = date.getMinutes();
            var s = date.getSeconds();
            var S = date.getTime()%1000;
    
            var html = y + "-";
    
            if(M < 10)
            {
                html += "0";
            }
            html += M + "-";
    
            if(d < 10)
            {
                html += "0";
            }
            html += d + " ";
    
            if(h < 10)
            {
                html += "0";
            }
            html += h + ":";
    
            if(m < 10)
            {
                html += "0";
            }
            html += m + ":";
    
            if(s < 10)
            {
                html += "0";
            }
            html += s;
    
            html += " ";
    
            if(S < 100)
            {
                html += "0"
            }
    
            if(S < 10)
            {
                html += "0";
            }
    
            html += S;
    
            return html;
        }
    
        </script>
    </head>
    <body>
      <br>
      <input id="url"  style="100%" value=""/>     
      <script type="text/javascript">
               function getdate(){
                    var mydate = new Date().format("yyyyMMddhhmm");
                    return mydate;
                } 
                function getsingname(){
                    //apiName 为:DiskTwistApi
                    //code为:+r3UghilkTSfOFLfubg==
                    //var str="test201511251000testcode";
                    //拼接字符串(按照`apiName+timestamp+code`顺序)
                   var mydate = new Date().format("yyyyMMddhhmm");  
                    var str="DiskTwistApi"+mydate+"+r3UghilkTSfOFLfubg==";
                    var signname=hex_md5(str);
                      alert( signname);
                }
                function check(){
                        var timestamp =  new DateFormat("yyyyMMddHHmm").format(new Date());
                        var str="DiskTwistApi"+timestamp+"+r3UghilkTSfOFLfubg==";
                        var signature=hex_md5(str);
                        $("#timestamp").val(timestamp);
                        $("#signature").val(signature);
                         $("#forminfo").submit();
                }
      </script>
        <form id="forminfo" action="http://idcm.alitest.com/tboss/web/api/workorder/diskDegauss/saveSnapshot.xhtml" method="post" enctype="multipart/form-data" >
             apiName:<input id="apiName" name="apiName" value="DiskTwistApi">
             <br>
             code:<input id="code" name="code" value="+r3UghilkTSfOFLfubg==">
             <br>
             timestamp:<input id="timestamp" name="timestamp" value="">
             <br>
             signature<input id="signature" name="signature" value="">
             <br>
             sn:<input id="sn" name="sn" style="500px" value="AUGUST281"/> 
             <br>
             orderId:<input id="orderId" name="orderId" style="500px" value="8136"/> 
             <br>
             img:<input id="img" name="fileInput" type="file"/>
             <br>
             <button type="button" onclick="check()">保存</button>
        </form>
    </body>
    </html>
    View Code
  • 相关阅读:
    通过Internet使用VSS2005
    基于角色的权限设计(一)
    WFF架构及技术
    WFF概述
    企业库:Cache
    权限设计(二)
    应用系统中的编码和编码规则
    希望更多的人也可以来应用wordpress程序
    说说我的一点小感受了
    思维决定命运
  • 原文地址:https://www.cnblogs.com/RunForLove/p/5481950.html
Copyright © 2011-2022 走看看