前面粗略的介紹了對於返回更多信息的簡單配置,但是有些時候業務的需求 不是簡單的查詢語句就能解決,比如說我查詢用戶信息需要結合登錄憑證中的多個字段
例如:我們有個項目的登錄用戶確定是根據訪問域名和登錄名同時確定的,這樣就不得不進行拓展。
primaryPrincipalResolver:解析Credential(用戶憑證,簡單點就是用戶登錄信息)處理成用戶登錄成功以後的返回信息
在deployerConfigContext.xml文件中,我們可以找到id爲primaryPrincipalResolver的bean
<bean id="primaryPrincipalResolver"
class="org.jasig.cas.authentication.principal.PersonDirectoryPrincipalResolver" >
<property name="attributeRepository" ref="attributeRepository" />
</bean>
同樣要理清楚其中的邏輯還是得看PersonDirectoryPrincipalResolver這個類是怎麼處理的,其中關鍵代碼:
public final Principal resolve(final Credential credential) {
logger.debug("Attempting to resolve a principal...");
String principalId = extractPrincipalId(credential);
if (principalId == null) {
logger.debug("Got null for extracted principal ID; returning null.");
return null;
}
logger.debug("Creating SimplePrincipal for [{}]", principalId);
final IPersonAttributes personAttributes = this.attributeRepository.getPerson(principalId);
final Map<String, List<Object>> attributes;
if (personAttributes == null) {
attributes = null;
} else {
attributes = personAttributes.getAttributes();
}
if (attributes == null & !this.returnNullIfNoAttributes) {
return new SimplePrincipal(principalId);
}
if (attributes == null) {
return null;
}
final Map<String, Object> convertedAttributes = new HashMap<String, Object>();
for (final String key : attributes.keySet()) {
final List<Object> values = attributes.get(key);
if (key.equalsIgnoreCase(this.principalAttributeName)) {
if (values.isEmpty()) {
logger.debug("{} is empty, using {} for principal", this.principalAttributeName, principalId);
} else {
principalId = values.get(0).toString();
logger.debug(
"Found principal attribute value {}; removing {} from attribute map.",
principalId,
this.principalAttributeName);
}
} else {
convertedAttributes.put(key, values.size() == 1 ? values.get(0) : values);
}
}
return new SimplePrincipal(principalId, convertedAttributes);
}
其中principalId是通過credential得到
String principalId = extractPrincipalId(credential);
代碼如下:
protected String extractPrincipalId(final Credential credential) {
return credential.getId();
}
可以看到其實就是拿用戶憑證的Id 至於這個getId()返回的是什麼呢,我們看下UsernamePasswordCredential類
public String getId() {
return this.username;
}
發現這個其實就是返回的登錄名,繼續看resolve方法的代碼會發現這行代碼
final IPersonAttributes personAttributes = this.attributeRepository.getPerson(principalId);
可以看到通過attributeRepository的getPerson方法來獲取需要返回的用戶屬性 而參數就是我們的principalId
從上面可以看出attributeRepository作爲primaryPrincipalResolver的一個屬性注入進來,而attributeRepository的配置呢,前面文章已經有了相關配置,jdbc的方式
實現類 SingleRowJdbcPersonAttributeDao 其實中字面意思也能看出 是singleRow 即單列的 ,同樣該類的代碼:
public class SingleRowJdbcPersonAttributeDao extends AbstractJdbcPersonAttributeDao<Map<String, Object>> {
private static final ParameterizedRowMapper<Map<String, Object>> MAPPER = new ColumnMapParameterizedRowMapper(true);
public SingleRowJdbcPersonAttributeDao(DataSource ds, String sql) {
super(ds, sql);
}
@Override
protected ParameterizedRowMapper<Map<String, Object>> getRowMapper() {
return MAPPER;
}
@Override
protected List<IPersonAttributes> parseAttributeMapFromResults(List<Map<String, Object>> queryResults, String queryUserName) {
final List<IPersonAttributes> peopleAttributes = new ArrayList<IPersonAttributes>(queryResults.size());
for (final Map<String, Object> queryResult : queryResults) {
final Map<String, List<Object>> multivaluedQueryResult = MultivaluedPersonAttributeUtils.toMultivaluedMap(queryResult);
final IPersonAttributes person;
if (queryUserName != null) {
person = new CaseInsensitiveNamedPersonImpl(queryUserName, multivaluedQueryResult);
}
else {
//Create the IPersonAttributes doing a best-guess at a userName attribute
final String userNameAttribute = this.getConfiguredUserNameAttribute();
person = new CaseInsensitiveAttributeNamedPersonImpl(userNameAttribute, multivaluedQueryResult);
}
peopleAttributes.add(person);
}
return peopleAttributes;
}
}
繼承了AbstractJdbcPersonAttributeDao的類,繼續跟,構造函數代碼
public AbstractJdbcPersonAttributeDao(DataSource ds, String queryTemplate) {
Validate.notNull(ds, "DataSource can not be null");
Validate.notNull(queryTemplate, "queryTemplate can not be null");
this.simpleJdbcTemplate = new SimpleJdbcTemplate(ds);
this.queryTemplate = queryTemplate;
}
聯想之前在在deployerConfigContext.xml文件中對attributeRepository的配置
<bean id= "attributeRepository" class="org.jasig.services.persondir.support.jdbc.SingleRowJdbcPersonAttributeDao" >
<constructor-arg index= "0" ref ="dataSource"/>
<constructor-arg index= "1" value ="SELECT * FROM T_BASE_USER WHERE {0} AND USER_STATUS IN (1,2)" />
<property name= "queryAttributeMapping" >
<map>
<entry key= "username" value ="USER_LOGIN_NAME" />
</map>
</property>
<property name="resultAttributeMapping" >
<map>
<entry key="USER_ACTUAL_NAME" value ="userName"/>
<entry key="SCHOOL_CODE" value="schoolCode" />
<entry key="USER_ID" value="userId" />
<entry key="USER_TYPE" value="userType"/>
</map>
</property>
</bean >
constructor-arg 構造函數的方式初始化屬性 正好ds 對應我們傳入的數據源(dataSource)queryTemplate 對應我們設置的sql語句
接着看往下看
上面也看到了其實是調用了getPerson方法 ,對於該方法的實現是在 AbstractDefaultAttributePersonAttributeDao 類中(AbstractJdbcPersonAttributeDao的祖父類)
代碼如下:
public IPersonAttributes getPerson(String uid) {
Validate.notNull(uid, "uid may not be null.");
//Generate the seed map for the uid
final Map<String, List<Object>> seed = this.toSeedMap(uid);
//Run the query using the seed
final Set<IPersonAttributes> people = this.getPeopleWithMultivaluedAttributes(seed);
//Ensure a single result is returned
IPersonAttributes person = (IPersonAttributes)DataAccessUtils.singleResult(people);
if (person == null) {
return null;
}
//Force set the name of the returned IPersonAttributes if it isn't provided in the return object
if (person.getName() == null) {
person = new NamedPersonImpl(uid, person.getAttributes());
}
return person;
}
其中this.toSeedMap(uid)其實就是對我們傳入的username做一個轉換 轉換爲map
其中關鍵代碼
final Set<IPersonAttributes> people = this.getPeopleWithMultivaluedAttributes(seed);
其中getPeopleWithMultivaluedAttributes的是實現在其子類 AbstractQueryPersonAttributeDao中,具體代碼:
public final Set<IPersonAttributes> getPeopleWithMultivaluedAttributes(Map<String, List<Object>> query) {
Validate.notNull(query, "query may not be null.");
final QB queryBuilder = this.generateQuery(query);
if (queryBuilder == null && (this.queryAttributeMapping != null || this.useAllQueryAttributes == true)) {
this.logger.debug("No queryBuilder was generated for query " + query + ", null will be returned");
return null;
}
//Get the username from the query, if specified
final IUsernameAttributeProvider usernameAttributeProvider = this.getUsernameAttributeProvider();
final String username = usernameAttributeProvider.getUsernameFromQuery(query);
//Execute the query in the subclass
final List<IPersonAttributes> unmappedPeople = this.getPeopleForQuery(queryBuilder, username);
if (unmappedPeople == null) {
return null;
}
//Map the attributes of the found people according to resultAttributeMapping if it is set
final Set<IPersonAttributes> mappedPeople = new LinkedHashSet<IPersonAttributes>();
for (final IPersonAttributes unmappedPerson : unmappedPeople) {
final IPersonAttributes mappedPerson = this.mapPersonAttributes(unmappedPerson);
mappedPeople.add(mappedPerson);
}
return Collections.unmodifiableSet(mappedPeople);
}
第二行: final QB queryBuilder = this.generateQuery(query); 字面意思就已經很清晰了 生成查詢語句
protected final QB generateQuery(Map<String, List<Object>> query) {
QB queryBuilder = null;
if (this.queryAttributeMapping != null) {
for (final Map.Entry<String, Set<String>> queryAttrEntry : this.queryAttributeMapping.entrySet()) {
final String queryAttr = queryAttrEntry.getKey();
final List<Object> queryValues = query.get(queryAttr);
if (queryValues != null ) {
final Set<String> dataAttributes = queryAttrEntry.getValue();
if (dataAttributes == null) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Adding attribute '" + queryAttr + "' with value '" + queryValues + "' to query builder '" + queryBuilder + "'");
}
queryBuilder = this.appendAttributeToQuery(queryBuilder, null, queryValues);
}
else {
for (final String dataAttribute : dataAttributes) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Adding attribute '" + dataAttribute + "' with value '" + queryValues + "' to query builder '" + queryBuilder + "'");
}
queryBuilder = this.appendAttributeToQuery(queryBuilder, dataAttribute, queryValues);
}
}
}
else if (this.requireAllQueryAttributes) {
this.logger.debug("Query " + query + " does not contain all nessesary attributes as specified by queryAttributeMapping " + this.queryAttributeMapping + ", null will be returned for the queryBuilder");
return null;
}
}
}
else if (this.useAllQueryAttributes) {
for (final Map.Entry<String, List<Object>> queryAttrEntry : query.entrySet()) {
final String queryKey = queryAttrEntry.getKey();
final List<Object> queryValues = queryAttrEntry.getValue();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Adding attribute '" + queryKey + "' with value '" + queryValues + "' to query builder '" + queryBuilder + "'");
}
queryBuilder = this.appendAttributeToQuery(queryBuilder, queryKey, queryValues);
}
}
if (this.logger.isDebugEnabled()) {
this.logger.debug("Generated query builder '" + queryBuilder + "' from query Map " + query + ".");
}
return queryBuilder;
}
大致意思就是如何配置了queryAttributeMapping字段 就按配置的來處理,沒有就使用默認的,重點在
queryBuilder = this.appendAttributeToQuery(queryBuilder, dataAttribute, queryValues);
appendAttributeToQuery方法的實現 同樣在其子類 也就是AbstractJdbcPersonAttributeDao類中
protected PartialWhereClause appendAttributeToQuery(PartialWhereClause queryBuilder, String dataAttribute, List<Object> queryValues) {
for (final Object queryValue : queryValues) {
final String queryString = queryValue != null ? queryValue.toString() : null;
if (StringUtils.isNotBlank(queryString)) {
if (queryBuilder == null) {
queryBuilder = new PartialWhereClause();
}
else if (queryBuilder.sql.length() > 0) {
queryBuilder.sql.append(" ").append(this.queryType.toString()).append(" ");
}
//Convert to SQL wildcard
final Matcher queryValueMatcher = IPersonAttributeDao.WILDCARD_PATTERN.matcher(queryString);
final String formattedQueryValue = queryValueMatcher.replaceAll("%");
queryBuilder.arguments.add(formattedQueryValue);
if (dataAttribute != null) {
queryBuilder.sql.append(dataAttribute);
if (formattedQueryValue.equals(queryString)) {
queryBuilder.sql.append(" = ");
}
else {
queryBuilder.sql.append(" LIKE ");
}
}
queryBuilder.sql.append("?");
}
}
return queryBuilder;
}
其中final Matcher queryValueMatcher = IPersonAttributeDao.WILDCARD_PATTERN.matcher(queryString);
根據queryString 生成匹配器 而queryString 是對 queryValues的循環 queryValues呢 當然得看generateQuery方法咯
final String queryAttr = queryAttrEntry.getKey();
final List<Object> queryValues = query.get(queryAttr);
是不是很明瞭了 ,其實就是取之前生成的seed 對應的key的值,但是我們發現 之前生成的是個單key的map 也就是說 這裏 我們只可能獲取到一個key的value值
其實這幾個方法都是爲了拼裝我們需要的查詢sql,對於queryAttributeMapping是一個Map<String,Set<String>>類型的集合
首先是循環這個map 根據我們的配置
<property name= "queryAttributeMapping" >
<map>
<entry key= "username" value ="USER_LOGIN_NAME" />
</map>
</property>
拿到queryAttr 應該就爲username了,接着在query中獲取key爲username的值 結合上面說的 這裏配置多個queryAttributeMapping其實起作用的也就key爲username的
雖然query爲Map<String,List<Object>> 類型 通過它得到的value爲List<Object> 由於set的無序的我們無法確定 生成的sql會按照我們配置的循序 ,怎麼說呢
在我理解是 我們只能配置一個查詢key爲username value可以配置多個,對應query中list同樣可以多個 但是沒法確定順序 可以set中第一個字段對應list中的第一個字段
也可能set中的第一個字段對應list中的第二個字段 這種不確定性 導致我們沒法通過這種方式來配置複雜的查詢sql
以上純屬個人理解,如有不對還望指出,不想誤導他人。
說了這麼多,其實就是爲了證明以這種方式有時候沒法實現我們需要的業務、。。。。。。。。。。。。。。
不過通過上面的分析,起碼我們知道爲什麼sql需要以{0} 這種方式作爲佔位符,以及queryAttributeMapping中 key的值 爲什麼只能是username
接下來纔是進入正題,怎樣實現這種複雜的登錄查詢呢
其實很簡單,直接替換掉這種拼裝sql的方式
當然入口還是從用戶憑證開始,自定義用戶憑證,之前文章已經說過了,這裏我們只簡單對getId方法重寫就行了
public String getId(){
return getUsername()+","+getRequestUrl();
}
比如我這個,就是由用戶名和請求域名來進行處理
接着,自定義我們的primaryPrincipalResolver 也很簡單
public class CustomPrincipalResolver implements PrincipalResolver {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private boolean returnNullIfNoAttributes = false;
@NotNull
private IPersonAttributeDao attributeRepository = new StubPersonAttributeDao(new HashMap<String, List<Object>>());
private String principalAttributeName;
@Override
public boolean supports(final Credential credential) {
return true;
}
@Override
public final Principal resolve(final Credential credential) {
logger.debug("Attempting to resolve a principal...");
String tempPrincipal = extractPrincipalId(credential);
String principalId = tempPrincipal.split(",")[0];
// String principalId = extractPrincipalId(credential);
if (principalId == null) {
logger.debug("Got null for extracted principal ID; returning null.");
return null;
}
logger.debug("Creating SimplePrincipal for [{}]", principalId);
final IPersonAttributes personAttributes = this.attributeRepository.getPerson(tempPrincipal);
final Map<String, List<Object>> attributes;
if (personAttributes == null) {
attributes = null;
} else {
attributes = personAttributes.getAttributes();
}
if (attributes == null & !this.returnNullIfNoAttributes) {
return new SimplePrincipal(principalId);
}
if (attributes == null) {
return null;
}
final Map<String, Object> convertedAttributes = new HashMap<String, Object>();
for (final String key : attributes.keySet()) {
final List<Object> values = attributes.get(key);
if (key.equalsIgnoreCase(this.principalAttributeName)) {
if (values.isEmpty()) {
logger.debug("如果爲空,設置默認值", this.principalAttributeName, principalId);
} else {
principalId = values.get(0).toString();
logger.debug(
"找到需要的屬性,移除",
principalId,
this.principalAttributeName);
}
} else {
convertedAttributes.put(key, values.size() == 1 ? values.get(0) : values);
}
}
return new SimplePrincipal(principalId, convertedAttributes);
}
public final void setAttributeRepository(final IPersonAttributeDao attributeRepository) {
this.attributeRepository = attributeRepository;
}
public void setReturnNullIfNoAttributes(final boolean returnNullIfNoAttributes) {
this.returnNullIfNoAttributes = returnNullIfNoAttributes;
}
public void setPrincipalAttributeName(final String attribute) {
this.principalAttributeName = attribute;
}
protected String extractPrincipalId(final Credential credential) {
return credential.getId();
}
}
唯一改動就是對 credential.getId()的返回值進行解析
然後getPerson方法中傳入我們需要的組合值
當然對於getPerson方法也得重寫啦
自定義JdbcPersonAttributeDao類 CustomSingleRowJdbcPersonAttributeDao繼承AbstractJdbcPersonAttributeDao類
代碼參考如下:
public class CustomSingleRowJdbcPersonAttributeDao extends
AbstractJdbcPersonAttributeDao<Map<String, Object>> {
private static final Logger logger = LoggerFactory.getLogger(CustomSingleRowJdbcPersonAttributeDao.class);
private static final ParameterizedRowMapper<Map<String, Object>> MAPPER = new ColumnMapParameterizedRowMapper(
true);
private final SimpleJdbcTemplate simpleJdbcTemplate;
private final String queryTemplate;
public CustomSingleRowJdbcPersonAttributeDao(DataSource ds,
String queryTemplate) {
super(ds, queryTemplate);
this.simpleJdbcTemplate = new SimpleJdbcTemplate(ds);
this.queryTemplate = queryTemplate;
// TODO Auto-generated constructor stub
}
@Override
protected List<IPersonAttributes> parseAttributeMapFromResults(
List<Map<String, Object>> queryResults, String queryUserName) {
// TODO Auto-generated method stub
final List<IPersonAttributes> peopleAttributes = new ArrayList<IPersonAttributes>(
queryResults.size());
for (final Map<String, Object> queryResult : queryResults) {
final Map<String, List<Object>> multivaluedQueryResult = MultivaluedPersonAttributeUtils
.toMultivaluedMap(queryResult);
final IPersonAttributes person;
if (queryUserName != null) {
person = new CaseInsensitiveNamedPersonImpl(queryUserName,
multivaluedQueryResult);
} else {
// Create the IPersonAttributes doing a best-guess at a userName
// attribute
final String userNameAttribute = this
.getConfiguredUserNameAttribute();
person = new CaseInsensitiveAttributeNamedPersonImpl(
userNameAttribute, multivaluedQueryResult);
}
peopleAttributes.add(person);
}
return peopleAttributes;
}
@Override
protected ParameterizedRowMapper<Map<String, Object>> getRowMapper() {
// TODO Auto-generated method stub
return MAPPER;
}
@Override
public IPersonAttributes getPerson(String uid) {
Validate.notNull(uid, "uid may not be null.");
// Run the query using the seed
final Set<IPersonAttributes> people = this
.getPeopleWithMultivaluedAttributesNew(uid);
// Ensure a single result is returned
IPersonAttributes person = (IPersonAttributes) DataAccessUtils
.singleResult(people);
if (person == null) {
return null;
}
// Force set the name of the returned IPersonAttributes if it isn't
// provided in the return object
if (person.getName() == null) {
person = new NamedPersonImpl(uid, person.getAttributes());
}
return person;
}
public final Set<IPersonAttributes> getPeopleWithMultivaluedAttributesNew(
String query) {
Validate.notNull(query, "query may not be null.");
// Get the username from the query, if specified
final String username = query.split(",")[0];
// Execute the query in the subclass
final List<IPersonAttributes> unmappedPeople = this
.getPeopleForQueryNew(query, username);
if (unmappedPeople == null) {
return null;
}
// Map the attributes of the found people according to
// resultAttributeMapping if it is set
final Set<IPersonAttributes> mappedPeople = new LinkedHashSet<IPersonAttributes>();
for (final IPersonAttributes unmappedPerson : unmappedPeople) {
final IPersonAttributes mappedPerson = this
.mapPersonAttributes(unmappedPerson);
mappedPeople.add(mappedPerson);
}
return Collections.unmodifiableSet(mappedPeople);
}
protected List<IPersonAttributes> getPeopleForQueryNew(String query,
String queryUserName) {
// Execute the query
final ParameterizedRowMapper rowMapper = this.getRowMapper();
List results = this.simpleJdbcTemplate.query(this.queryTemplate,
rowMapper, query.split(","));
//List results = new ArrayList();
/*byte[] resultByteArr = this.getRedisManager().get(SerializeUtils.serialize(query));
if(resultByteArr!=null){
results = (List) SerializeUtils.deserialize(resultByteArr);
logger.debug("user is select from redis");
}else{
results = this.simpleJdbcTemplate.query(this.queryTemplate,
rowMapper, query.split(","));
this.getRedisManager().set(SerializeUtils.serialize(query), SerializeUtils.serialize(results),18000000);
logger.debug("user is select from DB");
} */
return this.parseAttributeMapFromResults(results, queryUserName);
}
}
很簡單,就是對幾個方法的重寫,首先是重寫getPerson方法
final Set<IPersonAttributes> people = this
.getPeopleWithMultivaluedAttributesNew(uid);
getPeopleWithMultivaluedAttributesNew方法爲自定義方法 可以看到 和源碼的差距就是去掉了generateQuery一步
自定義了getPeopleForQuery方法
而我們sql中需要的參數值 都包含在query中 也就是credential.getId()的返回值
到此對登錄成功以後查詢用戶信息的複雜改造完結。