配置转换器返回更多用户信息
从cas server登录成功后,默认只能从cas server得到用户名。但程序中也可能遇到需要得到更多如姓名,手机号,email等更多用户信息的情况。
cas client拿到用户名后再到数据库中查询,的确可以得到关于该用户的更多信息。
但是如果用户登录成功后,直接从cas server返回给cas client用户的详细信息,这也是一个不错的做法。这个好处,尤其是在分布式中得以彰显,cas server可以把用户信息传递给各个应用系统,如果是上面那种做法,那么各个系统得到用户名后,都得去数据库中查询一遍,无疑是一件重复性工作。
一、首先需要配置属性attributeRepository
首先,你需要到WEB-INF目录找到 deployerConfigContext.xml文件,同时配置 attributeRepository 如下:
<bean class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao" id="attributeRepository">
<constructor-arg index="0" ref="dataSource"/>
<constructor-arg index="1" value="select * from t_user where {0}"/>
<property name="queryAttributeMapping">
<map>
<!--这里的key需写username和登录页面一致,value对应数据库用户名字段-->
<entry key="username" value="loginname"/>
</map>
</property>
<property name="resultAttributeMapping">
<map>
<!--key为对应的数据库字段名称,value为提供给客户端获取的属性名字,系统会自动填充值-->
<entry key="Id" value="Id"/>
<entry key="password" value="password"/>
<entry key="age" value="age"/>
</map>
</property>
<!-- <property name="queryType">
<value>OR</value>
</property> -->
</bean>
其中:
切记:查询出来的字段名中间不能使用 _ (下划线),否则获取不到数据,如 cell_phone 需要 设置别名为 cellPhone.
queryAttributeMapping是组装sql用的查询条件属性,上述配置后,结合封装成查询sql就是 select* from userinfo where loginname=#username#
resultAttributeMapping是sql执行完毕后返回的结构属性, key对应数据库字段,value对应客户端获取参数。
如果要组装多个查询条件,需要加上下面这个,默认为AND
<property name="queryType">
<value>OR</value>
</property>
二、配置用户认证凭据转化的解析器
也是在 deployerConfigContext.xml中,为 UsernamePasswordCredentialsToPrincipalResolver注入 attributeRepository,那么 attributeRepository就会被触发并通过此类进行解析,红色为新添部分。
<property name="credentialsToPrincipalResolvers">
<list>
<bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" >
<property name="attributeRepository" ref="attributeRepository" />
</bean>
<bean
class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
</list>
</property>
三、修改 deployerConfigContext.xml
中的 org.jasig.cas.services.InMemoryServiceRegistryDaoImpl的属性 registeredServices
修改 registeredServices 列表中的每个协议中的 allowedAttributes属性的值。列出的每个值,在客户端就可以访问了
<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="allowedAttributes">
<list>
<value>Id</value>
<value>password</value>
<value>age</value>
</list>
</property>
</bean>
此步骤灰常重要,可以看看 org.jasig.cas.services.RegexRegisteredService的源码,其中的 allowedAttributes是关键
【提示】网上说此bean中的ignoreAttributes属性默认是不添加用户信息,查看了 CAS 3.5.2版本的 AbstractRegisteredService 源码后,发现其默认值就是 false,即:添加属性后,客户端就可见了
四、修改casServiceValidationSuccess.jsp
WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp
在server验证成功后,这个页面负责生成与客户端交互的xml信息,在默认的casServiceValidationSuccess.jsp中,只包括用户名,并不提供其他的属性信息,因此需要对页面进行扩展,如下,红色为新添加部分
<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="${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>
<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>
通过完成上面四个步骤的配置后,CAS Server端的工作就完成了,那么如何在客户端获取这些信息呢?下面进行说明:
cas client获取用户信息:
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
Map attributes = principal.getAttributes();
String email=attributes .get("age");
补充:
cas_client项目结构:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
<display-name>cas_client</display-name>
<welcome-file-list>
<welcome-file>userInfoView.jsp</welcome-file>
</welcome-file-list>
<!-- ======================== 单点登录开始 ======================== -->
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置-->
<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>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>CAS Filter</filter-name>
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://localhost:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:18080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<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://localhost:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:18080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</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>/*</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>/*</url-pattern>
</filter-mapping> -->
<!-- ======================== 单点登录结束 ======================== -->
<servlet>
<servlet-name>HelloWorldExample</servlet-name>
<servlet-class>com.tgb.cas.client.HelloWorldExample</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloWorldExample</servlet-name>
<url-pattern>/servlet/HelloWorldExample</url-pattern>
</servlet-mapping>
</web-app>
HelloWorldExample:
public class HelloWorldExample extends HttpServlet {
private static final long serialVersionUID = 1L;
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head>");
String title = "Hello";
out.println("<title>" + title + "</title>");
out.println("</head>");
out.println("<body bgcolor=\"white\">");
out.println("<a href=\"../helloworld.html\">");
out.println("<img src=\"../images/code.gif\" height=24 "
+ "width=24 align=right border=0 alt=\"view code\"></a>");
out.println("<a href=\"../index.html\">");
out.println("<img src=\"../images/return.gif\" height=24 "
+ "width=24 align=right border=0 alt=\"return\"></a>");
out.println("<h1>" + title + "</h1>");
//以下是两种获取用户信息的两种方式,分别与Web.XML中的配置相对应,大家结合理解
// 通过 CAS HttpServletRequest Wrapper Filter 获取用户信息
String userNameString = request.getRemoteUser();
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
if (null != principal) {
Map<String, Object> attMap = principal.getAttributes();
out.println(" Log | getAttributes Map size = " + attMap.size() + "<br>");
for (Entry<String, Object> entry : attMap.entrySet()) {
out.println(" | " + entry.getKey() + "=:" + entry.getValue() + "<br>");
}
String username = null;
out.print(" Log | UserName:");
if (null != principal) {
username = principal.getName();
out.println("<span style='color:red;'>" + username + "</span><br>");
}
}
// 通过CAS Assertion Thread Local Filter 获取用户信息,共两种方式
// Assertion assertion = (Assertion) request.getSession().getAttribute(
// AbstractCasFilter.CONST_CAS_ASSERTION);
// if (null != assertion) {
//
// Map<String, Object> attMap = assertion.getPrincipal().getAttributes();
// out.println(" Log | getAttributes Map size = " + attMap.size() + "<br>");
// for (Entry<String, Object> entry : attMap.entrySet()) {
// out.println(" | " + entry.getKey() + "=:" + entry.getValue() + "<br>");
// }
//
// AttributePrincipal principal = assertion.getPrincipal();
// // AttributePrincipal principal = (AttributePrincipal) request
// // .getUserPrincipal();
// String username = null;
// out.print(" Log | UserName:");
// if (null != principal) {
// username = principal.getName();
// out.println("<span style='color:red;'>" + username + "</span><br>");
// }
// }
out.println("</body>");
out.println("</html>");
}
}
userInfoView.jsp(未使用):
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<a href="servlet/HelloWorldExample">获取Server端用户信息</a>
<br>
<a href="http://localhost:8080/cas/logout?service=http://localhost:18080/cas_client/servlet/HelloWorldExample">单点退出</a>
</body>
</html>
效果:
访问:http://localhost:18080/cas_client/servlet/HelloWorldExample
附:
其中cas服务端对应的代码是官网中提供的cas-server-webapp-3.5.2.war,而cas_client客户端代码是我们自己编写的,参照tomcat中的example项目。
总结:
CAS返回更多用户信息这方面的文章有很多,在这里我再次进行总结学习也是为了梳理自己的知识,让自己加深理解。以上只是一个简单的知识点,大家理解就好,接下来会结合Spring进行学习。