你的位置:首页 > Java教程

[Java教程]ApiAuthValue鉴权机制总结


一、背景介绍

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

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

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

 

       <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.</includeDescriptorPattern>            <includeDescriptorPattern>autoconf/conf/auto-config.</includeDescriptorPattern>            <includeDescriptorPattern>autoconf/conf/customize-autoconf/auto-config.</includeDescriptorPattern>          </includeDescriptorPatterns>        </configuration>        <executions>          <execution>            <phase>package</phase>            <goals>              <goal>autoconfig</goal>            </goals>          </execution>        </executions>      </plugin>

pom.

 

  Convention over Configuration(CoC),约定优于配置,上述配置文件中,充分体现了CoC原则。默认地插件会去扫描autoconf/auto-config.

 

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.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.

 

<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.

 

<??><beans:beans>  <services:pipeline ="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.

 

  接着实现了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");    }  }