Spring Cloud微服务安全实战_5-2_实现授权码认证流程

目前的架构

到目前为止,已经实现了在前后端分离的架构,微服务的环境下,一个完整的业务逻辑,包括用户的登录,获取令牌,拿着令牌调服务,退出。(流程如下)

目前的架构是存在一些问题的:

1,用户输入用户名密码,是提交给了前端服务器的,前端服务器的开发人员,都会接触到用户的用户名密码,存在安全问题

2,每个客户端应用都要处理登录逻辑,一旦登录逻辑有变化,所有的客户端都要改,重新部署,有耦合。

希望的场景

 用户需要登录的时候,前端服务器直接将用户引导到 认证服务器 上,认证的动作是在认证服务器上完成的。

这样做的好处是:

1,客户端应用完全接触不到用户的用户名密码,保证了安全性。

2,客户端应用没有登录逻辑,登录逻辑都在认证服务器上,如果登录逻辑有变化,直接修改认证服务器一处,减少了耦合。

修改程序为上边的架构(OAuth协议的授权码模式)

+++++++++++++++++++++++ 番外, 讲一些 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

 点击去登录,跳转到了认证服务器,这是自带的页面

可以重写认证服务器的安全配置方法,自定义登录页

 

 到目前为止已经完成了上述授权码授权流程,但是还存在很多问题,比如,现在的退出登录:

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

只是将nb-admin客户端应用的session失效调了,里面没有token了,但是认证服务器上的token并没有失效,再点击登录,直接就会登录成功,不用输入用户名密码,给人的感觉是“没有退出去”。

下节来处理这些问题,本节代码github:https://github.com/lhy1234/springcloud-security/tree/chapt-5-2-authcode

猜你喜欢

转载自www.cnblogs.com/lihaoyang/p/12142861.html