zoukankan      html  css  js  c++  java
  • 认证和SSO(二)-OAuth2四种授权模式及项目改造为授权码模式实现单点登陆SSO

    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

  • 相关阅读:
    PAT (Basic Level) Practise 1013 数素数
    PAT (Basic Level) Practise 1014 福尔摩斯的约会
    codeforces 814B.An express train to reveries 解题报告
    KMP算法
    rsync工具
    codeforces 777C.Alyona and Spreadsheet 解题报告
    codeforces 798C.Mike and gcd problem 解题报告
    nginx + tomcat多实例
    MongoDB副本集
    指针的艺术(转载)
  • 原文地址:https://www.cnblogs.com/caofanqi/p/12269632.html
Copyright © 2011-2022 走看看