zoukankan      html  css  js  c++  java
  • 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类,该类中定义了不同场景下通用的错误标识,在程序开发时可以直接使用它提供的常量。
     
     
  • 相关阅读:
    性能测试实战
    毕业以后读书报告(不定时更新)
    sqlalchemy.orm.exc.flusherror:错误解决
    Anaconda安装第三方模块
    关于BeanShell报错提示Error invoking bsh method
    jmeter函数和变量
    jmeter插件安装及使用
    doc
    doc
    doc
  • 原文地址:https://www.cnblogs.com/jdluojing/p/4201729.html
Copyright © 2011-2022 走看看