Shiro单点登录过程和重定向问题分析

Shiro单点登录过程和重定向问题分析

[if !supportLists]1.  [endif]技术背景

单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

1.1 Session

Http协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session的主要目的就是为了弥补Http的无状态特性,服务器可以利用session存储客户端在同一个会话期间的一些操作记录;

会话机制:浏览器第一次请求服务器,服务器创建一个会话,并将会话的id作为响应的一部分发送给浏览器,浏览器存储会话id,并在后续第二次和第三次请求中带上会话id,服务器取得请求中的会话id判断是否是同一个用户。

1.1.1 session保存方式

浏览器端:存放在浏览器cookie中,生命周期是session,当浏览器关闭时cookie会消失。(session劫持)。

Tomcat session 以CurrentHashMap的数据结构存储。

tomcat中多个会话对应的session是由ManagerBase类来维护,查看其代码,可以发现其有一个sessions成员属性,存储着各个会话的session信息:

    /**

     * The set of currently activeSessions for this Manager, keyed by

     * session identifier.

     */

    protected Map sessions = new ConcurrentHashMap();

1.1.2 Session的生成

Tomcat

session生成器的类实现,SessionIdGeneratorBase,实现代码如下


public String generateSessionId() {

    return this.generateSessionId(this.jvmRoute);

}


public String generateSessionId(String route) {

    byte[] random =new byte[16];

    int sessionIdLength =this.getSessionIdLength();

    StringBuilder buffer =new StringBuilder(2 * sessionIdLength + 20);

    int resultLenBytes = 0;

    while(resultLenBytes < sessionIdLength) {

        this.getRandomBytes(random);

        for(int j = 0; j < random.length && resultLenBytes < sessionIdLength; ++j) {

            byte b1 = (byte)((random[j] & 240) >> 4);

            byte b2 = (byte)(random[j] & 15);

            if (b1 < 10) {

                buffer.append((char)(48 + b1));

            }else {

                buffer.append((char)(65 + (b1 - 10)));

            }

            if (b2 < 10) {

                buffer.append((char)(48 + b2));

            }else {

                buffer.append((char)(65 + (b2 - 10)));

            }

            ++resultLenBytes;

        }

    }

    if (route !=null && route.length() > 0) {

        buffer.append('.').append(route);

    }else {

        String jvmRoute =this.getJvmRoute();

        if (jvmRoute !=null && jvmRoute.length() > 0) {

            buffer.append('.').append(jvmRoute);

        }

    }

    return buffer.toString();

}

1.1.3 session

ID生产算法

Random类中实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字,当种子可知时,随机数也是可知的。

[if !supportLists]·        [endif]SecureRandom类提供加密的强随机数生成器(RNG)

使用的随机数生成工具SecureRandom工具生成随机数。

protected void getRandomBytes(byte[] bytes) {

    SecureRandom random = (SecureRandom)this.randoms.poll();

    if (random ==null) {

        random =this.createSecureRandom();

    }

    random.nextBytes(bytes);

    this.randoms.add(random);

}


SecureRandom的实现算法,NativePRNG,否则就是SHA1PRNG。

操作系统收集了一些随机事件,比如鼠标点击,键盘点击等等,SecureRandom 使用这些随机事件作为种子,从而使得种子不可预测。


1.2 单系统登录实现。

1.2.1 会话维护

会话机制中,浏览器第一次请求服务器需要输入用户名与密码验证身份,服务器拿到用户名密码去数据库比对,验证通过则说明当前持有这个会话的用户是合法用户,应该将这个会话标记为“已授权”或者“已登录”等等之类的状态,并将信息保存在session中,tomcat在会话对象中设置登录状态如下

1

2

HttpSession session = request.getSession();

session.setAttribute("isLogin", true);

用户再次访问时,tomcat在会话对象中查看登录状态

1

2

HttpSession session = request.getSession();

session.getAttribute("isLogin");

1.2.2 处理过程

实现了登录状态的浏览器请求服务器模型如下图描述

1.3 多系统情况下登录实现。

1.3.1 同域名下session共享的方式。

利用cookie的特性和session共享实现单点登录。但是无法实现跨域的情况。


