MyBlog


登录那些事

<p>[TOC]</p> <h1>简单登录</h1> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/a0d244d3f4c211408c91a5817c9e4920?showdoc=.jpg" alt="" /></p> <p>client:提供用户名和密码或者是其他的认证凭证</p> <p>server:验证client提供的认证凭证,记录登录状态</p> <p>过程:访问系统时,client必须输入用户名和密码,server进行验证,验证通过后server建立一个叫做session的东西,然后把sessionId通过cookie发送给浏览器,下次登录的时候会带着cookie一起发过来,server从cookie中拿到sessionId就知道已经登录啦。</p> <p>cookie和session的作用就是保持client和server的交互状态,这样一来可以将无状态的通信转化成有状态的交互,也就是让server有了“记忆”能力。</p> <h1>多系统登录</h1> <p>现在有三个服务,需要输入三次用户信息,但是如果有成百上千的服务呢?HOW???</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/7d56e1207a9ca2625b739dc23ce8cc05?showdoc=.jpg" alt="" /></p> <p>参考上面简单登录的原理:服务端如何有了“记忆”能力呢:在client和server之间引入了一个中间层(cookie或者是session)。(题外话:计算机世界的99%的问题都可以通过一个引入一个中间层来解决)</p> <p>所有我们可以想到一个可行的解决方案:对这个“中间层”做共享。</p> <p>比如说先登录了A,就有了一个cookie,然后在用cookie去登录其他的系统。但是这样明显是有问题的:</p> <p>1,cookie不能跨域,比如说a.com产生的cookie是不能传递给b.com的</p> <p>2,就算cookie可以传递,但是其他系统并没有session来校验这个cookie。</p> <p>解决方案:</p> <p>1.cookie做共享可以挂到同一个一级域名下,比如A叫a.corp.com,B叫b.corp.com,C叫c.corp.com,这样cookie不就能共享了嘛</p> <p>2,将session也做共享,从内存里面拿出来,放入一个大家都能访问的中间层里面,比如说redis。(题外话:又是中间层)</p> <p>像下面这样:</p> <p><img src="https://www.showdoc.cc/server/api/common/visitfile/sign/c4955614c4bf1d9f7e6746d9e5afdfe0?showdoc=.jpg" alt="" /></p> <p>可以解决多系统登录的问题么?可以解决!!! 但是</p> <p>1,要共享cookie就必须让所有的系统都在同一个域名下</p> <p>2,多用户账号的存在。比如张三登录了A,然后去登录B,通过这种方式的话B需要去校验张三这个用户的合法性,此张三可能未必是彼张三!各个业务系统必须自己去维护自己系统的用户,而且用户信息还不止一个。</p> <p>HOW????</p> <h1>SSO</h1> <p>Single Sign One :单点登录</p> <p>消除多用户信息的问题,不用各个业务系统自己去维护用户信息,建立一个统一的用户认证中心(中间层:又是我哈哈哈),所有用户的认证工作都交给这个认证中心来完成。各个子业务只需要负责实现自己的业务即可,不在需要关心用户、认证等细节。</p> <p>大致流程如下: <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/c25c13ba7dc6dc2c847e5bc4b921ba3c?showdoc=.jpg" alt="" /></p> <p>用户第一次访问A系统:www.a.corp.com,这个时候如果A发现用户没有登录的话,那他需要做的一项操作就是重定向认证中心:www.sso.com/login?redirect=www.a.corp.com。</p> <p>其中www.sso.com/login就是认证中心的登录地址,redirect=www.a.corp.com就是登录完成后需要跳转到的地址。在认证中心登录之后认证中心会做以下几件事情:</p> <p>1,建立一个session</p> <p>2,发放ticket</p> <p>3,重定向到你的地址,并在浏览器种下cookie信息。注意这个cookie是认证中心服务器的cookie哦。如:ssoid=1,domain=sso.com</p> <p>需要注意的有两点:</p> <p>1,进行了两次重定向。第一次是重定向到SSO的服务器,第二次是重定向我们的后端服务器。</p> <p>2,流程完成后再浏览器种了两个cookie,一个是sso服务器的cookie,一个是子系统A的cookie。</p> <p>接下来我们访问B系统: <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/da9a9a830926c7e3c7c140c10a634863?showdoc=.jpg" alt="" /></p> <p>这个时候会有三个cookie:</p> <p>1,认证中心的cookie domain: sso.com </p> <p>2,A系统的cookie domain: a.corp.com</p> <p>3, B系统的cookie domain: b.corp.com</p> <p>本质上就是保存了一个认证中心的cookie和多个子系统的cookie。一般我们称SSO的cookie的对应的会话成为全局会话,各自子系统cookie对应的会话称为局部会话。</p> <h1>解决方案(CAS)</h1> <p>概述</p> <p>CAS(Central Authentication Service) 是 Yale 大学发起的一个企业级的、开源的项目,旨在为 Web 应用系统提供一种可靠的单点登录解决方法(属于 Web SSO)。</p> <p>CAS 开始于 2001 年, 并在 2004 年 12 月正式成为 JA-SIG 的一个项目。</p> <p>特性</p> <p>1、 开源的、多协议的 SSO 解决方案;Protocols:Custom Protocol、CAS、OAuth、OpenID、RESTful API、SAML1.1、SAML2.0 等。</p> <p>2、 支持多种认证机制:Active Directory、JAAS、JDBC、LDAP、X.509 Certificates 等;</p> <p>3、 安全策略:使用票据(Ticket)来实现支持的认证协议;</p> <p>4、 支持授权:可以决定哪些服务可以请求和验证服务票据(Service Ticket);</p> <p>5、 提供高可用性:通过把认证过的状态数据存储在 TicketRegistry 组件中,这些组件有很多支持分布式环境的实现,如:BerkleyDB、Default 、EhcacheTicketRegistry、JDBCTicketRegistry、JBOSS TreeCache、JpaTicketRegistry、MemcacheTicketRegistry 等;</p> <p>6、 支持多种客户端: Java、 .Net、 PHP、 Perl、 Apache, uPortal 等。</p> <p>体系结构 </p> <p>从结构上看,CAS 包含两个部分:CAS Server 和 CAS Client,CAS 需要独立部署,主要负责对用户的认证工作,CAS Server 会处理用户名 / 密码等凭证 (Credentials)。</p> <p>负责处理对客户端受保护资源的访问请求,需要对请求方进行身份认证时,重定向到 CAS Server 进行认证。CAS Client一般与 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。过滤从客户端过来的每一个 Web 请求,同时, CAS Client 会分析 HTTP 请求中是否包请求 Service Ticket</p> <p>术语</p> <p>Ticket Granting ticket (TGT) :可以认为是CAS Server根据用户名密码生成的一张票,存在server端</p> <p>Ticket-granting cookie (TGC) :其实就是一个cookie,存放用户身份信息,由server发给client端</p> <p>Service ticket (ST) :由TGT生成的一次性票据,用于验证,只能用一次。相当于server发给client一张票,然后client拿着这是个票再来找server验证,看看是不是server签发的。就像是我给了你一张我的照片,然后你拿照片再来问我,这个照片是不是你。。。没错,就是这么无聊。</p> <p>安全性</p> <p>TGC安全性:</p> <p>对于一个 CAS 用户来说,最重要是要保护它的 TGC,如果 TGC 不慎被 CAS Server 以外的实体获得,Hacker 能够找到该 TGC,然后冒充 CAS 用户访问所有授权资源。从基础模式可以看出, TGC 是 CAS Server 通过 SSL 方式发送给终端用户,因此,要截取 TGC 难度非常大,从而确保 CAS 的安全性。TGT 的存活周期默认为 120 分钟。</p> <p>ST安全性:</p> <p>ST(Service Ticket)是通过 Http 传送的,因此网络中的其他人可以 Sniffer 到其他人的 Ticket。CAS 通过以下几方面来使 ST 变得更加安全:</p> <p>1、 ST 只能使用一次</p> <p>CAS 协议规定,无论 Service Ticket 验证是否成功, CAS Server 都会清除服务端缓存中的该 Ticket,从而可以确保一个 Service Ticket 不被使用两次。</p> <p>2、 ST 在一段时间内失效</p> <p>CAS 规定 ST 只能存活一定的时间,然后 CAS Server 会让它失效。默认有效时间为 5 分钟。</p> <p>3、 ST 是基于随机数生成的</p> <p>ST 必须足够随机,如果 ST 生成规则被猜出,Hacker 就等于绕过 CAS 认证,直接访问对应的服务。</p> <p>流程 <img src="https://www.showdoc.cc/server/api/common/visitfile/sign/827fc89e6d3a8d6b9dbb0d08bc350c6d?showdoc=.jpg" alt="" /></p> <p>上图是3个登录场景,分别为:第一次访问www.qiandu.com、第二次访问、以及登录状态下第一次访问mail.qiandu.com。</p> <p>第一次访问www.qiandu.com</p> <p>标号1:用户访问http://www.qiandu.com,经过他的第一个过滤器:org.jasig.cas.client.authentication.AuthenticationFilter(cas提供,在web.xml中配置)。主要作用:判断是否登录,如果没有登录则重定向到认证中心</p> <p>标号2:www.qiandu.com发现用户没有登录,则返回浏览器重定向地址。</p> <p>首先可以看到我们请求www.qiandu.com,之后浏览器返回状态码302,然后让浏览器重定向到cas.qiandu.com并且通过get的方式添加参数service,该参数目的是登录成功之后会要重定向回来,因此需要该参数。并且你会发现,其实server的值就是编码之后的我们请求www.qiandu.com的地址。</p> <p>标号3:浏览器接收到重定向之后发起重定向,请求cas.qiandu.com。</p> <p>标号4:认证中心cas.qiandu.com接收到登录请求,返回登陆页面。</p> <p>上图就是标号3的请求,以及标号4的响应。请求的URL是标号2返回的URL。之后认证中心就展示登录的页面,等待用户输入用户名密码。</p> <p>标号5:用户在cas.qiandu.com的login页面输入用户名密码,提交。</p> <p>标号6:服务器接收到用户名密码,则验证是否有效,验证逻辑可以使用cas-server提供现成的,也可以自己实现。</p> <p>上图就是标号5的请求,以及标号6的响应了。当cas.qiandu.com即csa-server认证通过之后,会返回给浏览器302,重定向的地址就是Referer中的service参数对应的值。后边并通过get的方式挟带了一个ticket令牌,这个ticket就是ST(数字3处)。同时会在Cookie中设置一个CASTGC,该cookie是网站cas.qiandu.com的cookie,只有访问这个网站才会携带这个cookie过去。</p> <p>Cookie中的CASTGC:向cookie中添加该值的目的是当下次访问cas.qiandu.com时,浏览器将Cookie中的TGC携带到服务器,服务器根据这个TGC,查找与之对应的TGT。从而判断用户是否登录过了,是否需要展示登录页面。TGT与TGC的关系就像SESSION与Cookie中SESSIONID的关系。点击这里了解Java如何操作Cookie。</p> <p>TGT:Ticket Granted Ticket(俗称大令牌,或者说票根,他可以签发ST)</p> <p>TGC:Ticket Granted Cookie(cookie中的value),存在Cookie中,根据他可以找到TGT。</p> <p>ST:Service Ticket (小令牌),是TGT生成的,默认是用一次就生效了。也就是上面数字3处的ticket值。</p> <p>标号7:浏览器从cas.qiandu.com哪里拿到ticket之后,就根据指示重定向到www.qiandu.com,请求的url就是上面返回的url。</p> <p>标号8:www.qiandu.com在过滤器中会取到ticket的值,然后通过http方式调用cas.qiandu.com验证该ticket是否是有效的。</p> <p>标号9:cas.qiandu.com接收到ticket之后,验证,验证通过返回结果告诉www.qiandu.com该ticket有效。</p> <p>标号10:www.qiandu.com接收到cas-server的返回,知道了用户合法,展示相关资源到用户浏览器上。</p> <p>第二次访问www.qiandu.com</p> <p>标号11:用户发起请求,访问www.qiandu.com。会经过cas-client,也就是过滤器,因为第一次访问成功之后www.qiandu.com中会在session中记录用户信息,因此这里直接就通过了,不用验证了。</p> <p>标号12:用户通过权限验证,浏览器返回正常资源。</p> <p>访问mail.qiandu.com</p> <p>标号13:用户在www.qiandu.com正常上网,突然想访问mail.qiandu.com,于是发起访问mail.qiandu.com的请求。</p> <p>标号14:mail.qiandu.com接收到请求,发现第一次访问,于是给他一个重定向的地址,让他去找认证中心登录。</p> <p>上图可以看到,用户请求mail.qiandu.com,然后返回给他一个网址,状态302重定向,service参数就是回来的地址。</p> <p>标号15:浏览器根据14返回的地址,发起重定向,因为之前访问过一次了,因此这次会携带上次返回的Cookie:TGC到认证中心。</p> <p>标号16:认证中心收到请求,发现TGC对应了一个TGT,于是用TGT签发一个ST,并且返回给浏览器,让他重定向到mail.qiandu.com</p> <p>可以发现请求的时候是携带Cookie:CASTGC的,响应的就是一个地址加上TGT签发的ST也就是ticket。</p> <p>标号17:浏览器根据16返回的网址发起重定向。</p> <p>标号18:mail.qiandu.com获取ticket去认证中心验证是否有效。</p> <p>标号19:认证成功,返回在mail.qiandu.com的session中设置登录状态,下次就直接登录。</p> <p>标号20:认证成功之后就反正用想要访问的资源了。</p> <p>Demo演示 配置filter</p> <p>我们需要在应用的web.xml文件中配置四个Filter,这四个Filter必须按照固定的顺序来进行配置,而且它们必须配置在应用的其它Filter之前。它们的先后顺序要求如下:</p> <p>1、AuthenticationFilter</p> <p>casServerLoginUrl用来指定Cas Server登录地址,serverName或service用来指定认证成功后需要跳转地址。</p> <p>2、TicketValidationFilter</p> <pre><code> 请求通过AuthenticationFilter的认证之后,如果请求中携带了参数ticket则将会由TicketValidationFilter来对携带的ticket进行校验。 TicketValidationFilter只是对验证ticket的这一类Filter的统称,其并不对应Cas Client中的一个具体类型。Cas Client中有多种验证ticket的Filter,</code></pre> <p>都继承自AbstractTicketValidationFilter,它们的验证逻辑都是一致的,都有AbstractTicketValidationFilter实现,不同的是使用的TicketValidator不一样。这里我们使用Cas10TicketValidationFilter,也可以使用Cas20ProxyReceivingTicketValidationFilter或Saml11TicketValidationFilter。</p> <p>3、HttpServletRequestWrapperFilter</p> <p>HttpServletRequestWrapperFilter用于将每一个请求对应的HttpServletRequest封装为其内部定义的CasHttpServletRequestWrapper,该封装类将利用之前保存在Session或request中的Assertion对象重写HttpServletRequest的getUserPrincipal()、getRemoteUser()和isUserInRole()方法。</p> <pre><code> 这样在我们的应用中就可以非常方便的从HttpServletRequest中获取到用户的相关信息</code></pre> <p>4、AssertionThreadLocalFilter</p> <p>AssertionThreadLocalFilter可以在应用的其它地方获取Assertion对象,找个过滤器会把Assertion对象存放到当前的线程变量中,我们在程序的任何地方都可以从线程变量中获取当前Assertion,就不需要再从Session或request中进行解析了。这个线程变量是由AssertionHolder持有的,我们在获取当前的 Assertion时也只需要通过AssertionHolder的getAssertion()方法获取即可</p>

页面列表

ITEM_HTML