1、OAuth2四種模式
1.1、密碼模式
這也是我們之前一直使用的模式,流程如下;這種模式下,用戶敏感信息直接泄漏給了客戶端應用,因此這種模式只能用於客戶端應用是我們自己開發的。因此密碼模式一般用於自己開發的App或單頁面應用。
1.2、授權碼模式
授權碼模式是四種模式中最繁瑣也是最安全的一種模式。用戶向客戶端發起請求時,客戶端應用引導用戶去授權服務器進行認證(需要有客戶端id和回調地址),認證成功授權服務器會將授權碼發送給客戶端應用,客戶端應用再通過授權碼,客戶端id,客戶端密碼去授權服務器換取令牌,授權服務器驗證無誤后,將令牌發送給客戶端應用。這種場景下,用戶的敏感信息沒有暴漏給客戶端應用,保證了安全。一般適用與客戶端應用是Web服務器或第三方的App。
1.3、簡化模式(隱式授權模式)
簡化模式相對於授權碼模式省略了,通過授權碼換取令牌過程。一般用於沒有服務器的前端應用。
4、客戶端模式
最簡單的模式,發出的令牌與用戶無關。因此,一般適用於我們完全信任的客戶端應用(服務器端服務),並且不需要用戶參與。
2、改造項目為授權碼認證方式
目前我們使用的是OAuth2的密碼模式,我們來修改為使用授權碼模式。
2.1、修改客戶端應用,需要用戶進行認證時直接引導去認證服務器完成認證
2.1.1、修改index.html用戶未登錄時,直接跳轉到認證服務器進行獲取授權碼,需提供客戶端id(client_id)、回調地址(redirect_uri)、返回值類型(response_type)為code、狀態標記(state)傳什么返回什么,為可選項。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>cfq security</title> </head> <body> <h1>Study Security</h1> <div id="login-success"> <h1>登陸成功!</h1> <p> <button onclick="getOrder()">獲取訂單信息</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> </hr> <p> <button onclick="logout()">退出</button> </p> </div> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <script> //判斷用戶是否登陸 $(function () { $.get("/me", function (data) { if (data) { //已登錄 $("#login-success").show(); $("#goto-login").hide(); } else { //未登錄 $("#login-success").hide(); //$("#goto-login").show(); location.href = "http://auth.caofanqi.cn:9020/oauth/authorize?" + "client_id=webApp&" + "redirect_uri=http://web.caofanqi.cn:9000/oauth/callback&" + "response_type=code&" + "state=abc"; } }); }); //登陸方法 function login() { var username = $("#username").val(); var password = $("#password").val(); $.ajax({ type: "POST", url: "/login", contentType: "application/json;charset=utf-8", data: JSON.stringify({"username": username, "password": password}), success: function (msg) { location.href = "/"; }, error: function (msg) { alert("登錄失敗!!") } }); } //獲取訂單信息,通過/api轉發到網關,通過/order轉發到order微服務 function getOrder() { $.get("/api/order/orders/1", function (data) { $("#orderId").val(data.id); $("#productId").val(data.productId); }); } //退出 function logout() { $.get("/logout",function(){}); location.href = "/"; } </script> </body> </html>
2.1.2、提供回調方法
/** * 回調方法 * 接收認證服務器發來的授權碼,並換取令牌 * @param code 授權碼 * @param state 請求授權服務器時發送的state */ @GetMapping("/oauth/callback") public void oauthCallback(@RequestParam String code, String state, HttpServletRequest request, HttpServletResponse response) throws IOException { String oauthTokenUrl = "http://gateway.caofanqi.cn:9010/token/oauth/token"; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setBasicAuth("webApp", "123456"); MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); params.set("code",code); params.set("grant_type", "authorization_code"); params.set("redirect_uri", "http://web.caofanqi.cn:9000/oauth/callback"); HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers); ResponseEntity<TokenInfoDTO> authResult = restTemplate.exchange(oauthTokenUrl, HttpMethod.POST, httpEntity, TokenInfoDTO.class); request.getSession().setAttribute("token", authResult.getBody()); log.info("tokenInfo : {}",authResult.getBody()); log.info("state :{}",state); //一般會根據state記錄需要登陸時的路由 response.sendRedirect("/"); }
2.2、認證服務器配置支持授權碼模式
2.3、啟動各服務,進行測試
2.3.1、訪問http://web.caofanqi.cn:9000/ 會直接跳轉到認證服務器
2.3.3、輸入zhangsan,123456進行登陸,會進行權限選擇
如果想跨過權限選擇,可以設置autoapprove為true
2.3.4、點擊Authorize會調轉會我們設定的頁面,再WebApp服務的日志中可以看到我們傳的state給原封不動的返回來了
2.3.5、點擊獲取訂單信息,可以正常獲得
2.3、到目前為止,我們實現了由OAuth2密碼模式替換為OAuth2授權碼模式,同時也實現了SSO,微服務環境下前后端分離的單點登陸。為什么說實現了SSO呢,我們可以把再啟動一個webApp服務,並把涉及到的端口改為8090,客戶端表中加入一條記錄,各修改如下
訪問http://web.caofanqi.cn:8090/ ,並沒有讓我們進行登陸,直接就是登陸狀態
這是為什么呢?因為在這種模式下,登陸的位置是認證服務器,只要在認證服務器的session沒過期,客戶端在任何時候跳過去,認證服務器就知道你是誰,就不會讓你輸入用戶名和密碼了,直接跳回客戶端應用去,如果之前的令牌還有效,就直接發給客戶端應用,無效了,就會新生成一個發給客戶端應用。
但是這種方式還有一些問題,我們在后面進行探討。
項目源碼:https://github.com/caofanqi/study-security/tree/dev-web-authorization_code