Apache olth学习笔记

简介

Apache olth是oauth2.0协议的java实现,可简化oauth应用的开发,提供了授权服务器,资源服务器以及客户端的实现。我们这里主要使用oauth2.0协议做授权服务,因此主要学习授权服务器的实现。
 

代码结构

技术分享
 
上图为apache olth的授权服务器的代码组织结构,从包的组织可以看到分为四个模块:
  • issuser        主要提供用于生成授权码(authorization code)、访问令牌(access token)和刷新令牌(refresh token)的通用实现
  • request       用于封装授权码请求和令牌请求的通用逻辑,并提供响应的校验手段
  • response    用于封装授权流程中通用的响应逻辑,提供生成不同响应结果的方法
  • validator    为request提供校验服务
 

issuser代码分析

技术分享
 
一共包含2个接口和3个类,其中OAuthIssuser接口定义issuer的通用功能:
public interface OAuthIssuer {
    public String accessToken() throws OAuthSystemException;

    public String authorizationCode() throws OAuthSystemException;

    public String refreshToken() throws OAuthSystemException;
}
OAuthIssuer的实现中使用ValueGenerator来生成实际的值:
public interface ValueGenerator {
    public String generateValue() throws OAuthSystemException;

    public String generateValue(String param) throws OAuthSystemException;
}
 
ValueGenerator提供了两个通用的实现类:MD5GeneratorUUIDValueGenerator.
 

request代码分析

技术分享
 
request包中包含5个类,其中OAuthRequest是其他四个类的父类,提供最基础最通用的逻辑和工具方法,OAuthAuthzRequest类用于授权码请求,而OAuthTokenRequestOAuthUnauthenticatedTokenRequest用于访问令牌和刷新访问令牌请求。
 
请求封装的主要作用是根据oauth2.0规范中规定的各个步骤中相关参数是否可选等规则,来对实际的请求进行校验。校验的逻辑又有validator包中的各种validator实现来完成,request包中只需要根据不同的业务需求组合不同的validator即可完成对应的校验工作。
 
首先看父类OAuthRequest提供的方法:
技术分享
 
除了提供从实际请求中获取oauth2.0规定的参数的方法外,还有两个protected方法:validate和initValidator,其中initValidator方法由子类负责实现。也就是说子类负责提供validator,validator方法中会调用提供的validator:
protected void validate() throws OAuthSystemException, OAuthProblemException {
    try {
        // 拿到validator
        validator = initValidator();
        validator.validateMethod(request);
        validator.validateContentType(request);
        // 校验必填的参数是否满足
        validator.validateRequiredParameters(request);
        // 校验凭证认证
        validator.validateClientAuthenticationCredentials(request);
    } catch (OAuthProblemException e) {
        try {
            String redirectUri = request.getParameter(OAuth.OAUTH_REDIRECT_URI);
            if (!OAuthUtils.isEmpty(redirectUri)) {
                e.setRedirectUri(redirectUri);
            }
        } catch (Exception ex) {
            if (log.isDebugEnabled()) {
                log.debug("Cannot read redirect_url from the request: {}", new String[] {ex.getMessage()});
            }
        }

        throw e;
    }

}
 
接着我们看子类OAuthAuthzRequest的initValidator方法:
protected OAuthValidator<HttpServletRequest> initValidator() throws OAuthProblemException, OAuthSystemException {
    // 请求授权码时response_type参数可以是code或token,详情看oauth2.0规范
    validators.put(ResponseType.CODE.toString(), CodeValidator.class);
    validators.put(ResponseType.TOKEN.toString(), TokenValidator.class);
   
    // 从实际请求中获取response_type参数,跟根据其值返回对应的validator实例
    final String requestTypeValue = getParam(OAuth.OAUTH_RESPONSE_TYPE);
    if (OAuthUtils.isEmpty(requestTypeValue)) {
        throw OAuthUtils.handleOAuthProblemException("Missing response_type parameter value");
    }
    final Class<? extends OAuthValidator<HttpServletRequest>> clazz = validators.get(requestTypeValue);
    if (clazz == null) {
        throw OAuthUtils.handleOAuthProblemException("Invalid response_type parameter value");
    }

    return OAuthUtils.instantiateClass(clazz);
}
 
