认证和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