簡介
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類,該類中定義了不一樣場景下通用的錯誤標識,在程序開發時能夠直接使用它提供的常量。