Spring Cloud微服務安全實戰_5-2_實現授權碼認證流程&實現SSO初步


 

目前的架構

到目前為止,已經實現了在前后端分離的架構,微服務的環境下,一個完整的業務邏輯,包括用戶的登錄,獲取令牌,拿着令牌調服務,退出。(流程如下)

目前的架構是基於oauth2的password模式的,是存在一些問題的:

1,用戶輸入用戶名密碼,是提交給了前端服務器的,前端服務器的開發人員,都會接觸到用戶的用戶名密碼,存在安全問題

2,每個客戶端應用都要處理登錄邏輯,一旦登錄邏輯有變化,所有的客戶端都要改,重新部署,有耦合。

希望的場景

 用戶需要登錄的時候,前端服務器直接將用戶引導到 認證服務器 上,認證的動作是在認證服務器上完成的。

這樣做的好處是:

1,客戶端應用完全接觸不到用戶的用戶名密碼,保證了安全性。

2,客戶端應用沒有登錄邏輯,登錄邏輯都在認證服務器上,如果登錄邏輯有變化,直接修改認證服務器一處,減少了耦合。

 

將password授權模式改為授權碼模式

 

+++++++++++++++++++++++ 番外, 講一些 OAuth協議的4種授權模型++++++++++++++++++++++++

1,密碼模式

也是之前一直在用的授權模式,適用場景:手機app ,這個客戶端應用是你完全可以信任的,你的app就是自己公司開發的。但是這個模式並不適合在web場景下用,在web下,用戶名密碼並不是直接填給自己寫的應用的,而是填在瀏覽器呈現的一個頁面上的,這個瀏覽器是客戶端應用的一個代理,瀏覽器是沒法保證安全性的。(還是不是很理解哪里不安全)

 

 

 2,授權碼模式

 

1,用戶訪問客戶端應用

2,引導用戶到認證服務器進行登錄(此步驟需要攜帶客戶端應用的clientId,可以是html直接轉發認證服務器),用戶輸入用戶名、密碼

3,認證成功后,認證服務器向客戶端應用發一個授權碼code

4,客戶端應用拿着授權碼code,和clientId,clientSecret,去換取access_token

5,返回access_token給客戶端應用 

這種場景下,用戶名、密碼、客戶端應用信息,都沒有直接暴露在瀏覽器,是web下是最安全的。

3,隱式授權/簡化授權

 

 

 

 

授權碼模式的簡化,用戶認證成功后,直接將token返回給瀏覽器。因為某些應用沒有前端服務器,只有一堆靜態的html(很少見),這種模式,一般不用。

4,客戶端證書

 

 

 客戶端應用直接發 clientId、clientSecret給認證服務器,發的令牌是針對客戶端應用的,不是針對用戶的。跟沒授權一樣,令牌不能識別用戶身份。

+++++++++++++++++++++++++++++++++我是分割線結束+++++++++++++++++++++++++++++++++++++++++

 改造nb-admin代碼為授權碼模式

1,刪掉登錄頁面,登錄是在認證服務器上完成的,所以這里刪除nb-admin的登錄頁面

2,進入 index.html,點擊登錄,將用戶引導到認證服務器提供的登錄頁去。

 index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
    <h1>welcome to 系統1</h1>
    <div id="loginTip"></div>
    <p><button onclick="getOrderInfo()">獲取訂單信息</button></p>

    <table>
        <tr><td>order id</td><td><input id="orderId" /></td></tr>
        <tr><td>order product id</td><td><input id="productId" /></td></tr>
    </table>




</body>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
<script>

    function getOrderInfo(){
        $.get("api/order/orders/1",function(data){
            $("#orderId").val(data.id);
            $("#productId").val(data.productId);
        });
    }

    $(document).ready(function(){

        $.get("/me",function(data,status){
            if(data){
                //已登錄
                var htm = "已登錄,<a href='/logout'>退出</a>";
                $("#loginTip").html(htm);
            }else{
                //未登錄
                var href = "<a href='/toAuthLogin'>未登錄,去登錄</a>";
                $("#loginTip").append(href);
            }
        });
    });

</script>
</html>

點擊登錄按鈕,轉發到認證服務器,這個可以放在前端做,因為只有clientId信息,沒有安全問題。

