• 认证和SSO(三)-基于session的SSO存在的问题之session问题


    1、两个session,三个有效期

      在上一节,实现的其实是基于session的sso,在该方案中有两个session,一个是客户端应用的session,一个是认证服务器的sessioin。一共有三个有效期,两个session的有效期,还有一个token令牌的有效期。他们的作用是如下:

    1.1、客户端应用session的有效期,控制多长时间跳转一次认证服务器。

    1.2、认证服务器session的有效期,控制多长时间需要用户输入一次用户名密码。

    1.3、token有效期,控制登陆一次能访问多长时间微服务。

    2、处理退出的用户体验问题

      因为有这两个session,我们目前的退出逻辑只是将客户端应用的session失效掉,但是并没有将认证服务器的session失效掉,修改推出逻辑,退出时客户端应用和认证服务器两个session都失效掉。

    2.1、index.xml,/logout是springsecurity提供的退出方法

        //退出
        function logout() {
            $.get("/logout",function(){});
            //客户端session失效后,将认证服务器session也失效掉
            location.href = "http://auth.caofanqi.cn:9020/logout";
        }

    2.2、登陆后点击退出后会跳转到认证服务器进行退出如下

    2.3、点击Log Out,提示已经退出,跳转到认证服务器的/login?logout,是springsecurity默认的退出成功路径

    2.4、再次输入用户名密码后,是如下情况

    2.5、如果我们想要退出成功后跳转回我们自己的首页,可以做如下修改

      2.5.1、index.html添加重定向url

        //退出
        function logout() {
            $.get("/logout",function(){});
            //客户端session失效后,将认证服务器session也失效掉,添加重定向url
            location.href = "http://auth.caofanqi.cn:9020/logout?redirect_uri=http://web.caofanqi.cn:9000";
        }

      2.5.2、因为springsecurity默认处理退出请求的是DefaultLogoutPageGeneratingFilter这个过滤器类,我们在我们自己的类路径下,写一个一模一样的类(包名类名相同),来替代spring原有的(因为java的类加载机制)。修改代码中的html片段,使其能够自动提交,并携带redirect_uri参数。

    package org.springframework.security.web.authentication.ui;
    
    import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
    import org.springframework.security.web.util.matcher.RequestMatcher;
    import org.springframework.util.Assert;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.Collections;
    import java.util.Map;
    import java.util.function.Function;
    
    /**
     * Generates a default log out page.
     * 对spring security 提供的过滤器进行自定义
     *
     */
    public class DefaultLogoutPageGeneratingFilter extends OncePerRequestFilter {
        private RequestMatcher matcher = new AntPathRequestMatcher("/logout", "GET");
    
        private Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs = request -> Collections
                .emptyMap();
    
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            if (this.matcher.matches(request)) {
                renderLogout(request, response);
            } else {
                filterChain.doFilter(request, response);
            }
        }
    
        private void renderLogout(HttpServletRequest request, HttpServletResponse response)
                throws IOException {
            String page =  "<!DOCTYPE html>
    "
                    + "<html lang="en">
    "
                    + "  <head>
    "
                    + "    <meta charset="utf-8">
    "
                    + "    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    "
                    + "    <meta name="description" content="">
    "
                    + "    <meta name="author" content="">
    "
                    + "    <title>Confirm Log Out?</title>
    "
                    + "    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
    "
                    + "    <link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous"/>
    "
                    + "  </head>
    "
                    + "  <body>
    "
                    + "     <div class="container">
    "
                    + "      <form id="logoutForm" class="form-signin" method="post" action="" + request.getContextPath() + "/logout">
    "
    //                + "        <h2 class="form-signin-heading">Are you sure you want to log out?</h2>
    "
                    + renderHiddenInputs(request)
                    + "        <input type=hidden name=redirect_uri value="+request.getParameter("redirect_uri")+">"
    //                + "        <button class="btn btn-lg btn-primary btn-block" type="submit">Log Out</button>
    "
                    +          "<script>document.getElementById('logoutForm').submit()</script>"
                    + "      </form>
    "
                    + "    </div>
    "
                    + "  </body>
    "
                    + "</html>";
    
            response.setContentType("text/html;charset=UTF-8");
            response.getWriter().write(page);
        }
    
        /**
         * Sets a Function used to resolve a Map of the hidden inputs where the key is the
         * name of the input and the value is the value of the input. Typically this is used
         * to resolve the CSRF token.
         * @param resolveHiddenInputs the function to resolve the inputs
         */
        public void setResolveHiddenInputs(
                Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs) {
            Assert.notNull(resolveHiddenInputs, "resolveHiddenInputs cannot be null");
            this.resolveHiddenInputs = resolveHiddenInputs;
        }
    
        private String renderHiddenInputs(HttpServletRequest request) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> input : this.resolveHiddenInputs.apply(request).entrySet()) {
                sb.append("<input name="").append(input.getKey()).append("" type="hidden" value="").append(input.getValue()).append("" />
    ");
            }
            return sb.toString();
        }
    }

      2.5.3、WebSecurityConfig安全配置配置类,自定义退出成功处理器

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin().and()
                .httpBasic().and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler());
        }
    
        /**
         * 自定义退出成功处理器
         */
        @Bean
        public LogoutSuccessHandler logoutSuccessHandler() {
            return (request, response, authentication) -> {
                String redirectUri = request.getParameter("redirect_uri");
                if (StringUtils.isNotBlank(redirectUri)) {
                    response.sendRedirect(redirectUri);
                }
            };
        }

      2.5.4、启动各项目,登陆、获取订单详情、退出、再次登陆,到我们正常的页面(图略)。

    3、认证服务器使用spring-session实现session共享

      认证服务器,在生产环境中,需要是高可用的,会集群部署,各节点需要进行session共享,我们使用spring-session来实现。

      spring-session为我们提供了多种实现方式,有redis,jdbc等,我们使用jdbc来实现。

    3.1、pom中引入依赖

            <dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-jdbc</artifactId>
            </dependency>

    3.2、在spring-session-jdbc中提供了各种数据库的建表语句,copy出来执行即可,这里使用mysql会创建两张表。

    3.3、application.yml配置文件

    server:
      port: 9020
      servlet:
        session:
          #session超时时间设置
          timeout: 2592000
    
    spring:
      application:
        name: auth-server
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/study-security?characterEncoding=UTF-8&useSSL=false
        username: root
        password: root
      #speing-session相关配置
      session:
        #指定存储类型,这里使用JDBC
        store-type: JDBC

    3.4、启动各项目进行登陆,数据库中多出了记录

    3.5、将webApp、认证服务器重启,再次刷新页面,不用再次登陆了,说明我们的认证服务器是高可用的了。

    spring-session官方文档:https://docs.spring.io/spring-session/docs/2.2.1.RELEASE/reference/html5/

    spring-session官方示例:https://github.com/spring-projects/spring-session/tree/2.2.1.RELEASE/spring-session-samples

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

     

  • 相关阅读:
    tcp/ip协议
    soap协议
    JS引擎运行js过程
    clear:both可以清除浮动的原理(给子元素设置clear:both相当于给它自动设置了1个mrgin-top外边距从而可以撑开父盒子高度)
    BFC详解
    圣杯布局和双飞翼布局的作用和区别
    flex布局之space-evenly兼容性不好,巧用space-between实现space-evenly效果
    css巧用 transform的 rotate属性得到三角形箭头(取代iconfont的字体符号)
    li 鼠标悬停抖动问题
    小米官网首页商品列表鼠标悬停动画和阴影效果
  • 原文地址:https://www.cnblogs.com/caofanqi/p/12271746.html
Copyright © 2020-2023  润新知