其他几个实现类逻辑基本相同,就不在做分析了。
 

validator代码分析

技术分享
 
这里展示的类只是validator体系中和授权服务器相关的部分,其接口定义部分在org.apache.olth.oauth2.common.validators包中,所有validator都实现了OAuthValidator接口:
public interface OAuthValidator<T extends HttpServletRequest> {

    public void validateMethod(T request) throws OAuthProblemException;

    public void validateContentType(T request) throws OAuthProblemException;

    public void validateRequiredParameters(T request) throws OAuthProblemException;

    public void validateOptionalParameters(T request) throws OAuthProblemException;

    public void validateNotAllowedParameters(T request) throws OAuthProblemException;

    public void validateClientAuthenticationCredentials(T request) throws OAuthProblemException;

    public void performAllValidations(T request) throws OAuthProblemException;

}
并且系统提供了实现了所有方法和功能逻辑的AbstractValidator类:
// 必填字段列表
protected List<String> requiredParams = new ArrayList<String>();
// 可选字段列表
protected Map<String, String[]> optionalParams = new HashMap<String, String[]>();
// 不允许出现字段列表
protected List<String> notAllowedParams = new ArrayList<String>();
// 是否必须进行权限认证
protected boolean enforceClientAuthentication;
 
该类中包含四个成员变量,分别用于保存一些信息,在其他各个方法中使用这些成员变量来进行处理,例如validateRequiredParameters方法:
public void validateRequiredParameters(T request) throws OAuthProblemException {
    final Set<String> missingParameters = new HashSet<String>();
    for (String requiredParam : requiredParams) {
        String val = request.getParameter(requiredParam);
        if (OAuthUtils.isEmpty(val)) {
            missingParameters.add(requiredParam);
        }
    }
    if (!missingParameters.isEmpty()) {
        throw OAuthUtils.handleMissingParameters(missingParameters);
    }
}
只需要遍历对应成员变量中的数据,然后进行检测即可。那么这些成员变量中的数据从什么地方来呢?答案就是子类!例如查看在授权码请求中使用到的CodeValidator:
public class CodeValidator extends AbstractValidator<HttpServletRequest> {

    public CodeValidator() {
        requiredParams.add(OAuth.OAUTH_RESPONSE_TYPE);
        requiredParams.add(OAuth.OAUTH_CLIENT_ID);
    }

    @Override
    public void validateMethod(HttpServletRequest request) throws OAuthProblemException {
        String method = request.getMethod();
        if (!OAuth.HttpMethod.GET.equals(method) && !OAuth.HttpMethod.POST.equals(method)) {
            throw OAuthProblemException.error(OAuthError.CodeResponse.INVALID_REQUEST)
                .description("Method not correct.");
        }
    }

    @Override
    public void validateContentType(HttpServletRequest request) throws OAuthProblemException {
    }
}
 
通过在构造方法中操作父类的成员变量和覆盖AbstractValidator中的方法即可。其他validator实现方式类似,就不在分析了。
 

response代码分析

response包中只有一个类OAuthASReponse,该类提供了组装不同请求的基本方法,具体要返回哪些参数可在程序中自由指定。
技术分享
构造方法是protected,因此不允许获取该类的实例,实际上也没必要直接操作该类的实例,因为实际我们需要使用的他的两个静态内部类:OAuthAuthorizationResponseBuilderOAuthTokenResponseBuilder,然后通过他们提供的方法来构造和生成最终的响应数据。
 
实际上这两个Builder类只是根据不同的业务场景提供一些特定的方法,比如OAuthTokenResponseBuilder用于构造访问令牌响应数据,因此他提供了如setAccessToken和setRefreshToken之类的方法。最终实际的实现实在他们的父类OAuthResponseBuilder类中(该类是OAuthASResponse的父类OAuthResponse类的静态内部类)。
 

ResponseBuilder代码分析

