springboot從原理上實現oauth2

springboot從原理上實現oauth2

一直以來,使用spring-cloud-starter-oauth2進行用戶認證,然而只要配置一些配置文件,寫一個登陸頁,就可以進行簡單的使用。這樣會造成我們無法理解oauth2的工作原理,從哪跳轉到哪,又在哪裏做驗證。這樣會導致oauth2用起來感覺莫名奇妙,也無法體會到它的安全性。

從網上找了一幅圖,就是整個oauth2.0的協議實現原理。
在這裏插入圖片描述

代碼

1.springboot客戶端依賴:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>

	<dependency>
		<groupId>org.apache.oltu.oauth2</groupId>
		<artifactId>org.apache.oltu.oauth2.client</artifactId>
		<version>0.31</version>
	</dependency>
</dependencies>

2.springboot服務端依賴:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
	<dependency>
		<groupId>org.apache.oltu.oauth2</groupId>
		<artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
		<version>0.31</version>
	</dependency>
	<dependency>
		<groupId>org.apache.oltu.oauth2</groupId>
		<artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
		<version>0.31</version>
	</dependency>
</dependencies>

這裏用的springboot版本是2.0.3

3.在客戶端web項目中構造一個oauth的客戶端請求對象(OAuthClientRequest),在此對象中攜帶客戶端信息(clientId、accessTokenUrl、response_type、redirectUrl),將此信息放入http請求中,重定向到服務端。此步驟對應上圖1
ServerController.java

@RequestMapping("/requestServerCode")
public String requestServerFirst(HttpServletRequest request, HttpServletResponse response, RedirectAttributes attr)throws OAuthProblemException {
   clientId = "clientId";
    clientSecret = "clientSecret";
    accessTokenUrl = "responseCode";
    userInfoUrl = "userInfoUrl";
    redirectUrl = "http://localhost:8081/oauthclient01/server/callbackCode";
    responseType = "code";

    OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
    String requestUrl = null;
    try{
        OAuthClientRequest accessTokenRequest = OAuthClientRequest
                .authorizationLocation(accessTokenUrl)
                .setResponseType(responseType)
                .setClientId(clientId)
                .setRedirectURI(redirectUrl)
                .buildQueryMessage();
        requestUrl = accessTokenRequest.getLocationUri();
        System.out.println(requestUrl);
    }catch (OAuthSystemException e){
        e.printStackTrace();
    }
    return "redirect:http://localhost:8082/oauthserver/" + requestUrl;
}

此段代碼對應開發步驟1.其中accessTokenUrl是服務端返回code的controller方法映射地址。redirectUrl是告訴服務端,code要傳回客戶端的一個controller方法,該方法的映射地址就是redirectUrl。

4.在服務端web項目中接受第一步傳過來的request,從中獲取客戶端信息,可以自行驗證信息的可靠性。同時構造一個oauth的code授權許可對象(OAuthAuthorizationResponseBuilder),並在其中設置授權碼code,將此對象傳回客戶端。此步驟對應上圖2
AuthorizeController.java

@RequestMapping("/responseCode")
public Object toShowUser(Model model, HttpServletRequest request){
    System.out.println("-------------------------服務端/responseCode---------------------------------------------------");
    try{
        OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request);
        if(oauthRequest.getClientId() != null && oauthRequest.getClientId() != ""){
            String authorizationCode = "authorizationCode";
            String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);
            OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND);
            builder.setCode(authorizationCode);
            String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);
            final OAuthResponse response = builder.location(redirectURI).buildQueryMessage();
            System.out.println("服務的/responseCode內,返回的回調路徑:" + response.getLocationUri());
            System.out.println("----------------------服務端/responseCode-----------------------------------------------");
            String responseUri = response.getLocationUri();

            HttpHeaders headers = new HttpHeaders();
            try{
                headers.setLocation(new URI(response.getLocationUri()));
            }catch(URISyntaxException e){
                e.printStackTrace();
            }
            return "redirect:" + responseUri;
        }
    }catch (OAuthSystemException e) {
        e.printStackTrace();
    } catch (OAuthProblemException e) {
        e.printStackTrace();
    }
    System.out.println("---------------------服務端/responseCode-----------------------------------------------------");
    return null;
}

