認證和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>\n"
                + "<html lang=\"en\">\n"
                + "  <head>\n"
                + "    <meta charset=\"utf-8\">\n"
                + "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n"
                + "    <meta name=\"description\" content=\"\">\n"
                + "    <meta name=\"author\" content=\"\">\n"
                + "    <title>Confirm Log Out?</title>\n"
                + "    <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
                + "    <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
                + "  </head>\n"
                + "  <body>\n"
                + "     <div class=\"container\">\n"
                + "      <form id=\"logoutForm\" class=\"form-signin\" method=\"post\" action=\"" + request.getContextPath() + "/logout\">\n"
//                + "        <h2 class=\"form-signin-heading\">Are you sure you want to log out?</h2>\n"
                + 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>\n"
                +          "<script>document.getElementById('logoutForm').submit()</script>"
                + "      </form>\n"
                + "    </div>\n"
                + "  </body>\n"
                + "</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("\" />\n");
        }
        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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM