转载自:blog.yoodb.com/yoodb/artic…
单点登录(Single Sign-On, 简称SSO)是目前比较流行的服务于企业业务整合的解决方案之一,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。大家在使用时CAS Server验证成功后会立即跳转到客户端,CAS Server默认只返回账号uid给客户端,如何定义CAS server返回信息到CAS Client客户端(CAS server返回更多用户信息)呢?下面本站素文宅www.yoodb.com本篇文章具体讲解一下怎么实现,仅供大家参考学习。
\
1、相关接口
首先了解一下相关的几个接口
• Credentials
• Principal
• IPersonAttributeDao
• CredentialsToPrincipalResolver
1)org.jasig.cas.authentication.principal.Credentials接口, 在工程中UsernamePasswordCredential 的类就是实现了Credentials接口。这个接口是用来定义我们在登录时输入的认证信息,比如用户名、密码、验证码等,可以理解为用户认证的相关凭据。
2)org.jasig.cas.authentication.principal.Principal接口,主要是用来保存用户认证后的用户信息,信息保存在一个Map集合中。
3)org.jasig.services.persondir.IPersonAttributeDao接口,是用来定义我们需要返回给客户端相关信息的接口,CAS Server默认有提供许多实现,比如
• SingleRowJdbcPersonAttributeDao : JDBC SQL查询返回信息
• LdapPersonAttributeDao :查询 LDAP 目录返回信息
• StubPersonAttributeDao :自定义属性返回信息
等参考源码中的实现,CAS Server提供了各种功能的实现
4)org.jasig.cas.authentication.principal. CredentialsToPrincipalResolver接口,主要是用来把Credentials里面的信息转换到 Principal中,在这接口中有两个方法,具体如下:\
Principal resolvePrincipal(Credentials credentials);// 解析Credentials中的信息,返回 Principal 接口
boolean supports(Credentials credentials); //判断Credentials 是否支持 Principal 协议
注意:在3.x版本是CredentialsToPrincipalResolver接口,4.x版本是PrincipalResolver接口
\
2、CAS Server流程
打开 deployerConfigContext.xml 文件,CAS Server3.5.2版本默认配置了org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver类,它注入一个 attributeRepository 属性,实现IPersonAttributeDao 接口调用getPerson方法,传入principalId参数,也就是调用Credentials 接口的getId() 方法,配置文件详情具体如下:
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 | <?xml version= "1.0" encoding= "UTF-8" ?>``<!--`` Licensed to Jasig under one or more contributor license`` agreements. See the NOTICE file distributed with this work`` for additional information regarding copyright ownership.`` Jasig licenses this file to you under the Apache License,`` Version 2.0 (the "License" ); you may not use this file`` except in compliance with the License. You may obtain a`` copy of the License at the following location:`` http: //www .apache.org /licenses/LICENSE-2 .0`` Unless required by applicable law or agreed to in writing,`` software distributed under the License is distributed on an`` "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY`` KIND, either express or implied. See the License for the`` specific language governing permissions and limitations`` under the License.``-->``<!--``| 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= "http://www.springframework.org/schema/beans"`` xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"`` xmlns:p= "http://www.springframework.org/schema/p"`` xmlns:tx= "http://www.springframework.org/schema/tx"`` xmlns:sec= "http://www.springframework.org/schema/security"`` xsi:schemaLocation="http: //www .springframework.org /schema/beans http: //www .springframework.org /schema/beans/spring-beans-3 .1.xsd`` http: //www .springframework.org /schema/tx http: //www .springframework.org /schema/tx/spring-tx-3 .1.xsd`` http: //www .springframework.org /schema/security http: //www .springframework.org /schema/security/spring-security-3 .1.xsd">``<!-- ldap 数据源 -->``<bean id = "contextSource" class= "org.springframework.ldap.core.support.LdapContextSource" >``<property name= "pooled" value= "false" />``<property name= "urls" >``<list>`` <value>ldap: //127 .0.0.1:1389< /value >``< /list >``< /property >``<property name= "userDn" value= "cn=Directory Manager" />``<property name= "password" value= "123456" />``<property name= "baseEnvironmentProperties" >``<map>``<entry>``<key>``<value>java.naming.security.authentication< /value >``< /key >``<value>simple< /value >``<!--<value>none< /value > -->``< /entry >``< /map >``< /property >``< /bean >``<!--``| 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" >``<!-- Uncomment the metadata populator to allow clearpass to capture and cache the password`` This switch effectively will turn on clearpass.``<property name= "authenticationMetaDataPopulators" >`` <list>`` <bean class= "org.jasig.cas.extension.clearpass.CacheCredentialsMetaDataPopulator" >`` <constructor-arg index= "0" ref= "credentialsCache" />`` < /bean >`` < /list >``< /property >`` -->``<!--``| 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.``+-->``<bean class= "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).``+-->``<bean``class= "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" />``<!--``| 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.``+-->``<!-- <bean``class= "org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" /> -->``<!-- ldap认证 -->`` <bean class= "org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler" >``<property name= "filter" value= "uid=%u" />``<property name= "searchBase" value= "ou=users,dc=CMP" />``<property name= "contextSource" ref= "contextSource" />``<!-- <property name= "requireAllQueryAttributes" value= "true" /> -->``< /bean > ``< /list >``< /property >``< /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 implementation``may go against a database or LDAP server. The id should remain "attributeRepository" though.``<bean id = "attributeRepository"``class= "org.jasig.services.persondir.support.StubPersonAttributeDao" >`` -->`` <bean id = "attributeRepository"``class= "org.jasig.cas.util.ShiroStubPersonAttributeDao" >``<property name= "backingMap" >``<map>``<entry key= "uid" value= "uid" />``<entry key= "username" value= "username" />``<entry key= "password" value= "password" />``<entry key= "eduPersonAffiliation" value= "eduPersonAffiliation" />``<entry key= "groupMembership" value= "groupMembership" />``< /map >``< /property >``< /bean >``<!--``Sample, in -memory data store for the ServiceRegistry. A real implementation``would probably want to replace this with the JPA-backed ServiceRegistry DAO``The name of this bean should remain "serviceRegistryDao" .`` -->``<bean``id = "serviceRegistryDao"`` class= "org.jasig.cas.services.InMemoryServiceRegistryDaoImpl" >`` <property name= "registeredServices" >`` <list>`` <bean class= "org.jasig.cas.services.RegexRegisteredService" >`` <property name= "id" value= "0" />`` <property name= "name" value= "HTTP and IMAP" />`` <property name= "description" value= "Allows HTTP(S) and IMAP(S) protocols" />`` <property name= "serviceId" value= "^(https?|imaps?)://.*" />`` <property name= "evaluationOrder" value= "10000001" />`` <property name= "ignoreAttributes" value= "true" />`` < /bean >`` <!--`` Use the following definition instead of the above to further restrict access`` to services within your domain (including subdomains).`` Note that example.com must be replaced with the domain you wish to permit.`` -->`` <!--`` <bean class= "org.jasig.cas.services.RegexRegisteredService" >`` <property name= "id" value= "1" />`` <property name= "name" value= "HTTP and IMAP on example.com" />`` <property name= "description" value= "Allows HTTP(S) and IMAP(S) protocols on example.com" />`` <property name= "serviceId" value= "^(https?|imaps?)://([A-Za-z0-9_-]+\.)*example\.com/.*" />`` <property name= "evaluationOrder" value= "0" />`` < /bean >`` -->`` < /list >`` < /property >`` < /bean >`` <bean id = "auditTrailManager" class= "com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager" />`` <bean id = "healthCheckMonitor" class= "org.jasig.cas.monitor.HealthCheckMonitor" >`` <property name= "monitors" >`` <list>`` <bean class= "org.jasig.cas.monitor.MemoryMonitor"`` p:freeMemoryWarnThreshold= "10" />`` <!--`` NOTE`` The following ticket registries support SessionMonitor:`` * DefaultTicketRegistry`` * JpaTicketRegistry`` Remove this monitor if you use an unsupported registry.`` -->`` <bean class= "org.jasig.cas.monitor.SessionMonitor"`` p:ticketRegistry-ref= "ticketRegistry"`` p:serviceTicketCountWarnThreshold= "5000"`` p:sessionCountWarnThreshold= "100000" />`` < /list >`` < /property >`` < /bean >``< /beans > |
|---|
新增以及修改内容,具体如下:
| 123456789101112 | <bean id = "attributeRepository"``class= "org.jasig.cas.util.ShiroStubPersonAttributeDao" >``<property name= "backingMap" >``<map>``<entry key= "uid" value= "uid" />``<entry key= "username" value= "username" />``<entry key= "password" value= "password" />``<entry key= "eduPersonAffiliation" value= "eduPersonAffiliation" />``<entry key= "groupMembership" value= "groupMembership" />``< /map >``< /property >``< /bean > |
|---|
调试源码会发现org.jasig.cas.CentralAuthenticationServiceImpl.java类中有判断是否允许客户端获取数据信息,代码如下:
| 12345678 | if (!registeredService.isIgnoreAttributes()) {`` final Map<String, Object> attributes = new HashMap<String, Object>();`` for (final String attribute : registeredService.getAllowedAttributes()) {`` final Object value = principal.getAttributes().get(attribute);`` if (value != null) {`` attributes.put(attribute, value);`` }`` } |
|---|
下面配置将ignoreAttributes属性值设置为true,允许客户端获取数据,具体配置如下:
| 1 | <property name= "ignoreAttributes" value= "true" /> |
|---|
\
重写org.jasig.services.persondir.support.StubPersonAttributeDao类中getPerson方法,实现多用户信息具体代码如下:
| 12345678910111213141516171819 | package org.jasig.cas.util;``import java.util.Collections;``import java.util.HashMap;``import java.util.List;``import java.util.Map;``import org.jasig.services.persondir.IPersonAttributes;``import org.jasig.services.persondir.support.AttributeNamedPersonImpl;``import org.jasig.services.persondir.support.StubPersonAttributeDao;``public class ShiroStubPersonAttributeDao extends StubPersonAttributeDao{``@Override`` public IPersonAttributes getPerson(String uid) {`` Map<String, List<Object>> attributes = new HashMap<String, List<Object>>();`` attributes.put( "uid" , Collections.singletonList((Object)uid));`` attributes.put( "username" , Collections.singletonList((Object) "www.yoodb.com" ));`` attributes.put( "password" , Collections.singletonList((Object) "123456" ));`` return new AttributeNamedPersonImpl(attributes);`` }``} |
|---|
\
修改cas-server-webapp工程中路径/cas-server-webapp/src/main/webapp/WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp文件内容,具体如下:
| 123456789101112131415161718192021222324252627282930313233343536373839404142434445 | <%--`` Licensed to Jasig under one or more contributor license`` agreements. See the NOTICE file distributed with this work`` for additional information regarding copyright ownership.`` Jasig licenses this file to you under the Apache License,`` Version 2.0 (the "License" ); you may not use this file`` except in compliance with the License. You may obtain a`` copy of the License at the following location:`` http: //www.apache.org/licenses/LICENSE-2.0`` Unless required by applicable law or agreed to in writing,`` software distributed under the License is distributed on an`` "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY`` KIND, either express or implied. See the License for the`` specific language governing permissions and limitations`` under the License.`` --%>``<%@ page session= "false" %>``<%@ taglib prefix= "c" uri= "http://java.sun.com/jsp/jstl/core" %>``<%@ taglib uri= "http://java.sun.com/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>``<!--pros Begin-->``<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 >``<!--pros End-->``<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 >``</cas:authenticationSuccess>``</cas:serviceResponse> |
|---|
新增内容,具体如下:
| 1234567 | <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 > |
|---|
\
3、CAS Client流程
在CAS Client设置信息接收,java可以通过下面的方式获取,代码如下:
| 123 | AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();``Map attributes = principal.getAttributes();``String uid = attributes .get( "uid" ); |
|---|
\
CAS Client如果集成了shiro权限控制,由于cas client将casFilter交给了shiroFilter来控制,接受数据信息代码如下:
| 12345 | Subject subject = SecurityUtils.getSubject();``List list = subject.getPrincipals().asList();``String name = (String) list.get( 0 ); //账号``Map<String, Object> info = (Map<String, Object>)list.get( 1 ); //Json串``String uid = (String) info.get( "uid" ); |
|---|