900字范文,内容丰富有趣,生活中的好帮手!
900字范文 > CAS解决单点登录SSO

CAS解决单点登录SSO

时间:2023-02-12 00:36:48

相关推荐

CAS解决单点登录SSO

关于CAS很多的原理和基础的配置启动,网上是很多的,我更多是结合我的实践和心得。需要了解CAS的原理,认证协议,认证流程,可以参考以下文章。

让CAS支持客户端自定义登陆页面——客户端篇

CAS原理与配置-基于CAS的单点登陆的研究(上)

服务端配置

CAS单点登陆部署

CAS配置手册

CAS单点登录配置

背景

单点登录(SSO)是企业开发的重要问题,在我的毕设项目中,由于要和系统其他开发模块共用用户认证模块,方便共享用户资源,所以需要一个好的SSO解决方案。

一般SSO的实现机制有两种:基于session的和基于cookie的。WebLogic通过Session共享认证信息。Session是一种服务器端机制,当客户端访问服务器时,服务器为客户端创建一个惟一的SessionID,以使在整个交互过程中始终保持状态,而交互的信息则可由应用自行指定,因此用Session方式实现SSO,不能在多个浏览器之间实现单点登录,但却可以跨域;WebSphere通过Cookie记录认证信息。Cookie是一种客户端机制,它存储的内容主要包括: 名字、值、过期时间、路径和域,路径与域合在一起就构成了Cookie的作用范围,因此用Cookie方式可实现SSO,但域名必须相同。对应这两者,开源的SSO实现分别是OAuth和CAS。

OAuth更多的是解决第三方去访问服务提供方的用户的资源,我认为更适用于不同的系统,比如大的平台都会提供OAuth的认证机制(新浪微博,google)。而CAS更贴近我的需求,就是解决同一系统下不同服务间的用户认证工作,可以无缝连接。

关于CAS

CAS是Yale大学开源项目,是企业级单点登录解决方案。是部署在Tomcat上的独立的web应用。CAS-Server是单独部署的认证服务器,客户端即各服务开发者需要使用CAS-Client包。为了认证的安全性,CAS-server需要Tomcat开启SSL的https端口,并生成,导入和导出证书(一般可以用jre的keytool生成)。其实也可以取消cas的https。CAS是一个基于Spring框架开发的东西,下载来的war包放到tomcat的webapp里就可以用。 往往这样的开源工具的特点就是,配置比较复杂,或者说基础配置是简单的,进阶配置是很麻烦的,你可能要去看源码,要去研究很多细小的东西,这个和solr一样。而显然他的优点就是定制和扩展。

我的CAS配置和实践 我使用的是cas-server-3.4.10和cas-client-3.2.1。先说cas-server-3.4.10的一些配置。deployerConfigContext.xml里,一般我们需要添加的是:使用我们自己的身份验证方式;获取更多用户信息放到session里让客户端获得。我对deployerConfigContext.xml的修改是使用jdbc连接mysql认证,并且返回除了username外其他的email等信息,

