zoukankan      html  css  js  c++  java
  • Spring Cloud微服务安全实战_5-1_单点登录基本架构

    下面是常见的前后端分离的架构,准确的说,这是一种半分离的架构,这种架构存在一些问题:

    1,SEO,搜索引擎只能识别静态的HTML资源,爬HTML的DOM树,它是没法识别js动态渲染出来的内容的,所以如果是这种架构,SEO会有问题

    2,页面渲染是在浏览器完成的,如果业务复杂,要数据量大,可能会对浏览器造成压力,可能会卡顿。

     鉴于以上前后端分离的架构存在的问题,我们要搭的一个前后端分离的架构是下面的架构:

    主要区别是把Nginx换成了nodeJS,nodejs可以实现nginx的所有的功能。

    而且还提供了其他的功能,比如服务端的页面渲染,浏览器在请求一个html资源的时候,nodejs可以发api请求,在nodejs上把页面渲染好,然后把渲染好的页面给浏览器,此时做SEO的时候,搜索引擎爬到的是一个完整的页面。如果某些页面不需要SEO,仍然可以在页面直接发api,在浏览器渲染页面,这两种都支持。

    本实验用springboot代替nodejs,页面使用html js。之前的系列文章里,都是使用的postman作为客户端应用的,从今往后,就用实际的前端应用替换postman了。

    整体就是下面这种架构:

     下面开始敲代码

     1,新建一个应用nb-admin

    效果:

    未登录时候,显示

     点击登录,输入用户名密码

     (认证服务器写死的,密码是123456即可)

     登录后:

    主要代码:在  AdminController ,处理登录请求,调用网关,获取token ,获取后放在了 前端服务器的session里

    @ResponseBody
        @PostMapping("/login")
        public void login(@RequestParam String username,@RequestParam String password/*@RequestBody Credential credential*/, HttpSession session){
    
            //认证服务器验token地址 /oauth/check_token 是  spring .security.oauth2的验token端点
            String oauthServiceUrl = "http://localhost:9070/token/oauth/token";
    
            HttpHeaders headers = new HttpHeaders();//org.springframework.http.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("username",username);
            params.add("password",password);
            params.add("grant_type","password");
            params.add("scope","read write");
    
            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());
            log.info("token info : {}",response.getBody().toString());
        }

    在 index.html 里,进页面就发一个/me 请求,如果能从session获取到token信息(先这么干),说明登录成功:

    @GetMapping("/me")
        @ResponseBody
        public AccessToken me(HttpSession session){
            AccessToken accessToken = (AccessToken) session.getAttribute("token");
            return accessToken;
        }

    index.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Index</title>
    </head>
    <body>
        <h1>欢迎来到sso子系统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='/loginPage'>未登录,去登录</a>";
                    $("#loginTip").append(href);
                }
            });
        });
    
    </script>
    </html>

    login.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>login</title>
    </head>
    <body>
    
        <center>
            <h3>登录</h3>
            <table border="1">
                <tr>
                    <td>用户名</td>
                    <td><input id="username" name="username" value="lhy"/></td>
                </tr>
                <tr>
                    <td>密码</td>
                    <td><input id="password" name="password" value="123456"/></td>
                </tr>
                <tr>
                    <td colspan="2" align="right"><button id="submitBtn" onclick="login()">登录</button></td>
                </tr>
            </table>
        </center>
    </body>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
    <script>
        function login() {
            var username = $("#username").val();
            var password = $("#password").val();
            //alert(username+", " +password)
    
            $.ajax({
                type: "POST",
                url: "/login",
                data: {"username":username, "password":password},
                success: function(msg){
                    location.href = "/index";
                },
                error:function(msg){
                    alert("登录失败!!")
                }
            });
        }
    
    </script>
    </html>

     请求转发

    在nb-admin上,index.html,加上获取订单信息,点击获取订单按钮,发一个请求到前端服务nb-admin,nb-admin配上zuul网关,让它把这个请求转发到网关去,由网关再去请求订单服务,获取订单信息。

    在nb-admin项目加入zuul依赖:

    <dependency>
         <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>

    启动类加上注解:

    @EnableZuulProxy

    配置zuul路由,以 /api 开头的请求,转发到网关

    zuul:
      routes: #路由的配置是个Map,可以配置多个
        api:  #api开头的请求,都转发到网关9070
          url:  http://gateway.nb.com:9070
      sensitive-headers:  null  #设置敏感头设置为空,Authorization等请求头的请求,都往后转发
    
    server:
      port: 8080
    spring:
      application:
        name: admin

    注:几个项目都配置了host,以方便区分cookie等

    127.0.0.1 gateway.nb.com    ----网关

    127.0.0.1 admin.nb.com  ----前端服务

    127.0.0.1 auth.nb.com    ----认证服务

    127.0.0.1 order.nb.com  ----订单服务

    网关上,获取订单信息,是需要token的,所以在nb-admin上,需要从session中获取到token,加在请求头里,再发往网关。新建一个zuulFilter,统一在请求头加token。

    SessionTokenFilter
    package com.nb.security.admin;
    
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    
    /**
     * 从session获取token,统一加到请求头中去
     */
    @Component
    public class SessionTokenFilter extends ZuulFilter {
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 1;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
            RequestContext requestContext = RequestContext.getCurrentContext();
            HttpServletRequest request = requestContext.getRequest();
            AccessToken accessToken = (AccessToken)request.getSession().getAttribute("token");
            if(accessToken != null){
                requestContext.addZuulRequestHeader("Authorization","Bearer "+accessToken.getAccess_token());
            }
            return null;
        }
    }

    AccessToken

    import lombok.Data;
    
    import java.util.Date;
    
    /**
     * access_token
     * Created by: 李浩洋 on 2020-01-02
     **/
    @Data
    public class AccessToken {
    
        private String access_token;
    
        private String token_type;
    
        private Date expires_in;
    
        private String scope;
    }

    现在效果:

     登录:

     登录后

    点击获取订单信息,会发一个  api/order/orders/1 请求,由于nb-admin里配置了zuul路由,api开头的请求都会转发到网关,所以这里实际访问了网关,网关又转发到了order-api,最终返回orderInfo

    function getOrderInfo(){
            $.get("api/order/orders/1",function(data){
                $("#orderId").val(data.id);
                $("#productId").val(data.productId);
            });
        }

    退出登录

    退出登录很简单,直接发一个请求,将nb-admin里的session失效,就行了
    @GetMapping("/logout")
        public String logout(HttpSession session){
            session.invalidate();
            return "index";
        }

    目前已经用 springboot实现了一个前端服务器,实现了oauth协议中 password模式的登录,请求转发,退出。目前还存在不少问题,下节讲解问题并解决。

    (其实我认为将这个就当做某个系统的后端服务也没啥问题,类似于你去微信申请客户端应用一样,这里的nb-admin就是我们自己的公众号项目)

    代码github:https://github.com/lhy1234/springcloud-security/tree/chapt-5-1-ui

    如果对你有一点,就给个小星星吧

  • 相关阅读:
    GDI+学习笔记2
    GDI+学习笔记1- 概述
    Java网络编程和NIO详解1:JAVA 中原生的 socket 通信机制
    Java网络编程和NIO详解开篇:Java网络编程基础
    我在阿里工作的这段时间里,都学到了哪些东西
    在大公司做凤尾,还是在小公司做鸡头?
    蚂蚁金服财富技术部,诚招Java研发工程师。校招内推!!!
    测试课程
    新笔记
    阅读书籍电技术
  • 原文地址:https://www.cnblogs.com/lihaoyang/p/12129852.html
Copyright © 2011-2022 走看看