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


     

    目前的架构

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

    目前的架构是基于oauth2的password模式的,是存在一些问题的:

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

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

    希望的场景

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

    这样做的好处是:

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

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

    将password授权模式改为授权码模式

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

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

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

     

    到目前为止已将OAuth2的授权模式由 【密码模式】改造成了 【 授权码】  授权流程,完成改造的同时,也实现了SSO,微服务环境前后端分离模式下的单点登录。

    目前的单点登录实际上是基于session的,目前有两个session,一个是认证服务器的session,一个是客户端应用的session,用户在客户端应用上点击登录按钮,跳转到认证服务器上做登录,登录成功后认证服务器上存了该用户的session信息,客户端应用拿到认证服务器返回的access_token后,将token存到自己的session,这样就认为该用户已登录:

    但是还存在很多问题,比如,现在的退出登录:

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

    只是将nb-admin客户端应用的session失效掉了,里面没有token了,但是认证服务器上的session并没有失效,重启nb-admin,客户端应用的session是失效掉了,重新访问 http://admin.nb.com:8080/index ,出现未登录:

     点击登录,跳转到认证服务器,由于认证服务器session上还有客户端该用户的登录信息,所以不会出现登录表单,用户的感觉是没有再做登录就已经登录,给人的感觉是,我已经点了退出登录,但是点击登录按钮后,没让我再输用户名密码,感觉是“没有退出去”。其实F12可以看到,已经请求了认证服务器,只不过是认证服务器的session没有失效,直接就登录成功了而已 :

    1,请求认证服务器登录页    http://auth.nb.com:9090/oauth/authorize?client_id=admin&redirect_uri=http://admin.nb.com:8080/oauth/callback&response_type=code&state=/index  

    2,登录成功后的回调            http://admin.nb.com:8080/oauth/callback?code=IMbu7Y&state=/index

     还有目前认证服务器的session是存在内存的,生产环境下认证服务器要保证高可用,是一个集群,要保证session共享,所以直接用session是不行的,接下来来处理这些问题。

    本节代码github:https://github.com/lhy1234/springcloud-security/tree/chapt-5-2-authcode,如果帮助到了你,给个小星星吧

  • 相关阅读:
    js中字符串的操作
    javascript中null与undefined的区别
    javascript中=、==与===的区别
    less
    火狐浏览器下点击a标签时出现虚线的解决方案
    js删除数组中重复的元素
    js中的indexOf
    css选择器
    bootstrap-table组合表头
    使用yo -v查看yeoman版本号
  • 原文地址:https://www.cnblogs.com/lihaoyang/p/12142861.html
Copyright © 2020-2023  润新知