<?xml version="1.0" encoding="UTF-8"?><!--| deployerConfigContext.xml centralizes into one file some of the declarative configuration that| all CAS deployers will need to modify.|| This file declares some of the Spring-managed JavaBeans that make up a CAS deployment. | The beans declared in this file are instantiated at context initialization time by the Spring | ContextLoaderListener declared in web.xml. It finds this file because this| file is among those declared in the context parameter "contextConfigLocation".|| By far the most common change you will need to make in this file is to change the last bean| declaration to replace the default SimpleTestUsernamePasswordAuthenticationHandler with| one implementing your approach for authenticating usernames and passwords.+--><beans xmlns="/schema/beans"xmlns:xsi="/2001/XMLSchema-instance"xmlns:p="/schema/p"xmlns:sec="/schema/security"xsi:schemaLocation="/schema/beans /schema/beans/spring-beans-3.0.xsd/schema/security /schema/security/spring-security-3.0.xsd"><!--| This bean declares our AuthenticationManager. The CentralAuthenticationService service bean| declared in applicationContext.xml picks up this AuthenticationManager by reference to its id, | "authenticationManager". Most deployers will be able to use the default AuthenticationManager| implementation and so do not need to change the class of this bean. We include the whole| AuthenticationManager here in the userConfigContext.xml so that you can see the things you will| need to change in context.+--><bean id="authenticationManager"class="org.jasig.cas.authentication.AuthenticationManagerImpl"><!--| This is the List of CredentialToPrincipalResolvers that identify what Principal is trying to authenticate.| The AuthenticationManagerImpl considers them in order, finding a CredentialToPrincipalResolver which | supports the presented credentials.|| AuthenticationManagerImpl uses these resolvers for two purposes. First, it uses them to identify the Principal| attempting to authenticate to CAS /login . In the default configuration, it is the DefaultCredentialsToPrincipalResolver| that fills this role. If you are using some other kind of credentials than UsernamePasswordCredentials, you will need to replace| DefaultCredentialsToPrincipalResolver with a CredentialsToPrincipalResolver that supports the credentials you are| using.|| Second, AuthenticationManagerImpl uses these resolvers to identify a service requesting a proxy granting ticket. | In the default configuration, it is the HttpBasedServiceCredentialsToPrincipalResolver that serves this purpose. | You will need to change this list if you are identifying services by something more or other than their callback URL.+--><property name="credentialsToPrincipalResolvers"><list><!--| UsernamePasswordCredentialsToPrincipalResolver supports the UsernamePasswordCredentials that we use for /login | by default and produces SimplePrincipal instances conveying the username from the credentials.| | If you've changed your LoginFormAction to use credentials other than UsernamePasswordCredentials then you will also| need to change this bean declaration (or add additional declarations) to declare a CredentialsToPrincipalResolver that supports the| Credentials you are using.+--><beanclass="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" ><property name="attributeRepository" ref="attributeRepository" /></bean><!--| HttpBasedServiceCredentialsToPrincipalResolver supports HttpBasedCredentials. It supports the CAS 2.0 approach of| authenticating services by SSL callback, extracting the callback URL from the Credentials and representing it as a| SimpleService identified by that callback URL.|| If you are representing services by something more or other than an HTTPS URL whereat they are able to| receive a proxy callback, you will need to change this bean declaration (or add additional declarations).+--><beanclass="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" /></list></property><!--| Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate, | AuthenticationHandlers actually authenticate credentials. Here we declare the AuthenticationHandlers that| authenticate the Principals that the CredentialsToPrincipalResolvers identified. CAS will try these handlers in turn| until it finds one that both supports the Credentials presented and succeeds in authenticating.+--><property name="authenticationHandlers"><list><!--| This is the authentication handler that authenticates services by means of callback via SSL, thereby validating| a server side SSL certificate.+--><bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"p:httpClient-ref="httpClient" p:requireSecure="false" /><!--| This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS | into production. The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials| where the username equals the password. You will need to replace this with an AuthenticationHandler that implements your| local authentication strategy. You might accomplish this by coding a new such handler and declaring| edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules.+--><!--<beanclass="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />--><bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"><property name="dataSource" ref="dataSource"></property><property name="sql" value="select password from academic_user where username=?"></property><!--<property name="passwordEncoder" ref="MD5PasswordEncoder"></property>--></bean></list></property></bean><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property><property name="url"><value>jdbc:mysql://localhost:3307/academic</value></property><property name="username"><value>root</value></property><property name="password"><value></value></property></bean><!--<bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"><constructor-arg index="0"><value>MD5</value></constructor-arg></bean>--><!--This bean defines the security roles for the Services Management application. Simple deployments can use the in-memory version.More robust deployments will want to use another option, such as the Jdbc version.The name of this should remain "userDetailsService" in order for Spring Security to find it.--><!-- <sec:user name="@@THIS SHOULD BE REPLACED@@" password="notused" authorities="ROLE_ADMIN" />--><sec:user-service id="userDetailsService"><sec:user name="@@THIS SHOULD BE REPLACED@@" password="notused" authorities="ROLE_ADMIN" /></sec:user-service><!-- Bean that defines the attributes that a service may return. This example uses the Stub/Mock version. A real implementationmay go against a database or LDAP server. The id should remain "attributeRepository" though.--><bean id="attributeRepository"class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao"><constructor-arg index="0" ref="dataSource"/><constructor-arg index="1" value="select * from academic_user where {0}"/><property name="queryAttributeMapping"><map><entry key="username" value="username" /></map></property><property name="resultAttributeMapping"><map><entry key="username" value="username"/><entry key="name" value="name"/><entry key="email" value="email"/></map></property></bean><!-- Sample, in-memory data store for the ServiceRegistry. A real implementationwould probably want to replace this with the JPA-backed ServiceRegistry DAOThe name of this bean should remain "serviceRegistryDao".--><beanid="serviceRegistryDao"class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl"><!--<property name="registeredServices"><list><bean class="org.jasig.cas.services.RegisteredServiceImpl"><property name="id" value="0" /><property name="name" value="HTTP" /><property name="description" value="Only Allows HTTP Urls" /><property name="serviceId" value="http://**" /></bean><bean class="org.jasig.cas.services.RegisteredServiceImpl"><property name="id" value="1" /><property name="name" value="HTTPS" /><property name="description" value="Only Allows HTTPS Urls" /><property name="serviceId" value="https://**" /></bean><bean class="org.jasig.cas.services.RegisteredServiceImpl"><property name="id" value="2" /><property name="name" value="IMAPS" /><property name="description" value="Only Allows HTTPS Urls" /><property name="serviceId" value="imaps://**" /></bean><bean class="org.jasig.cas.services.RegisteredServiceImpl"><property name="id" value="3" /><property name="name" value="IMAP" /><property name="description" value="Only Allows IMAP Urls" /><property name="serviceId" value="imap://**" /></bean></list></property>--></bean><bean id="auditTrailManager" class="com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" /></beans>

