select password from user where user_account=?,只能按照某一字段查询,但是实际应用时一般可以使用账号、email、手机号皆可登录。而返回到客户端的数据则是你登录时输入的账号,实际应用可能需要返回更多数据。所以本章为大家讲解如何采用多条件查询,以及返回更多的数据列(单条数据,多列)。
我们先看源码
package org.jasig.cas.adaptors.jdbc;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import javax.validation.constraints.NotNull;
public class QueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {
@NotNull
private String sql;
protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException {
final String username = getPrincipalNameTransformer().transform(credentials.getUsername());
final String password = credentials.getPassword();
final String encryptedPassword = this.getPasswordEncoder().encode(
password);
try {
<span style="color:#ff0000;">final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
return dbPassword.equals(encryptedPassword);
} catch (final IncorrectResultSizeDataAccessException e) {
// this means the username was not found.
return false;
}
}
/**
* @param sql The sql to set.
*/
public void setSql(final String sql) {
this.sql = sql;
}
}
大家可以看到,在getJdbcTemplate().queryForObject(this.sql, String.class, username); 用过spring-jdbc-template的同学都知道,这里面只设置了一个username参数,所以只能单条件查询,所以我们需要改造这个方法,创建一个我们自己的查询类
package org.jasig.cas.handler;
import org.jasig.cas.adaptors.jdbc.AbstractJdbcUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import javax.validation.constraints.NotNull;
/**
* 名称:org.jasig.cas.handler
* 描述:<br> 多条件查询
* 类型:JAVA<br>
* 最近修改时间:22/12/2014 19:01<br>
*
* @author Jack Chan
* @since 22/12/2014
*/
public class MultiQueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {
@NotNull
private String sql;
protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException {
final String username = getPrincipalNameTransformer().transform(credentials.getUsername());
final String password = credentials.getPassword();
final String encryptedPassword = this.getPasswordEncoder().encode(
password);
try {
//这里username个数对应我们的查询条件个数,只需要改这一处地方
<span style="color:#ff0000;">final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username, username, username);
return dbPassword.equals(encryptedPassword);
} catch (final IncorrectResultSizeDataAccessException e) {
// this means the username was not found.
return false;
}
}
/**
* @param sql The sql to set.
*/
public void setSql(final String sql) {
this.sql = sql;
}
}
然后修改deployerConfigContext.xml的
<property name="authenticationHandlers">
<list>
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient" />
<!-- 数据库查询认证处理器 -->
<span style="color:#ff0000;"><bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="mysqlDataSource"/>
<property name="sql"
value="select password from user where user_account=? "/>
<property name="passwordEncoder" ref="myPasswordEncoder"></property>
</bean>
</list>
</property>
修改为
<property name="authenticationHandlers">
<list>
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient" />
<!-- 数据库查询认证处理器 -->
<span style="color:#ff0000;"><bean class="org.jasig.cas.handler.MultiQueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="mysqlDataSource"/>
<property name="sql"
value="select password from user where user_account=? or email = ? or cellphone = ? "/>
<property name="passwordEncoder" ref="myPasswordEncoder"></property>
</bean>
</list>
</property>
现在重启cas-server后,cxh5060、17000000000、cxh5060@163.com 都可以登录了
多条件查询的问题我们已经解决了,现在来看看多数据列返回的问题,这个比多条件查询要麻烦一些。
同样在deployerConfigContext.xml中有这样一段xml
<!--
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">
<property name="backingMap">
<map>
<entry key="uid" value="uid" />
<entry key="eduPersonAffiliation" value="eduPersonAffiliation" />
<entry key="groupMembership" value="groupMembership" />
</map>
</property>
</bean>
看上面注释大概明白这个是干嘛的吧,即定义返回哪些需要返回的属性,这个StubPersonAttributeDao是一个测试对象。具体请搜索 stub/mock,用在单元测试时。其实这样的对象在cas中不鲜见。
查看org.jasig.services.persondir.support.StubPersonAttributeDao的父类的子类可以发现下面的类org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao,其继承关系如下
我之前想当然的以为这个会是解决方案,官网的示例代码https://wiki.jasig.org/display/PDM15/JDBC+Attribute+Source
<bean id="attributeRepository" class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao">
<constructor-arg index="0" ref="dataSource" />
<constructor-arg index="1" value="SELECT * FROM USER_DATA WHERE {0}" />
<span style="color:#ff0000;"><property name="queryAttributeMapping">
<map>
<entry key="username" value="uid" />
</map>
</property>
<property name="resultAttributeMapping">
<map>
<entry key="uid" value="username" />
<entry key="first_name" value="first_name" />
<entry key="last_name" value="last_name" />
<entry key="email" value="email" />
</map>
</property>
</bean>
但是当我配置后运行发现根本不行,看他的父类AbstractDefaultAttributePersonAttributeDao的protected Map<String, List<Object>> toSeedMap(String uid){};
protected Map<String, List<Object>> toSeedMap(String uid) {
final List<Object> values = Collections.singletonList((Object)uid);
final String usernameAttribute = this.usernameAttributeProvider.getUsernameAttribute();
final Map<String, List<Object>> seed = Collections.singletonMap(usernameAttribute, values);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Created seed map='" + seed + "' for uid='" + uid + "'");
}
return seed;
}
该方法只能返回一个单例的map,而且SimpleUsernameAttributeProvider中的usernameAttribute是写死的。public class SimpleUsernameAttributeProvider implements IUsernameAttributeProvider {
private String usernameAttribute = "username";
public SimpleUsernameAttributeProvider() {
}
public SimpleUsernameAttributeProvider(String usernameAttribute) {
this.setUsernameAttribute(usernameAttribute);
}
/**
* The usernameAttribute to use
*/
public void setUsernameAttribute(String usernameAttribute) {
Validate.notNull(usernameAttribute);
this.usernameAttribute = usernameAttribute;
}
/* (non-Javadoc)
* @see org.jasig.services.persondir.support.IUsernameAttributeProvider#getUsernameAttribute()
*/
public String getUsernameAttribute() {
return this.usernameAttribute;
}
/* (non-Javadoc)
* @see org.jasig.services.persondir.support.IUsernameAttributeProvider#getUsernameFromQuery(java.util.Map)
*/
public String getUsernameFromQuery(Map<String, List<Object>> query) {
final List<Object> usernameAttributeValues = query.get(this.usernameAttribute);
if (usernameAttributeValues == null || usernameAttributeValues.size() == 0) {
return null;
}
final Object firstValue = usernameAttributeValues.get(0);
if (firstValue == null) {
return null;
}
final String username = StringUtils.trimToNull(String.valueOf(firstValue));
if (username == null || username.contains(IPersonAttributeDao.WILDCARD)) {
return null;
}
return username;
}
}
也就是说queryAttributeMapping中的key,只能是username,开始我希望通过重写SimpleUsernameAttributeProvider这个类来改变,但是AbstractDefaultAttributePersonAttributeDao的toSeedMap方法让我绝望了,所以我们不得不修改源码,这里大家理解后希望找到更好的的方法。我这个方法不是很优雅。
只需要修改person-directory-impl-1.5.1的AbstractDefaultAttributePersonAttributeDao
protected Map<String, List<Object>> toSeedMap(String uid) {
final List<Object> values = Collections.singletonList((Object)uid);
final String usernameAttribute = this.usernameAttributeProvider.getUsernameAttribute();
final Map<String, List<Object>> seed = Collections.singletonMap(usernameAttribute, values);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Created seed map='" + seed + "' for uid='" + uid + "'");
}
return seed;
}
修改为
protected Map<String, List<Object>> toSeedMap(String uid) {
final List<Object> values = Collections.singletonList((Object) uid);
final String usernameAttribute = this.usernameAttributeProvider.getUsernameAttribute();
Map<String, List<Object>> seed = toSeedMap0(usernameAttribute, values);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Created seed map='" + seed + "' for uid='" + uid + "'");
}
return seed;
}
protected Map<String, List<Object>> toSeedMap0(String usernameAttributes, List<Object> values) {
Map<String, List<Object>> seed = new HashMap<String, List<Object>>(3);
String[] split = usernameAttributes.split(",");
for(String usernameAttribute : split) {
seed.put(usernameAttribute,values);
}
return seed;
}
修改后的toSeedMap将返回一个具有多个key的map,不再是一个单key的集合。这样我们就可以实现多条件查询了,将我们的源码编译打包替换之前的person-directory-impl-1.5.1.jar
我们还实现一个我们自己的SimpleUsernameAttributeProvider,
package org.jasig.cas.util;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.jasig.services.persondir.IPersonAttributeDao;
import org.jasig.services.persondir.support.IUsernameAttributeProvider;
import java.util.List;
import java.util.Map;
/**
* 名称:org.jasig.cas.util
* 描述:<br>
* 类型:JAVA<br>
* 最近修改时间:15/12/2014 11:26<br>
*
* @author Jack Chan
* @since 15/12/2014
*/
public class MyUsernameAttributeProvider implements IUsernameAttributeProvider {
private String usernameAttribute = "cellphone,email,user_account";
public MyUsernameAttributeProvider() {
}
public MyUsernameAttributeProvider(String usernameAttribute) {
this.setUsernameAttribute(usernameAttribute);
}
/**
* The usernameAttribute to use
*/
public void setUsernameAttribute(String usernameAttribute) {
Validate.notNull(usernameAttribute);
this.usernameAttribute = usernameAttribute;
}
/* (non-Javadoc)
* @see org.jasig.services.persondir.support.IUsernameAttributeProvider#getUsernameAttribute()
*/
public String getUsernameAttribute() {
return this.usernameAttribute;
}
/* (non-Javadoc)
* @see org.jasig.services.persondir.support.IUsernameAttributeProvider#getUsernameFromQuery(java.util.Map)
*/
public String getUsernameFromQuery(Map<String, List<Object>> query) {
final List<Object> usernameAttributeValues = query.get("cellphone");
if (usernameAttributeValues == null || usernameAttributeValues.size() == 0) {
return null;
}
final Object firstValue = usernameAttributeValues.get(0);
if (firstValue == null) {
return null;
}
final String username = StringUtils.trimToNull(String.valueOf(firstValue));
if (username == null || username.contains(IPersonAttributeDao.WILDCARD)) {
return null;
}
return username;
}
}
完成后修改WEB-INF/deployerConfigContext.xml的
<!--
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">
<property name="backingMap">
<map>
<entry key="uid" value="uid" />
<entry key="eduPersonAffiliation" value="eduPersonAffiliation" />
<entry key="groupMembership" value="groupMembership" />
</map>
</property>
</bean>
修改为
<bean id="usernameAttributeProvider" class="org.jasig.cas.util.MyUsernameAttributeProvider"/>
<bean id="attributeRepository" class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao">
<constructor-arg index="0" ref="mysqlDataSource"/>
<constructor-arg index="1">
<!-- 在AbstractJdbcPersonAttributeDao定义了替换的正则,会将{0}替换为查询的条件 -->
<value>select email,cellphone,user_account,user_id from user where {0}</value>
</constructor-arg>
<property name="requireAllQueryAttributes" value="true"></property>
<property name="usernameAttributeProvider" ref="usernameAttributeProvider"/>
<!--组装sql用的查询条件属性-->
<property name="queryAttributeMapping">
<map>
<entry key="cellphone" value="cellphone"/>
<entry key="email" value="email"/>
<entry key="user_account" value="user_account"/>
</map>
</property>
<!--如果要组装多个查询条件,需要加上下面这个,默认为AND -->
<property name="queryType">
<value>OR</value>
</property>
<!--要获取的属性在这里配置 -->
<property name="resultAttributeMapping">
<map>
<!--key为对应的数据库字段名称,value为提供给客户端获取的属性名字,系统会自动填充值-->
<entry key="email" value="email"/>
<entry key="cellphone" value="cellphone"/>
<entry key="user_id" value="userId"/>
<entry key="user_account" value="userAccount"/>
</map>
</property>
</bean>
现在我们已经实现了多条件与多数据(指的是单条数据,有多列,如果需要返回多列,见MultiRowJdbcPersonAttributeDao)返回了,但是还有最后一步,cas-server通过WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp返回xml,为了支持多列返回我们需要这样改造
将内容全部替换为以下内容
<%@ 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>
<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>
<!-- 在server验证成功后,这个页面负责生成与客户端交互的xml信息,在默认的casServiceValidationSuccess.jsp中,只包括用户名,并不提供其他的属性信息,因此需要对页面进行扩展 -->
<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>
重启服务器,使用客户端请求,在客户端AuthenticationFilter打断点可以看到返回的assertion中并没有attributes,这个地方我查找了好久才发现,
将deployerConfigContext.xml中的
<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" />
</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>
全部注释掉或删除,为什么这么做呢,是因为添加了注册服务后,其中有一个参数为ignoreAttributes,默认值为false,大家可以看
org.jasig.cas.CentralAuthenticationServiceImpl以及org.jasig.cas.services.AbstractRegisteredService,如果不删除上面的代码,给RegexRegisteredService加上<property name="ignoreAttributes" value="true" />也可以
现在重启服务器,可以在客户端得到attributes了
客户端返回的xml信息(客户端设置log4j为DEBUG级别),在控制台可以看到
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>15652515060</cas:user>
<cas:attributes>
<cas:email>cxh5060@163.com</cas:email>
<cas:cellphone>17000000000</cas:cellphone>
<cas:userId>aaabbb</cas:userId>
<cas:userAccount>cxh</cas:userAccount>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse><
本章探讨如何在CAS系统中实现多条件查询,如账号、email、手机号登录,并返回更多数据列。通过改造Spring JDBC Template的方法,解决单条件查询限制。同时解决多数据列返回问题,涉及修改`AbstractDefaultAttributePersonAttributeDao`源码,实现自定义的`SimpleUsernameAttributeProvider`。最终,调整部署配置使得客户端能获取到完整attributes信息。
400

被折叠的 条评论
为什么被折叠?



