前面粗略的介绍了对于返回更多信息的简单配置,但是有些时候业务的需求 不是简单的查询语句就能解决,比如说我查询用户信息需要结合登录凭证中的多个字段
例如:我们有个项目的登录用户确定是根据访问域名和登录名同时确定的,这样就不得不进行拓展。
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()的返回值
到此对登录成功以后查询用户信息的复杂改造完结。