sso登录后,可以将Cookie的域设置为顶域,例如.a.com,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie。这样所有的子系统都会带上该cookie。

多服务器的Sesssion共享实现cookie session的统一验证。

在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端把系统的Session共享。共享Session的解决方案有很多,例如:Spring-Session。

1.3.2 CAS实现单点登录。CentralAuthentication Service

CAS是Central

Authentication Service的简称,统一认证中心。CAS认证协议定义了统一认证的交互过程,同时CAS发行了3个版本。

V1实现单点登录。

V2增加代理模式,代理模式是一种更复杂形式的认证,即认证的Web应用(CAS Client)可以作为代理直接访问需要认证的后端服务(如邮件服务器),浏览器用户无需再和后端服务直接进行认证交互。

v3版本则更加丰富了协议,认证中心验证票据后不仅可以返回身份ID,还可携带用户昵称、性别等其他用户基本信息。


单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分。

相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理。

[if !supportLists]1)  [endif]登录过程

相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点登录的原理,用下图说明。

下面对上图简要描述

[if !supportLists]1)  [endif]用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数

[if !supportLists]2)  [endif]sso认证中心发现用户未登录,将用户引导至登录页面

[if !supportLists]3)  [endif]用户输入用户名密码提交登录申请

[if !supportLists]4)  [endif]sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌

[if !supportLists]5)  [endif]sso认证中心带着令牌跳转会最初的请求地址(系统1)

[if !supportLists]6)  [endif]系统1拿到令牌,去sso认证中心校验令牌是否有效

[if !supportLists]7)  [endif]sso认证中心校验令牌,返回有效,注册系统1

[if !supportLists]8)  [endif]系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源

[if !supportLists]9)  [endif]用户访问系统2的受保护资源

[if !supportLists]10)[endif]系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数

[if !supportLists]11)[endif]sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌

[if !supportLists]12)[endif]系统2拿到令牌,去sso认证中心校验令牌是否有效

[if !supportLists]13)[endif]sso认证中心校验令牌,返回有效,注册系统2

[if !supportLists]14)[endif]系统2使用该令牌创建与用户的局部会话,返回受保护资源

用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系

[if !supportLists]1)  [endif]局部会话存在,全局会话一定存在

[if !supportLists]2)  [endif]全局会话存在,局部会话不一定存在

[if !supportLists]3)  [endif]全局会话销毁,局部会话必须销毁

问题点:

SSO系统登录后,跳回原业务系统时,带了个参数ST,业务系统还要拿ST再次访问SSO进行验证,觉得这个步骤有点多余。他想SSO登录认证通过后,通过回调地址将用户信息返回给原业务系统,原业务系统直接设置登录状态,这样流程简单,也完成了登录,不是很好吗?

如果在SSO没有登录,而是直接在浏览器中敲入回调的地址,并带上伪造的用户信息,业务系统也认为登录,会产生安全问题。

[if !supportLists]2)  [endif]注销过程

单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明

sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作。

下面对上图简要说明

[if !supportLists]1)  [endif]用户向系统1发起注销请求

[if !supportLists]2)  [endif]系统1根据用户与系统1建立的会话id拿到令牌,向sso认证中心发起注销请求

[if !supportLists]3)  [endif]sso认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址

[if !supportLists]4)  [endif]sso认证中心向所有注册系统发起注销请求

[if !supportLists]5)  [endif]各注册系统接收sso认证中心的注销请求,销毁局部会话

[if !supportLists]6)  [endif]sso认证中心引导用户至登录页面

1.3.3 Shiro实现会话管理和CAS验证

Shiro中同时已经存在了 cas 认证中心,shiro 官方在 1.2 中就表明已经弃用了CasFilter ,建议使用 buji-pac4j ,使用pac4j 来做单点登录的控制,贷款服务目前使用的CasFilter。

1)登录filter, AccessControlFilter

2)登录授权,验证ticket,CasFilter 。

3)登录信息存储,使用redis统一存储登录信息,key是ticket,value是shiro session。

RedisSessionDAO redisSessionDAO =new RedisSessionDAO();

redisSessionDAO.setRedisManager(redisManager());

