zoukankan      html  css  js  c++  java
  • 认证和SSO(一)-基于OAuth2单点登陆基本架构

    1、前后端分离架构

      1.1、前后端半分离

      工作流程大致是,访问html页面,加载css、js等静态资源,加载到浏览器时,js会发送一些ajax请求由nginx转发到后端服务器,后端服务器给出相应的json数据,页面解析Json数据,通过Dom操作渲染页面。

      在这样的架构下,存在一些明显的弊端:

      SEO( 搜索引擎优化)非常不方便,由于搜索引擎的爬虫无法爬下JS异步渲染的数据,导致这样的页面,SEO会存在一定的问题;

      在Json返回的数据量比较大的情况下,渲染的十分缓慢,会出现页面卡顿的情况;

    1.2、前后端分离

      将nginx替换成了NodeJS,浏览器请求NodeJS,NodeJS在发http请求去服务器获取json数据,NodeJS收到json后再渲染出HTML页面,NodeJS直接将HTML页面flush到浏览器,这样,浏览器得到的就是普通的HTML页面,而不用再发Ajax去请求服务器了。

    在这里,前端不仅包括浏览器,还包括一个NodeJS服务器,后端只负责数据的处理。

    2、前后端分离的OAuth2认证架构

      在之前我们实现的OAuth2认证架构中,客户端应用,其实是使用的一些http请求工具,如Restlet Client、Postman等。我们将架构改造为如下,客户端应用,其实应该由浏览器和前端服务器组成。(在这里前端服务器,我们使用SpringBoot模拟实现,在实际工作中,应该是由前端人员使用NodeJS来实现)

    3、项目改造实现认证流程

      由上图可知,我们需要改造的有以下两点:第一步请求令牌时,将http请求工具更换为前端服务器来请求令牌;第五步由前端服务器携带令牌请求服务。

      3.1、搭建前端服务器(由SpringBoot模拟实现)

      3.1.1、项目结构

      3.1.2、页面index.html

    <!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>
    
    <div id="goto-login">
        <table>
            <tr>
                <td>用户名</td>
                <td><input id="username" name="username" value=""/></td>
            </tr>
            <tr>
                <td>密码</td>
                <td><input id="password" name="password" value=""/></td>
            </tr>
            <tr>
                <td colspan="2" align="right">
                    <button id="submit" onclick="login()">登录</button>
                </td>
            </tr>
        </table>
    </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();
                }
            });
        });
    
        //登陆方法
        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>

      3.1.3、提供登陆方法,使用password模式像认证服务器获取令牌,将原来由http客户端的动作,放到代码中

    /**
     * 模拟前端服务器
     *
     * @author caofanqi
     * @date 2020/2/5 16:34
     */
    @Slf4j
    @RestController
    @EnableZuulProxy
    @SpringBootApplication
    public class WebAppApplication {
    
        private RestTemplate restTemplate = new RestTemplate();
    
    
        public static void main(String[] args) {
            SpringApplication.run(WebAppApplication.class, args);
        }
    
        /**
         * 获取当前认证的token信息
         */
        @GetMapping("/me")
        public TokenInfoDTO me(HttpServletRequest request){
            return (TokenInfoDTO)request.getSession().getAttribute("token");
        }
    
    
        /**
         * 登陆方法
         * 向认证服务器获取令牌,将token信息放到session中
         */
        @PostMapping("/login")
        public void login(@RequestBody UserDTO userDTO, HttpServletRequest request) {
    
            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("username", userDTO.getUsername());
            params.set("password", userDTO.getPassword());
            params.set("grant_type", "password");
            params.set("scope", "read write");
    
            HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers);
    
            ResponseEntity<TokenInfoDTO> response = restTemplate.exchange(oauthTokenUrl, HttpMethod.POST, httpEntity, TokenInfoDTO.class);
    
            request.getSession().setAttribute("token", response.getBody());
            log.info("tokenInfo : {}",response.getBody());
    
        }
    
        /**
         *  退出
         */
        @RequestMapping("/logout")
        public void logout(HttpServletRequest request){
            request.getSession().invalidate();
        }
    
    }

      3.1.4、通过zuul的转发功能,我们再每个请求头中添加token令牌

    /**
     * 将session中的token取出放到请求头中
     *
     * @author caofanqi
     * @date 2020/2/6 0:34
     */
    @Component
    public class SessionTokenFilter extends ZuulFilter {
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
    
            RequestContext requestContext = RequestContext.getCurrentContext();
            HttpServletRequest request = requestContext.getRequest();
    
            TokenInfoDTO token = (TokenInfoDTO) request.getSession().getAttribute("token");
    
            if (token != null) {
                requestContext.addZuulRequestHeader("Authorization","bearer " + token.getAccess_token());
            }
    
            return null;
        }
    }

      3.1.5、修改hosts文件,便于区别各服务

      3.1.6、application.yml配置文件,将/api/**的请求都转发到gateway网关服务

    spring:
      application:
        name: webApp
    
    server:
      port: 9000
    
    zuul:
      routes:
        api:
          path: /api/**
          url: http://gateway.caofanqi.cn:9010
      sensitive-headers:

      3.2、启动各服务,测试

       3.2.1、访问http://web.caofanqi.cn:9000/,因为没有登陆,所以显示登陆界面

      3.2.2、输入zhangsan,123456,登陆成功

      认证服务器,返回给webApp的token信息

       3.2.3、点击获取订单信息,可以获得订单,SessionTokenFilter会在请求头中添加token,并且请求是以/api开头的会转发到网关,再由网关转发到order服务。

      

      3.2.4、点击退出,又到了登陆页面

    项目源码:https://github.com/caofanqi/study-security/tree/dev-web-password

  • 相关阅读:
    Django学习路31_使用 locals 简化 context 写法,点击班级显示该班学生信息
    Django学习路30_view中存在重复名时,取第一个满足条件的
    【Python】基本统计值计算

    [CTSC2016]萨菲克斯·阿瑞
    Linux与WIN 网络连接 winscp 的连接问题
    MFC 文件详解
    Hadoop入门学习随笔
    Scala入门学习随笔
    大数据用到的技术
  • 原文地址:https://www.cnblogs.com/caofanqi/p/12268421.html
Copyright © 2011-2022 走看看