认证和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

 

posted @ 2020-02-06 18:02  caofanqi  阅读(3129)  评论(0编辑  收藏  举报