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