注意上面一些我注释掉的地方和添加的地方,我就不一一指出了,有什么问题可以私下再问我。

在客户端使用cas的时候,需要把cas-client的包导入web project/WEB-INF/lib里,需要什么包就用maven去打包特定的包。最关键的是web.xml文件里对于filter的一些设定。在这些设定里包括了cas的login和logout这俩最基础的功能,还有一个很重要的是cas的validation。如果validation成功,cas会在session里返回用户名,而我在上面的xml里还加入了别的用户信息,这些东西会在validation成功之后写入session里,以xml的形式放着,我们可以用自己写的AutoSetUserAdapterFilter来得到。下面是web.xml的配置,

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="/2001/XMLSchema-instance"xmlns="/xml/ns/javaee" xmlns:web="/xml/ns/javaee/web-app_2_5.xsd"xsi:schemaLocation="/xml/ns/javaee /xml/ns/javaee/web-app_2_5.xsd"id="WebApp_ID" version="2.5"><display-name>AcademicSearchEngine</display-name><welcome-file-list><welcome-file>home.jsp</welcome-file></welcome-file-list><filter><filter-name>struts2</filter-name><filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class></filter><filter-mapping><filter-name>struts2</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 --><listener><listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class></listener><!-- 该过滤器用于实现单点登出功能,可选配置。 --><filter><filter-name>CAS Single Sign Out Filter</filter-name><filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class></filter><filter-mapping><filter-name>CAS Single Sign Out Filter</filter-name><url-pattern>/share.jsp</url-pattern></filter-mapping><!-- 该过滤器负责用户的认证工作,必须启用它 --><filter><filter-name>CAS Authentication Filter</filter-name><filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class><init-param><param-name>casServerLoginUrl</param-name><param-value>http://dcd.academic:8443/cas/login</param-value></init-param><init-param><!--这里的server是服务端的IP --><param-name>serverName</param-name><param-value>http://dcd.academic:8080</param-value></init-param><init-param><param-name>renew</param-name><param-value>false</param-value></init-param><init-param><param-name>gateway</param-name><param-value>false</param-value></init-param></filter><filter-mapping><filter-name>CAS Authentication Filter</filter-name><url-pattern>/share.jsp</url-pattern></filter-mapping><filter><filter-name>CAS Validation Filter</filter-name><filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class><init-param><param-name>casServerUrlPrefix</param-name><param-value>http://dcd.academic:8443/cas</param-value></init-param><init-param><param-name>serverName</param-name><param-value>http://dcd.academic:8080</param-value></init-param><init-param><param-name>useSession</param-name><param-value>true</param-value></init-param><init-param><param-name>redirectAfterValidation</param-name><param-value>true</param-value></init-param></filter><filter-mapping><filter-name>CAS Validation Filter</filter-name><url-pattern>/share.jsp</url-pattern></filter-mapping><!-- 该过滤器负责实现HttpServletRequest请求的包裹, 比如允许开发者通过HttpServletRequest 的 getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 --><filter><filter-name>CAS HttpServletRequest Wrapper Filter</filter-name><filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class></filter><filter-mapping><filter-name>CAS HttpServletRequest Wrapper Filter</filter-name><url-pattern>/share.jsp</url-pattern></filter-mapping><!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。 --><filter><filter-name>CAS Assertion Thread Local Filter</filter-name><filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class></filter><filter-mapping><filter-name>CAS Assertion Thread Local Filter</filter-name><url-pattern>/share.jsp</url-pattern></filter-mapping><!-- 自动根据单点登录的结果设置本系统的用户信息 --><filter><display-name>AutoSetUserAdapterFilter</display-name><filter-name>AutoSetUserAdapterFilter</filter-name><filter-class>dcd.academic.cas.AutoSetUserAdapterFilter</filter-class></filter><filter-mapping><filter-name>AutoSetUserAdapterFilter</filter-name><url-pattern>/share.jsp</url-pattern></filter-mapping></web-app>

每一个都很重要,我把https关掉了,所以serverName里写http就行。自己写的AutoSetUserAdapterFilter来获取session里的用户信息的代码:

package dcd.academic.cas;import java.io.IOException;import java.util.Map;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import org.jasig.cas.client.util.AssertionHolder;import org.jasig.cas.client.validation.Assertion;import dcd.academic.DAO.DAOfactory;import dcd.academic.DAO.UserDAO;import dcd.academic.model.User;import dcd.academic.util.StdOutUtil;/*** CAS单点登陆的过滤器功能类,该类用来自动生成子应用的登陆Session* */public class AutoSetUserAdapterFilter implements Filter {/*** Default constructor.*/public AutoSetUserAdapterFilter() {StdOutUtil.out("[AutoSetUserAdapterFilter]");}/*** @see Filter#destroy()*/public void destroy() {}public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {HttpServletRequest httpRequest = (HttpServletRequest) request;// _const_cas_assertion_是CAS中存放登录用户名的session标志Object object = httpRequest.getSession().getAttribute("_const_cas_assertion_");if (object != null) {Assertion assertion = (Assertion) object;String loginName = assertion.getPrincipal().getName();StdOutUtil.out("[loginname]: " + loginName);Map<String, Object> map = assertion.getPrincipal().getAttributes();String email = (String) map.get("email");String name = (String) map.get("name");String username = (String) map.get("username");StdOutUtil.out("[email]: " + email);StdOutUtil.out("[name]: " + name);StdOutUtil.out("[username]: " + username);}chain.doFilter(request, response);}/*** @see Filter#init(FilterConfig)*/public void init(FilterConfig fConfig) throws ServletException {}}

还有一点,就是在validation success的返回jsp里,要新添加一些内容,在目录cas\WEB-INF\view\jsp\protocol\2.0的casServiceValidationSuccess.jsp

<%@ page session="false" %><%@ taglib prefix="c" uri="/jsp/jstl/core" %><%@ taglib uri="/jsp/jstl/functions" prefix="fn" %><cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'><cas:authenticationSuccess><cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user><c:if test="${not empty pgtIou}"><cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket></c:if><c:if test="${fn:length(assertion.chainedAuthentications) > 1}"><cas:proxies><c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1"><cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy></c:forEach></cas:proxies></c:if><c:if test="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes) > 0}"><cas:attributes><c:forEach var="attr" items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"><cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}></c:forEach></cas:attributes></c:if></cas:authenticationSuccess></cas:serviceResponse>

其实本质上这些都是servlet的处理。因为cas也是一个servlet写成的war,说简单也简单。所以cas自己的登录界面我们都是自己自己定制的。

我们在使用的时候,需要改动的项目代码很少。在需要登录或者认证的地方,把链接跳转到server:8443/cas/login上,登录成功后让cas的登录成功界面跳转回原service的url即可,这时候cas是通过service和service ticket生成了新的ticket grant ticket,然后在session里存了东西让客户端去读取的。在安全方面,这步是在SSL的基础上做的,所以我直接访问如server:8443/cas/serviceValidation是会出SSL证书错误的。

还是稍微说一下cas的协议机制吧。这张图也是别人文章里的图,为了方便大家理解,还是帖一下。

•ST:Service Ticket,用于客户端应用持有,每个ST对应一个用户在一个客户端上 •TGT:Ticket Granting Ticket,存储在CAS服务器端和用户cookie两个地方 •CAS服务器持有 ST 与 TGT+ 客户端的映射关系,客户端持有 ST 与用户 Session 的映射关系,在renew的情况下,每次客户端根据用户Session将ST发送给CAS服务器端,服务器端检验ST是否存在即可知道此用户是否已登陆。在普通情况下,用户第一次登陆应用时,客户端将用户页面重定向到CAS服务器,服务器取出用户cookie中的TGT,检验是否在服务器中存在,若存在则生成ST返回给客户端 (若不存在则要求登陆,登陆成功后同样返回ST给客户端),客户端拿到ST后再发送给CAS服务器认证是否为真实ST,认证成功即表示登陆成功

总结

总结cas的话,我们可以单独给一个tomcat来做用户认证模块,并且认证之后,客户端是可以得到session里的用户信息的。可以认为这样就把单点登录问题解决了。至于这个cas服务器怎么配置,怎么认证,需要传递什么的,就去tomcat/webapps/cas的许许多多jsp和xml里去配置。话说这些jsp和xml真的很多。

像这样的开源企业级解决方案,说简单也简单,说难也难,就和solr一样。配置这件事,要进阶使用的话需要很大力气花在源码阅读上,这样你才可以很好的进行定制和扩展。不然我们无法知道他给你写好的简单配置和复杂配置是怎么实现的,我们应该使用哪些写好的handler,需要什么params。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。