簡介
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;
}
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;
}
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;
}
}
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);
}
// 請求授權碼時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;
}
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;
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);
}
}
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 {
}
}
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);
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);
}
}
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類,該類中定義了不同場景下通用的錯誤標識,在程序開發時可以直接使用它提供的常量。