最近做oauth2預研,查了相當多的資料
因為現有的項目是使用java 語言來實現的,且不打算直接去實現這一整套的標准。因此先去官網(https://oauth.net/code/)看了下現有的java版實現。其實還有其他的實現沒有收錄進去。

比較之后發現資料相對較多的是Apache oltu以及 spring sercurity oauth.因為都是開源的,就去把源代碼都clone下來了。個人認為Oltu相對來說更輕量,也更簡單,是對oauth2的簡單實現。很多后續校驗的事情都需要我們自己去做,但這也是它靈活的一面。所以一開始,是決定使用Apache 的oltu。參考了楊開濤的博客(OAuth2集成——《跟我學Shiro》)使用oltu實現了一個簡單的認證服務器。
一開始是打算寫三個服務的oauthservice,oauthclient,oauthresource,后來為了省時間直接把客戶端也集成到服務里面了,交互界面也只是幾個簡單的輸入框。
認證服務主要有幾個接口
/login:登錄接口
/Oauth/authorize:獲取授權碼的接口
/Oauth/getCode:這個其實就是授權碼接口,只是例子中后端沒有存儲登錄狀態,做了個中轉
/Oauth/ accesstoken:獲取訪問令牌
下面來說說每個接口具體做了什么事
- 獲取授權碼
- 將請求轉換成oltu的認證請求OauthAuthzRequest
- 從 OauthAuthzRequest 讀取客戶端信息(clientId,redirectUrl,response_type)
- 校驗客戶端信息
- 校驗成功后生成訪問令牌
- 存儲訪問令牌
- 使用oltu的OAuthASResponse構建oauth響應
- 在響應中設置好授權碼,state等信息
- 重定向到客戶端的redirectUrl
public Object getCode(HttpServletRequest request) { try { OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request); OAuthResponse oAuthResponse; String clientId=oauthRequest.getClientId(); //校驗client信息 if(!oauthClientService.checkClient(clientId)) { return ControllerHelper .getResponseEntity(HttpServletResponse.SC_BAD_REQUEST , OAuthError.TokenResponse.INVALID_CLIENT , ErrorConstants.ERROR_CLIENT_MSG); } //獲取登陸信息 //已經登錄校驗內部token信息,沒有登陸,校驗登陸信息 String token=request.getParameter("token"); if(StringUtils.isEmpty(token))//token不存在及用戶沒有登陸,非法訪問 { return ControllerHelper .getResponseEntity(HttpServletResponse.SC_BAD_REQUEST , OAuthError.CodeResponse.ACCESS_DENIED , ErrorConstants.ERROR_CLIENT_LOGIN); } else {//校驗token 服務器端對應的token是否存在,及獲取用戶信息等 // checktoken() } //生成授權碼 String authcode=null; String responseType=oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE); if(responseType.equals(ResponseType.CODE.toString())) { OAuthIssuerImpl oAuthIssuerImpl=new OAuthIssuerImpl(new MD5Generator()); authcode=oAuthIssuerImpl.authorizationCode(); //保存授權碼 oauthClientService.saveCode(clientId, authcode); } //Oauth 響應 OAuthASResponse.OAuthAuthorizationResponseBuilder builder= OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND); //設置授權碼 builder.setCode(authcode); String redirectURI=oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI); oAuthResponse=builder.location(redirectURI).buildQueryMessage(); //根據OAuthResponse返回ResponseEntity響應 HttpHeaders headers = new HttpHeaders(); headers.setLocation(new URI(oAuthResponse.getLocationUri())); return new ResponseEntity(headers, HttpStatus.valueOf(oAuthResponse.getResponseStatus())); } catch (Exception e) { logger.error(e.getCause().getMessage(),e); } return null; }
- 獲取訪問令牌
- 將請求轉換成oltu的token 獲取請求OAuthTokenRequest
- 從OauthTokenRequest讀取客戶端信息
- 校驗客戶端信息
- 生成訪問令牌token
- 存儲訪問令牌
- 構建oauth2響應oAuthResponse
- 返回到客戶端
public Object getToken(HttpServletRequest request) throws OAuthSystemException {
try {
OAuthTokenRequest oAuthTokenRequest= new OAuthTokenRequest(request);
String clientId=oAuthTokenRequest.getClientId();
String clientKey= oAuthTokenRequest.getClientSecret();
if(!oauthClientService.checkClient(clientId,clientKey))
{
return ControllerHelper.getResponseEntity(HttpServletResponse.SC_BAD_REQUEST, OAuthError.TokenResponse.INVALID_CLIENT, ErrorConstants.ERROR_CLIENT_MSG);
}
OAuthResponse oAuthResponse;
String authcode= oAuthTokenRequest.getCode();
String grantType= oAuthTokenRequest.getGrantType();
if(GrantType.AUTHORIZATION_CODE.toString().equals(grantType) && authcode.equals(oauthClientService.getCode(clientId)))
{
//生成token
OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
final String accessToken = oauthIssuerImpl.accessToken();
oauthClientService.saveAccessToken(accessToken, "");
//生成OAuth響應
oAuthResponse = OAuthASResponse
.tokenResponse(HttpServletResponse.SC_OK)
.setAccessToken(accessToken)
.setExpiresIn(String.valueOf( 3600L))
.buildJSONMessage();
//根據OAuthResponse生成ResponseEntity
return new ResponseEntity(oAuthResponse.getBody(), HttpStatus.valueOf(oAuthResponse.getResponseStatus()));
}
else{
return ControllerHelper.getResponseEntity(HttpServletResponse.SC_BAD_REQUEST, OAuthError.TokenResponse.INVALID_GRANT, ErrorConstants.ERROR_AUTH_CODE);
}
} catch (Exception e) {
logger.error(e.getMessage(),e);
return ControllerHelper.getResponseEntity(HttpServletResponse.SC_BAD_REQUEST, ErrorConstants.ERROR_UNKNOW, e.getCause().getMessage());
}
}
客戶端主要有一下兩個接口
/requestAuth: 重定向到拼接好的授權請求url
@RequestMapping("/requestAuth")
public ModelAndView requestAuth(@ModelAttribute("oauthParams") OauthParam oauthParams) {
try {
OAuthClientRequest request = OAuthClientRequest
.authorizationLocation(oauthParams.getAuthzEndpoint())
.setClientId(oauthParams.getClientId())
.setRedirectURI(oauthParams.getRedirectUri())
.setResponseType(ResponseType.CODE.toString())
.setScope(oauthParams.getScope())
.setState(oauthParams.getState())
.buildQueryMessage();
return new ModelAndView(new RedirectView(request.getLocationUri()));
} catch (Exception e) {
logger.error(e.getMessage(),e);
return null;
}
}
/redirect:獲取授權碼后,處理授權碼的重定向地址。
OAuthAuthzResponse oar = null; oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request); String code = oar.getCode();//獲取授權碼 OAuthClientRequest request2 =OAuthClientRequest .tokenLocation(oauthParams.getTokenEndpoint()) .setClientId(oauthParams.getClientId()) .setClientSecret(oauthParams.getClientSecret()) .setRedirectURI(oauthParams.getRedirectUri()) .setCode(code) .setGrantType(GrantType.AUTHORIZATION_CODE) .buildBodyMessage(); OAuthClient client=new OAuthClient(new URLConnectionClient()); Class<? extends OAuthAccessTokenResponse> cl = OAuthJSONAccessTokenResponse.class; //請求token OAuthAccessTokenResponse oauthResponse=client.accessToken(request2,cl); String token=oauthResponse.getAccessToken();//獲取token
源碼地址:https://github.com/huanglin101/springboot_oltu_oauth2.git
