8、单点登录原理
前言
Github:https://github.com/HealerJean
1、单点登录流程
1.1、单点登录过程
1.1.1、A系统登录
用户通过浏览器地址栏访问系统A,系统A去
Cookie
中拿JSESSION
,即在Cookie
中维护的当前回话session
的id,如果拿到了,说明用户已经登录,如果未拿到,说明用户未登录。跳转到CAS
认证中心进行登录,然后输入正确的用户信息进行登录
A的地址为:http://a:8080/
CAS认证中心的服务地址为:http://cas.server:8080/
重点向前后地址变化为:http://a:8080/————>http://cas.server:8080/?service=http://a:8080/
1.1.2、登录成功,签发票据ST票据( ickt)
登录成功后,
CAS
认证中心签发一个TGC票据
,写入浏览器同时生成一个TGT对象
,放入自己的缓存,TGT对象的ID就是cookie的值,并再次重定向到客户端A,同时携带着ST票据
重定向之后的地址栏变成如下:将票据以ticket为参数名的方式通过地址栏发送给系统A
http://a:8080/?ticket=ST-XXXX-XXX,
1.1.3、A系统后台,验证ST票据,获取用户信息并成功登录创建会话
系统A通过地址栏获取ticket的参数值ST票据,然后从后台将ST发送给
CAS
认证中心验证,验证ST有效后,CAS
认证中心返回当前用户登录的相关信息,系统A接收到返回的用户信息,并为该用户创建session会话,会话id由cookie维护,来证明其已登录。
1.1.4、B系统登录
用户发送登录系统B的请求,首先会去
Cookie
中拿JSESSION
,因为系统B并未登录过,session会话还未创建,JSESSION
的值是拿不到的,然后将请求重定向到CAS认证中心
,CAS
认证中心先去用户浏览器中拿TGC
的值(用户已经登录过),也就是全局会话id,如果存在则代表用户在认证中心已经登录,附带上参数ticket(ST票据)认证令牌重定向到系统B。 B系统后台,验证ST票据,获取用户信息并成功登录创建会话
1.2、票据解释
1.2.1、TGT
(Ticket Grangting Ticket
)
TGT cookie 存储在关于服务器域名的cookie
TGT是CAS为用户签发的登录票据,拥有了TGT,用户就可以证明自己在CAS成功登录过。TGT封装了Cookie值以及此Cookie值对应的用户信息。
用户在CAS认证成功后,CAS生成cookie(叫TGC),写入浏览器,同时生成一个TGT对象,放入自己的缓存,TGT对象的ID就是cookie的值。
当HTTP再次请求到来时,如果传过来的有CAS生成的cookie,则CAS以此cookie值为key查询缓存中有无TGT ,如果有的话,则说明用户之前登录过,如果没有,则用户需要重新登录。
1.2.2、ST
(Service Ticket
)
ST是CAS为用户签发的访问某一客户端的票据。用户访问客户端时,客户端如果发现用户没有登录,则要求用户去CAS获取ST。用户向CAS发出获取ST的请求, 如果用户的请求中包含cookie,则CAS会以此cookie值为key查询缓存中有无TGT,
如果存在TGT,则用此TGT签发一个ST,返回给用户。用户凭借ST去访问service,service拿ST去CAS验证,验证通过后,允许用户访问资源。
如果不存在,则重定向到用户,要求用户登录后,再由服务端签发
1.3、问题
1.3.1、ST
和TGT
的过期策略是什么
1.3.1.1、ST
ST
的过期包括使用次数和时间,默认使用一次就过期,或者即使没有使用,一段时间后也要过期,st默认过期时间是10秒
1.3.1.2、TGT
TGT
是可以多次使用的,TGT
的默认过期时间是7200秒=2小时
1.3.2、客户端长时间不操作,就会重新登录?
那是因为我们的本地session过期了,正常情况下session的过期时间是30分钟,30分钟不操作就过期了,如果我们设置的session过期时间特别长会怎么办。那就一直可以操作啊,傻瓜,即使服务器过期了,客户端都不会过期。因为不需要再向服务端发起登录申请了
1.3.3、多个客户端jessionId
一样吗
当然是不一样的,每个客户端 服务器域名不一样,cookie就不能跨域,维持每个不同会话的sessionId
也是不一样的
2、单点登出
2.1、登出流程
1、浏览器请求 豆瓣读书的 /logout , 豆瓣读书 删除Session相关内容,即清除局部会话。
2、 豆瓣读书 引导浏览器重定向到 用户中心的 /logout,携带Cookie中的TGC
, 删除 Cookie中的TGC
,注销 TGC
对应的TGT
,清除全局会话,
3:、认证服务器 找到TGT
签发的ST票据
,即所有已登录的业务系统(豆瓣电影、音乐),在认知服务器后台,携带ST通知 所有有关的业务系统(Fire & Forget)清除局部会话
4: 业务系统 接到通知后,用ST 清除 对应的Session,即 清除对应的局部会话
2.2、登出原理
用户通过浏览器访问系统A
www.a.com/pageA
,这个pageA
是个需要登录才能访问的页面,系统A发现用户没有登录,这时候系统A需要做一个额外的操作,就是重定向到认证中心:www.sso.com/login?service=www.a.com/pageA
。这个
service
参数是一个回跳的url,认证完成后会重定向到系统A,另外有一个作用就是注册服务,简单来说注册服务为的是让我们的认证中心能够知道有哪些系统在我们这里完成过登录,其中一个重要目的是为了完成单点退出的功能。注册服务时保存一是回跳的service地址,二是对应的ticket。拿着ticket回跳到客户端后,客户端一看地址的请求参数有ticket的时候,
singleSignOutFilter
注册带有ST票据ticket作为id的session到sessionMappingStorage
。
###
@Bean
public FilterRegistrationBean singleSignOutFilter() {
FilterRegistrationBean filterRegistration = new FilterRegistrationBean();
filterRegistration.setFilter(new SingleSignOutFilter());
filterRegistration.addUrlPatterns("/*");
filterRegistration.addInitParameter("casServerUrlPrefix", casServerUrlPrefix);
filterRegistration.addInitParameter("serverName", casClientName);
filterRegistration.setOrder(3);
return filterRegistration;
}
当用户访问认证中心的 /logout 需要退出的时候,认证中心先把TGT
干掉,然后给之前注册过那些服务的地址发送退出登录的请求,并且携带之前登录的ticket,客户端一看请求中参数有logoutRequest
的时候,客户端的singleSignOutFilter
根据传过来的这个 ticket 来将对应的用户 session 干掉。
SingleSignOutFilter源码
public void doFilter(final ServletRequest servletRequest,
final ServletResponse servletResponse,
final FilterChain filterChain)
throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
//判断参数中是否具有artifactParameterName属性指定的参数名称,默认是ticket
if (handler.isTokenRequest(request)) {
// 如果存在,在本地sessionMappingStorage中记录sessionId。
handler.recordSession(request);
//判断是否具有logoutParameterName参数指定的参数,默认参数名称为logoutRequest
} else if (handler.isLogoutRequest(request)) {
// 如果存在,则在sessionMappingStorage中删除记录,并注销session。
handler.destroySession(request);
// 注销session后,立刻停止执行后面的过滤器
return;
} else {
log.trace("Ignoring URI " + request.getRequestURI());
}
//条件都不满足,继续执行下面的过滤器
filterChain.doFilter(servletRequest, servletResponse);
}
那么什么时候干掉sessionMappingStorage
呢?
(sessionMappingStorage
是一个map,key为ticket,value为sessionId)这是靠SingleSignOutHttpSessionListener
来实现的,当有session被注销的时候,触发将sessionMappingStorage
中对应的sessionId
中的数据删除,
所以在配置单点登出的时候,一定要配置这个监听器,否则客户端很容易导致内存溢出。
@Bean
public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() {
ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> listener = new ServletListenerRegistrationBean<>();
listener.setEnabled(true);
listener.setListener(new SingleSignOutHttpSessionListener());
listener.setOrder(1);
return listener;
}
public final class SingleSignOutHttpSessionListener implements HttpSessionListener {
private SessionMappingStorage sessionMappingStorage;
public void sessionCreated(final HttpSessionEvent event) {
// nothing to do at the moment
}
public void sessionDestroyed(final HttpSessionEvent event) {
if (sessionMappingStorage == null) {
sessionMappingStorage = getSessionMappingStorage();
}
final HttpSession session = event.getSession();
sessionMappingStorage.removeBySessionById(session.getId());
}
protected static SessionMappingStorage getSessionMappingStorage() {
return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage();
}
}