用于构造响应数据(OAuthResponse)的Builder类被作为OAuthResponse类的静态内部类的形式存在:
技术分享
 
根据类结构图看以看到有两个builder:OAuthResponseBuilderOAuthErrorResponseBuilder,其中后者又是前者的子类。我们先看一个实际使用中的场景:
// 授权码
OAuthResponse oAuthResponse= OAuthASResponse.authorizationResponse(request, 200)
        .location(jdUrl)
        .setCode(oauthCode)
        .setScope(state)
        .buildQueryMessage();
String url=oAuthResponse.getLocationUri();
response.sendRedirect(url);

// 访问令牌
OAuthResponse authASResponse = OAuthASResponse.tokenResponse(200)
        .setAccessToken(access_token)
        .setExpiresIn("7200")
        .setRefreshToken(refreshToken)
        .setTokenType(TokenType.BEARER.toString())
        .setParam("re_expires_in", "14400")
        .buildJSONMessage();
String  json=  authASResponse.getBody();

// 错误响应
OAuthResponse authASResponse = OAuthASResponse.errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
        .setError(OAuthError.ResourceResponse.INVALID_TOKEN)
        .setErrorDescription("invald expired")
        .buildJSONMessage();
return new ResponseEntity<String>(authASResponse.getBody(), headers, HttpStatus.UNAUTHORIZED);
 
可以看出我们调用的各种set方法实际上就是在设置响应参数,当我们调用buildJSONMessage之类的方法时会生成一个OAuthResponse对象,其中已经包含了响应的数据,我们只需要根据返回方式调用OAuthResponse对象的getBody或getHeaders之类的方法即可获取到构造好的响应数据。
public static class OAuthResponseBuilder {

    protected OAuthParametersApplier applier;
    protected Map<String, Object> parameters = new HashMap<String, Object>();
    protected int responseCode;
    protected String location;

    public OAuthResponseBuilder(int responseCode) {
        this.responseCode = responseCode;
    }

    public OAuthResponseBuilder location(String location) {
        this.location = location;
        return this;
    }

    public OAuthResponseBuilder setScope(String value) {
        this.parameters.put(OAuth.OAUTH_SCOPE, value);
        return this;
    }

    public OAuthResponseBuilder setParam(String key, String value) {
        this.parameters.put(key, value);
        return this;
    }

    public OAuthResponse buildQueryMessage() throws OAuthSystemException {
        OAuthResponse msg = new OAuthResponse(location, responseCode);
        this.applier = new QueryParameterApplier();

        if (parameters.containsKey(OAuth.OAUTH_ACCESS_TOKEN)) {
            this.applier = new FragmentParametersApplier();
        }else{
            this.applier = new QueryParameterApplier();
        }
       
        return (OAuthResponse)applier.applyOAuthParameters(msg, parameters);
    }

    public OAuthResponse buildBodyMessage() throws OAuthSystemException {
        OAuthResponse msg = new OAuthResponse(location, responseCode);
        this.applier = new BodyURLEncodedParametersApplier();
        return (OAuthResponse)applier.applyOAuthParameters(msg, parameters);
    }

    public OAuthResponse buildJSONMessage() throws OAuthSystemException {
        OAuthResponse msg = new OAuthResponse(location, responseCode);
        this.applier = new JSONBodyParametersApplier();
        return (OAuthResponse)applier.applyOAuthParameters(msg, parameters);
    }

    public OAuthResponse buildHeaderMessage() throws OAuthSystemException {
        OAuthResponse msg = new OAuthResponse(location, responseCode);
        this.applier = new WWWAuthHeaderParametersApplier();
        return (OAuthResponse)applier.applyOAuthParameters(msg, parameters);
    }
}
至于OAuthParameterApplier的实现,这里就不做深入了解了,其作用就是生成不同格式的数据并设置到OAuthResponse对象的成员变量中。
 
另外对应错误响应中的error字段,Apache olth中还提供了一个OAuthError类,该类中定义了不同场景下通用的错误标识,在程序开发时可以直接使用它提供的常量。
 
 

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。