/**
     * 重定向到認證服務器的登錄頁
     * @param response
     * @throws IOException
     */
    @GetMapping("/toAuthLogin")
    public void toAuthLogin(HttpServletResponse response) throws IOException{

        String redirectUrl = "http://auth.nb.com:9090/oauth/authorize?"
                            +"client_id=admin&"
                            +"redirect_uri=http://admin.nb.com:8080/oauth/callback&"
                            +"response_type=code&"
                            +"state=/index"; //state參數傳過去啥傳回來啥,一般記錄跳轉之前的路徑
        response.sendRedirect(redirectUrl);
    }

授權回調:

/**
     * 授權回調
     * @param code 授權碼
     * @param state 自定義參數
     * @param session
     */
    @GetMapping("/oauth/callback")
    public String callback(@RequestParam String code,String state ,HttpSession session){

        log.info("code is {}, state is {}",code,state);

        //認證服務器驗token地址 /oauth/check_token 是  spring .security.oauth2的驗token端點
        String oauthServiceUrl = "http://gateway.nb.com:9070/token/oauth/token";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);//不是json請求
        //網關的appId,appSecret,需要在數據庫oauth_client_details注冊
        headers.setBasicAuth("admin","123456");

        MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
        params.add("code",code);//授權碼
        params.add("grant_type","authorization_code");//授權類型-授權碼模式
        //認證服務器會對比數據庫客戶端信息的的redirect_uri和這里的是不是一致,不一致就報錯
        params.add("redirect_uri","http://admin.nb.com:8080/oauth/callback");

        HttpEntity<MultiValueMap<String,String>> entity = new HttpEntity<>(params,headers);
        ResponseEntity<AccessToken> response = restTemplate.exchange(oauthServiceUrl, HttpMethod.POST, entity, AccessToken.class);

        session.setAttribute("token",response.getBody());

        return "redirect:/index";
    }

給admin應用添加授權碼模式:

 

 啟動admin服務,認證服務,訂單服務,網關,訪問admin

 

 點擊去登錄,跳轉到了認證服務器,這是自帶的頁面

 

可以重寫認證服務器的安全配置方法,自定義登錄頁

 

 

到目前為止已將OAuth2的授權模式由 【密碼模式】改造成了 【 授權碼】  授權流程,完成改造的同時,也實現了SSO,微服務環境前后端分離模式下的單點登錄。

目前的單點登錄實際上是基於session的,目前有兩個session,一個是認證服務器的session,一個是客戶端應用的session,用戶在客戶端應用上點擊登錄按鈕,跳轉到認證服務器上做登錄,登錄成功后認證服務器上存了該用戶的session信息,客戶端應用拿到認證服務器返回的access_token后,將token存到自己的session,這樣就認為該用戶已登錄:

 

但是還存在很多問題,比如,現在的退出登錄:

@GetMapping("/logout")
    public String logout(HttpSession session){
        session.invalidate();
        return "index";
    }

只是將nb-admin客戶端應用的session失效掉了,里面沒有token了,但是認證服務器上的session並沒有失效,重啟nb-admin,客戶端應用的session是失效掉了,重新訪問 http://admin.nb.com:8080/index ,出現未登錄:

 點擊登錄,跳轉到認證服務器,由於認證服務器session上還有客戶端該用戶的登錄信息,所以不會出現登錄表單,用戶的感覺是沒有再做登錄就已經登錄,給人的感覺是,我已經點了退出登錄,但是點擊登錄按鈕后,沒讓我再輸用戶名密碼,感覺是“沒有退出去”。其實F12可以看到,已經請求了認證服務器,只不過是認證服務器的session沒有失效,直接就登錄成功了而已 :

1,請求認證服務器登錄頁    http://auth.nb.com:9090/oauth/authorize?client_id=admin&redirect_uri=http://admin.nb.com:8080/oauth/callback&response_type=code&state=/index  

2,登錄成功后的回調            http://admin.nb.com:8080/oauth/callback?code=IMbu7Y&state=/index

 

 還有目前認證服務器的session是存在內存的,生產環境下認證服務器要保證高可用,是一個集群,要保證session共享,所以直接用session是不行的,接下來來處理這些問題。

 

本節代碼github:https://github.com/lhy1234/springcloud-security/tree/chapt-5-2-authcode,如果幫助到了你,給個小星星吧

 

歡迎關注個人公眾號一起交流學習:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM