oauth原理簡述
oauth本身不是技術,而是一項資源授權協議,重點是協議!Apache基金會提供了針對Java的oauth封裝。我們做Java web專案想要實現oauth協議進行資源授權訪問,直接使用該封裝就可以。
oauth2.0 的協議實現原理,所有的技術層面的開發都是圍繞這張圖。
整個開發流程簡述一下:
1、 在客戶端web專案中構造一個oauth的客戶端請求物件(OAuthClientRequest),在此物件中攜帶客戶端資訊(clientId、accessTokenUrl、response_type、redirectUrl),將此資訊放入http請求中,重定向到服務端。此步驟對應上圖1
2、 在服務端web專案中接受第一步傳過來的request,從中獲取客戶端資訊,可以自行驗證資訊的可靠性。同時構造一個oauth的code授權許可物件(OAuthAuthorizationResponseBuilder),並在其中設定授權碼code,將此物件傳回客戶端。此步驟對應上圖2
3、 在在客戶端web專案中接受第二步的請求request,從中獲得code。同時構造一個oauth的客戶端請求物件(OAuthClientRequest),此次在此物件中不僅要攜帶客戶端資訊(clientId、accessTokenUrl、clientSecret、GrantType、redirectUrl),還要攜帶接受到的code。再構造一個客戶端請求工具物件(oAuthClient),這個工具封裝了httpclient,用此物件將這些資訊以post(一定要設定成post)的方式請求到服務端,目的是為了讓服務端返回資源訪問令牌。此步驟對應上圖3。(另外oAuthClient請求服務端以後,會自行接受服務端的響應資訊。
4、 在服務端web專案中接受第三步傳過來的request,從中獲取客戶端資訊和code,並自行驗證。再按照自己專案的要求生成訪問令牌(accesstoken),同時構造一個oauth響應物件(OAuthASResponse),攜帶生成的訪問指令(accesstoken),返回給第三步中客戶端的oAuthClient。oAuthClient接受響應之後獲取accesstoken,此步驟對應上圖4
5、 此時客戶端web專案中已經有了從服務端返回過來的accesstoken,那麼在客戶端構造一個服務端資源請求物件(OAuthBearerClientRequest),在此物件中設定服務端資源請求URI,並攜帶上accesstoken。再構造一個客戶端請求工具物件(oAuthClient),用此物件去服務端靠accesstoken換取資源。此步驟對應上圖5
6、 在服務端web專案中接受第五步傳過來的request,從中獲取accesstoken並自行驗證。之後就可以將客戶端請求的資源返回給客戶端了。
程式碼:
客戶端:
一、pom依賴:
<dependency> <groupId>org.apache.oltu.oauth2</groupId> <artifactId>org.apache.oltu.oauth2.client</artifactId> <version>0.31</version> </dependency>
|
二、controller方法:
2.1 向服務端請求授權碼code的controller方法:
@RequestMapping("/server") @Controller public class ServerController{
String clientId = null; String clientSecret = null; String accessTokenUrl = null; String userInfoUrl = null; String redirectUrl = null; String response_type = null; String code= null;
//提交申請code的請求 @RequestMapping("/requestServerCode") public String requestServerFirst(HttpServletRequestrequest, HttpServletResponseresponse, RedirectAttributesattr) throwsOAuthProblemException{ clientId = "clientId"; clientSecret = "clientSecret"; accessTokenUrl = "responseCode"; redirectUrl = "http://localhost:8081/oauthclient01/server/callbackCode"; response_type = "code";
OAuthClient oAuthClient =new OAuthClient(new URLConnectionClient()); String requestUrl = null; try { //構建oauthd的請求。設定請求服務地址(accessTokenUrl)、clientId、response_type、redirectUrl OAuthClientRequest accessTokenRequest = OAuthClientRequest .authorizationLocation(accessTokenUrl) .setResponseType(response_type) .setClientId(clientId) .setRedirectURI(redirectUrl) .buildQueryMessage(); requestUrl = accessTokenRequest.getLocationUri(); System.out.println(requestUrl); } catch (OAuthSystemExceptione) { e.printStackTrace(); } return "redirect:http://localhost:8082/oauthserver/"+requestUrl ; } |
此段程式碼對應開發步驟1.其中accessTokenUrl是服務端返回code的controller方法對映地址。redirectUrl是告訴服務端,code要傳回客戶端的一個controller方法,該方法的對映地址就是redirectUrl。
2.2 向服務端請求資源訪問令牌access token的controller方法:
//接受客戶端返回的code,提交申請access token的請求 @RequestMapping("/callbackCode") public Object toLogin(HttpServletRequestrequest)throws OAuthProblemException{ System.out.println("-----------客戶端/callbackCode--------------------------------------------------------------------------------"); clientId = "clientId"; clientSecret = "clientSecret"; accessTokenUrl="http://localhost:8082/oauthserver/responseAccessToken"; userInfoUrl = "userInfoUrl"; redirectUrl = "http://localhost:8081/oauthclient01/server/accessToken"; HttpServletRequest httpRequest = (HttpServletRequest)request; code = httpRequest.getParameter("code"); System.out.println(code); OAuthClient oAuthClient =new OAuthClient(new URLConnectionClient()); try { OAuthClientRequest accessTokenRequest = OAuthClientRequest .tokenLocation(accessTokenUrl) .setGrantType(GrantType.AUTHORIZATION_CODE) .setClientId(clientId) .setClientSecret(clientSecret) .setCode(code) .setRedirectURI(redirectUrl) .buildQueryMessage(); //去服務端請求access token,並返回響應 OAuthAccessTokenResponse oAuthResponse =oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST); //獲取服務端返回過來的access token String accessToken = oAuthResponse.getAccessToken(); //檢視access token是否過期 Long expiresIn =oAuthResponse.getExpiresIn(); System.out.println("客戶端/callbackCode方法的token:::"+accessToken); System.out.println("-----------客戶端/callbackCode--------------------------------------------------------------------------------"); return"redirect:http://localhost:8081/oauthclient01/server/accessToken?accessToken="+accessToken; } catch (OAuthSystemExceptione) { e.printStackTrace(); } return null; } |
此方法對應開發步驟3的全部和步驟4的一半,也就是還包括接受服務端返回的access token。最後的redirect地址不是服務端的地址,只是將此token傳進客戶端的另一個方法,該方法就是最後的資源請求方法。
2.3 利用服務端給的token去請求服務端的資源的controller方法。這裡說的資源就是服務端資料庫中的user表的uname值的拼接欄位。
//接受服務端傳回來的access token,由此token去請求服務端的資源(使用者資訊等) @RequestMapping("/accessToken") public ModelAndView accessToken(StringaccessToken) { System.out.println("---------客戶端/accessToken----------------------------------------------------------------------------------"); userInfoUrl = "http://localhost:8082/oauthserver/userInfo"; System.out.println("accessToken"); OAuthClient oAuthClient =new OAuthClient(new URLConnectionClient());
try {
OAuthClientRequest userInfoRequest =new OAuthBearerClientRequest(userInfoUrl) .setAccessToken(accessToken).buildQueryMessage(); OAuthResourceResponse resourceResponse =oAuthClient.resource(userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class); String username = resourceResponse.getBody(); System.out.println(username); ModelAndView modelAndView =new ModelAndView("usernamePage"); modelAndView.addObject("username",username); System.out.println("---------客戶端/accessToken----------------------------------------------------------------------------------"); returnmodelAndView; } catch (OAuthSystemExceptione) { e.printStackTrace(); } catch (OAuthProblemExceptione) { e.printStackTrace(); } System.out.println("---------客戶端/accessToken----------------------------------------------------------------------------------"); return null; }
|
此方法對應開發步驟5的全部和步驟6的一半,也就是還包括接受服務端返回的資源資訊。獲取了資源資訊之後,其餘的開發就和平時的springmvc一毛一樣了。
以上三個方法我全部封裝在同一個ServerController類中。
服務端
三 pom依賴
1. <dependency> 2. <groupId>org.apache.oltu.oauth2</groupId> 3. <artifactId>org.apache.oltu.oauth2.authzserver</artifactId> 4. <version>0.31</version> 5. </dependency> 6. <dependency> 7. <groupId>org.apache.oltu.oauth2</groupId> 8. <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId> 9. <version>0.31</version> 10. </dependency>
|
四 controller方法
4.1 向客戶端返回授權碼code的controller方法
@Controller public class AuthorizeController{
@Autowired private UserServiceuserService;
//向客戶端返回授權許可碼 code @RequestMapping("/responseCode") public Object toShowUser(Modelmodel, HttpServletRequestrequest){ System.out.println("----------服務端/responseCode--------------------------------------------------------------");
try { //構建OAuth授權請求 OAuthAuthzRequest oauthRequest =new OAuthAuthzRequest(request); /*oauthRequest.getClientId(); oauthRequest.getResponseType(); oauthRequest.getRedirectURI(); System.out.println(oauthRequest.getClientId()); System.out.println(oauthRequest.getResponseType()); System.out.println(oauthRequest.getRedirectURI());*/
if(oauthRequest.getClientId()!=null&&oauthRequest.getClientId()!="") { //設定授權碼 String authorizationCode ="authorizationCode"; //利用oauth授權請求設定responseType,目前僅支援CODE,另外還有TOKEN String responseType =oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE); //進行OAuth響應構建 OAuthASResponse.OAuthAuthorizationResponseBuilderbuilder = OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND); //設定授權碼 builder.setCode(authorizationCode); //得到到客戶端重定向地址 String redirectURI =oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI); //構建響應 final OAuthResponseresponse =builder.location(redirectURI).buildQueryMessage(); System.out.println("服務端/responseCode內,返回的回撥路徑:"+response.getLocationUri()); System.out.println("----------服務端/responseCode--------------------------------------------------------------"); String responceUri =response.getLocationUri();
//根據OAuthResponse返回ResponseEntity響應 HttpHeaders headers =new HttpHeaders(); try { headers.setLocation(new URI(response.getLocationUri())); } catch (URISyntaxExceptione) { // TODO Auto-generated catch block e.printStackTrace(); } return"redirect:"+responceUri; }
} catch (OAuthSystemExceptione) { e.printStackTrace(); } catch (OAuthProblemExceptione) { e.printStackTrace(); } System.out.println("----------服務端/responseCode--------------------------------------------------------------"); return null;
} } |
此段程式碼對應開發步驟2
4.2 向客戶端返回資源訪問令牌accesstoken的controller方法
@Controller public class AccessTokenController {
//獲取客戶端的code碼,向客戶端返回access token @RequestMapping(value="/responseAccessToken",method = RequestMethod.POST) public HttpEntity token(HttpServletRequest request){ System.out.println("--------服務端/responseAccessToken-----------------------------------------------------------"); OAuthIssuer oauthIssuerImpl=null; OAuthResponse response=null; //構建OAuth請求 try { OAuthTokenRequest oauthRequest =new OAuthTokenRequest(request); String authCode =oauthRequest.getParam(OAuth.OAUTH_CODE); String clientSecret = oauthRequest.getClientSecret(); if(clientSecret!=null||clientSecret!=""){ //生成Access Token oauthIssuerImpl =new OAuthIssuerImpl(new MD5Generator()); final StringaccessToken =oauthIssuerImpl.accessToken(); System.out.println(accessToken); System.out.println("--oooo---"); //生成OAuth響應 response = OAuthASResponse .tokenResponse(HttpServletResponse.SC_OK) .setAccessToken(accessToken) .buildJSONMessage(); }
System.out.println("--------服務端/responseAccessToken-----------------------------------------------------------");
//根據OAuthResponse生成ResponseEntity return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } catch (OAuthSystemExceptione) { // TODO Auto-generated catch block e.printStackTrace(); } catch (OAuthProblemExceptione) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("--------服務端/responseAccessToken-----------------------------------------------------------"); return null; } }
|
此段程式碼對應開發步驟4的前面一半,即服務端驗證code、生成token並給客戶端
4.3 向客戶端返回請求資源(username)的controller方法
@Controller public class UserInfoController {
@Autowired private UserServiceuserService;
@RequestMapping("/userInfo") public HttpEntity userInfo(HttpServletRequest request)throws OAuthSystemException{ System.out.println("-----------服務端/userInfo-------------------------------------------------------------");
try { //獲取客戶端傳來的OAuth資源請求 OAuthAccessResourceRequest oauthRequest =new OAuthAccessResourceRequest(request, ParameterStyle.QUERY); //獲取Access Token String accessToken =oauthRequest.getAccessToken(); System.out.println("accessToken"); //驗證Access Token /*if (accessToken==null||accessToken=="") { // 如果不存在/過期了,返回未驗證錯誤,需重新驗證 OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.ResourceResponse.INVALID_TOKEN) .buildHeaderMessage();
HttpHeaders headers = new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED); } */ //返回使用者名稱 User user=userService.selectByPrimaryKey(1); String username = accessToken+"---"+Math.random()+"----"+user.getUname(); System.out.println(username); System.out.println("服務端/userInfo::::::ppp"); System.out.println("-----------服務端/userInfo----------------------------------------------------------"); return new ResponseEntity(username, HttpStatus.OK); } catch (OAuthProblemExceptione) { // TODO Auto-generated catch block e.printStackTrace();
//檢查是否設定了錯誤碼 String errorCode =e.getError(); if (OAuthUtils.isEmpty(errorCode)) { OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .buildHeaderMessage();
HttpHeaders headers =new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED); }
OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(e.getError()) .setErrorDescription(e.getDescription()) .setErrorUri(e.getUri()) .buildHeaderMessage();
HttpHeaders headers =new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); System.out.println("-----------服務端/userInfo------------------------------------------------------------------------------"); return new ResponseEntity(HttpStatus.BAD_REQUEST); } } }
|
此程式碼對應開發步驟6的前一半。即服務端驗證access token、並將資源資訊給客戶端
至此,整個Java整合oauth就完成了。
另外:需要驗證的客戶端資訊,如clientId、clientSecret都是自行指定,與自己的專案相關,同時客戶端資訊的驗證方法也是依情況而定,沒有什麼具體標準,我的demo裡為了方便,基本上省略了客戶端資訊驗證,都是預設合法。但是accessTokenUrl、userInfoUrl、redirectUrl一定要與自己的專案路徑相符合。response_type、GrantType有標準模板,見程式碼。服務端生成的access token也是有標準的,見程式碼,too。
其他的所有模組和程式碼就是普通的spring-springmvc-mybatis了。
專案執行:
專案下載地址:點選開啟連結
下載專案壓縮包,解壓,裡面兩個maven專案:oauthserver和oauthclient01,分別對應oauth服務端和客戶端。
服務端對應的資料庫sql檔案在原始碼壓縮包裡可以看到。
兩個專案分別用8082埠(服務端埠)和8081埠(客戶端埠)部署並啟動。
輸入客戶端地址:http://localhost:8081/oauthclient01/index,顯示如下:
點選到服務端請求資源,就可以得到如下結果:
即獲取到了服務端的資源。