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提供了两个通用的实现类:MD5Generator和UUIDValueGenerator.

request代码分析

request包中包含5个类,其中OAuthRequest是其他四个类的父类,提供最基础最通用的逻辑和工具方法,OAuthAuthzRequest类用于授权码请求,而OAuthTokenRequest和OAuthUnauthenticatedTokenRequest用于访问令牌和刷新访问令牌请求。

请求封装的主要作用是根据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,因此不允许获取该类的实例,实际上也没必要直接操作该类的实例,因为实际我们需要使用的他的两个静态内部类:OAuthAuthorizationResponseBuilder和OAuthTokenResponseBuilder,然后通过他们提供的方法来构造和生成最终的响应数据。

实际上这两个Builder类只是根据不同的业务场景提供一些特定的方法,比如OAuthTokenResponseBuilder用于构造访问令牌响应数据,因此他提供了如setAccessToken和setRefreshToken之类的方法。最终实际的实现实在他们的父类OAuthResponseBuilder类中(该类是OAuthASResponse的父类OAuthResponse类的静态内部类)。

ResponseBuilder代码分析

用于构造响应数据(OAuthResponse)的Builder类被作为OAuthResponse类的静态内部类的形式存在:

根据类结构图看以看到有两个builder:OAuthResponseBuilder和OAuthErrorResponseBuilder,其中后者又是前者的子类。我们先看一个实际使用中的场景:

// 授权码
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类,该类中定义了不同场景下通用的错误标识,在程序开发时可以直接使用它提供的常量。

时间: 2024-07-30 13:38:30

Apache olth学习笔记的相关文章

Apache Shiro学习笔记(六)FilterChain

鲁春利的工作笔记,好记性不如烂笔头 Apache Shiro学习笔记(七)IniWebEnvironment

Apache Shiro学习笔记(九)Spring集成

鲁春利的工作笔记,好记性不如烂笔头 Integrating Apache Shiro into Spring-based Applications Shiro 的组件都是JavaBean/POJO 式的组件,所以非常容易使用Spring进行组件管理,可以非常方便的从ini配置迁移到Spring进行管理,且支持JavaSE应用及Web 应用的集成. Web Applications 1.web.xml <!-- The filter-name matches name of a 'shiroFil

Apache Shiro学习笔记(五)Web集成扩展

鲁春利的工作笔记,好记性不如烂笔头 http://shiro.apache.org/web-features.html 基于Basic的拦截器身份验证 shiro-authc-basic.ini # 基于Basic的拦截器身份验证 [main] # 默认是/login.jsp authc.loginUrl=/login authcBasic.applicationName=请登录 [users] # 用户名=密码,角色 lucl=123456,admin wang=123456 [roles]

Apache Shiro学习笔记(五)Web集成使用JdbcRealm

鲁春利的工作笔记,好记性不如烂笔头 http://shiro.apache.org/web-features.html 前面的示例都是把用户名或密码以及权限信息放在ini文件中,但实际的Web项目开发过程中,实际上一般是user<--->role.role<-->permission进行关联关系的配置,每次登录时加载其拥有的权限或者是每次访问时再判断其权限. jdbc-shiro.ini [main] #默认是/login.jsp authc.loginUrl=/login rol

Apache Shiro学习笔记

鲁春利的工作笔记,好记性不如烂笔头 官网地址:http://shiro.apache.org/ 主要功能包括: Authentication:身份认证/登录,验证用户是不是拥有相应的身份:Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限:即判断用户是否能做事情:常见的如:验证某个用户是否拥有某个角色. Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中:会话可以是普通JavaSE环境的,也可以是如Web环境

Apache Shiro 学习笔记

一.为什么要学习Shiro Shiro是简单易用的权限控制框架.应用范围广,受到许多开发人员的欢迎.他可以运用于javaSE项目还可以运用于javaEE项目.在项目中Shiro可以帮助我们完成:认证.授权.加密.会话管理.与Web集成.缓存等. 二.与spring security的笔记 Shiro简单易学,尽管功能没有spring security强大,但其功能已足够日常开发使用.spring官网使用的便是Shiro.可见其的方便.Shiro还可与spring整合.更方便了开发者. 三.Shi

Apache Shiro学习笔记(三)用户授权自定义Permission

鲁春利的工作笔记,好记性不如烂笔头 Shiro配置文件(shiro-customize-permission.ini) [main] myRealmA=com.invicme.apps.shiro.permission.MyRealmOne myPermissionResolver=com.invicme.apps.shiro.permission.MyPermissionResolver securityManager.authorizer.permissionResolver = $myPe

Apache Shiro学习笔记(三)用户授权

鲁春利的工作笔记,好记性不如烂笔头 Shiro默认提供的Realm 认证(Authentication)用来证明用户身份是合法的:而授权(Authorize)用来控制合法用户能够做什么(能访问哪些资源). 实际系统应用中一般继承AuthorizingRealm(授权)即可:其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现). 在授权中需了解的几个关键对象:主体(Subject).资源(Resource).权限(Permissio

Apache Shiro学习笔记(二)身份验证

鲁春利的工作笔记,好记性不如烂笔头 身份验证,即在应用中谁能证明他就是他本人,应用系统中一般通过用户名/密码来证明.在 shiro 中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份:    principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号.    credentials:证明/凭