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