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

  • 相关阅读:
    bash 中的 ; && 与|| 的作用
    远程root用户无法登陆
    MySQL5.6主从同步(热备份)
    进程之间的通信方式
    远程连接openGuass配置
    openGuass1.1.0部署
    Go同步原语
    spring boot集成activiti6
    解决默认的jackson序列化循环引用的问题
    spring boot集成websocket
  • 原文地址:https://www.cnblogs.com/caofanqi/p/12269632.html
Copyright © 2011-2022 走看看