5.在客戶端web項目中接受第二步的請求request,從中獲得code。同時構造一個oauth的客戶端請求對象(OAuthClientRequest),此次在此對象中不僅要攜帶客戶端信息(clientId、accessTokenUrl、clientSecret、GrantType、redirectUrl),還要攜帶接受到的code。再構造一個客戶端請求工具對象(oAuthClient),這個工具封裝了httpclient,用此對象將這些信息以post(一定要設置成post)的方式請求到服務端,目的是爲了讓服務端返回資源訪問令牌。此步驟對應上圖3。(另外oAuthClient請求服務端以後,會自行接受服務端的響應信息。
ServerController.java

@RequestMapping("/callbackCode")
public Object toLogin(HttpServletRequest request)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";
    code = request.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();

        OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST);
        String accessToken = oAuthResponse.getAccessToken();

        // 獲取時間用於查看token是否過期Long expiresln = oAuthResponse.getExpiresIn();
        System.out.println("客戶端/callbackCode方法的token:::" + accessToken);
        System.out.println("--------------客戶端/callbackCode------------------------------------------------------------------");
        return "redirect:http://localhost:8081/oauthclient01/server/accessToken?accessToken=" + accessToken;
    }catch (OAuthSystemException e){
        e.printStackTrace();
    }
    return null;
}

6.在服務端web項目中接受第三步傳過來的request,從中獲取客戶端信息和code,並自行驗證。再按照自己項目的要求生成訪問令牌(accesstoken),同時構造一個oauth響應對象(OAuthASResponse),攜帶生成的訪問指令(accesstoken),返回給第三步中客戶端的oAuthClient。oAuthClient接受響應之後獲取accesstoken,此步驟對應上圖4
AccessTokenController.java

@RequestMapping(value = "/responseAccessToken",method = RequestMethod.POST)
public HttpEntity token(HttpServletRequest request){
    System.out.println("-----------------服務端/responseAccessToken-------------------------------------");
    OAuthIssuer oAuthIssuerImpl = null;
    OAuthResponse response = null;
    try{
        OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);
        String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE);
        String clientSecret = oauthRequest.getClientSecret();
        if(clientSecret != null || clientSecret != ""){
            oAuthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
            final String accessToken = oAuthIssuerImpl.accessToken();
            System.out.println(accessToken);
            System.out.println("--000---");

            response = OAuthASResponse
                    .tokenResponse(HttpServletResponse.SC_OK)
                    .setAccessToken(accessToken)
                    .buildJSONMessage();
        }
        System.out.println("----------------服務端/responseAccessToken---------------------------------------");

        return new ResponseEntity(response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));
    }catch (OAuthSystemException e) {
        e.printStackTrace();
    }catch (OAuthProblemException e) {
        e.printStackTrace();
    }
    System.out.println("------------服務端/responseAccessToken----------------------------------------------");
    return null;
}

7.此時客戶端web項目中已經有了從服務端返回過來的accesstoken,那麼在客戶端構造一個服務端資源請求對象(OAuthBearerClientRequest),在此對象中設置服務端資源請求URI,並攜帶上accesstoken。再構造一個客戶端請求工具對象(oAuthClient),用此對象去服務端靠accesstoken換取資源。此步驟對應上圖5
在服務端web項目中接受第五步傳過來的request,從中獲取accesstoken並自行驗證。之後就可以將客戶端請求的資源返回給客戶端了。
ServerController.java

@RequestMapping("/accessToken")
 public ModelAndView accessToken(String accessToken){
     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-------------------------------------------------------------");
         return modelAndView;
     }catch (OAuthSystemException e){
         e.printStackTrace();
     }catch (OAuthProblemException e){
         e.printStackTrace();
     }
     System.out.println("-------------------客戶端/accessToken------------------------------------------------------------------");
     return null;
 }

UserInfoController.java

@RequestMapping("/userInfo")
 public HttpEntity userInfo(HttpServletRequest request)throws OAuthSystemException{
     System.out.println("--------------------服務端/userInfo----------------------------------------------");
     try{
         OAuthAccessResourceRequest oauthRequest = new OAuthAccessResourceRequest(request, ParameterStyle.QUERY);
         String accessToken = oauthRequest.getAccessToken();
         System.out.println("accessToken");

         User user = new User();
         user.setUsername("tom");
         user.setPassword("123456");

         String username = accessToken+"---"+Math.random()+"----"+user.getUsername();
         System.out.println(username);
         System.out.println("服務端/userInfo::::::ppp");
         System.out.println("-----------服務端/userInfo----------------------------------------------------------");
         return new ResponseEntity(username, HttpStatus.OK);
     }catch(OAuthProblemException e){
         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);
     }
 }

另外:項目中有些參數是自己指定,然後認證服務端是不需要寫跳轉的IP,這也是oauth2的精髓,需要跳轉的地方都有客戶端提供,項目中生成的accessToken可以當成token使用,這樣其他服務就不需要再認證,只要判斷是否由此token就可以了。

如果想要深入研究底層原理的話,推薦兩個博客:
阮一峯
張開濤

那還有同學問了,如果我的客戶端不是java,而是nginx咋辦,那麼你可以看我的另外一篇博客。

https://blog.csdn.net/lwy572039941/article/details/102397704

項目地址:

https://download.csdn.net/download/lwy572039941/11835481

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章