DefaultWebSessionManager sessionManager =new DefaultWebSessionManager();

//

设置自定义cookiesessionManager.setSessionIdCookie(simpleCookie());

sessionManager.setSessionDAO(redisSessionDAO);


2 贷款服务单点登录应用

2.1 全局会话TGT

所有的系统应用都会引导到CAS Server认证中心去登录。登录成功后,认证中心会产生一个票据叫TGT(Ticket Granting Ticket),TGT即代表了用户与认证中心直接的全局会话。TGT存在,表明该用户处于登录状态。

TGT并没有放在Session中,也就是说,CAS全局会话的实现并没有直接使用Session机制,而是利用了Cookie自己实现的,这个Cookie叫做TGC(Ticket Granting Cookie),它存放了TGT的id,认证中心服务端实现了TGT。



请求的时候带上TGC ,验证全局会话。如果删除了cookie TGC,那么需要重新登录。

2.2 局部会话shiro session

局部会话,第一次验证session时,在cookie中设置loan_session。


3 当前问题及分析

3.1 重定向问题。

3.1.1

Cookie取值

Tomcat有默认的sessionID, JSESSIONID。Shiro在不自定义名称的时候,默认session的cookie名称也是,JSESSIONID。导致存在两个重名cookie name。

Shiro的session域 是.loan.jiaoyi.ke.com 而 tomcat的cookie域是 loan.jiaoyi.ke.com。在域不同的情况下,同名cookie是可以共存的。

登录的时候cookie顺序是这样的,shiro可以取到正确的jsessionId。登出重新登录后,顺序相反,导致取到错误的JSESSIONID。


Cookie取值逻辑是按名称,取匹配的第一个。取值逻辑。

private static javax.servlet.http.Cookie

getCookie(HttpServletRequest request, String cookieName) {

javax.servlet.http.Cookie

cookies[] = request.getCookies();

if (cookies !=null){

for (javax.servlet.http.Cookie

cookie : cookies) {

if (cookie.getName().equals(cookieName))

{

return cookie;

}

}

}

return null;

}

3.1.2 重定向循环

当取到错误的sessionid验证时,验证失败。局部回话验证失败,浏览器重定向CAS服务器,CAS验证通过,返回ticket到贷款服务,贷款服务ticket验证通过,重新设计shiro会话session,浏览器重新访问贷款服务,但是session还是取值错误,所以无限循环重定向,导致报错。

4 解决方案

修改shiro的session名称,使得shiro

session取值正确,解决重定向问题。

升级shiro版本,添加配置,解决了线上重定向丢失参数问题,参数丢失的原因还无法确认,在测试环境没法复现问题。sessionManager.setSessionIdUrlRewritingEnabled(false);

@Bean

public SimpleCookie simpleCookie(){

    SimpleCookie simpleCookie =new SimpleCookie();

    simpleCookie.setName("loan_session");

    simpleCookie.setHttpOnly(true);

    return simpleCookie;

}


DefaultWebSessionManager sessionManager =new DefaultWebSessionManager();

//

设置自定义cookiesessionManager.setSessionIdCookie(simpleCookie());

sessionManager.setSessionIdUrlRewritingEnabled(false);

5 分析工具

5.1 远程调试

使用远程调试可以清晰的分析整个登录的处理和验证过程。

在jar包启动脚本中也配置,

jdwp=transport=dt_socket,server=y,suspend=n,address=38115


5.2 抓包工具fidder

使用fidder抓包,分析浏览器的请求过程。

[if !supportLists]6    [endif]引用

单点登录原理及简单实现。https://www.cnblogs.com/scode2/p/8671073.html

Tomcat

session 产生。https://www.cnblogs.com/chenpi/p/5434537.html

SecureRandom简介,https://www.cnblogs.com/deng-cc/p/8064481.html

Java随机工具,https://www.jianshu.com/p/2f6acd169202

单点登录看这一篇。https://yq.aliyun.com/articles/636281

CAS实现单点登录,

https://blog.csdn.net/javaloveiphone/article/details/52439613

CAS协议 http://www.imooc.com/article/3720

你可能感兴趣的:(Shiro单点登录过程和重定向问题分析)