Java的oauth2.0 服務端與客戶端的實現

Endv發表於2018-05-05

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,顯示如下:

點選到服務端請求資源,就可以得到如下結果:

即獲取到了服務端的資源。

 

相關文章