cas自定义primaryPrincipalResolver,拓展对用户返回信息复杂查询的支持


前面粗略的介绍了对于返回更多信息的简单配置,但是有些时候业务的需求  不是简单的查询语句就能解决,比如说我查询用户信息需要结合登录凭证中的多个字段

例如:我们有个项目的登录用户确定是根据访问域名和登录名同时确定的,这样就不得不进行拓展。


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()的返回值



到此对登录成功以后查询用户信息的复杂改造完结。






你可能感兴趣的:(cas自定义primaryPrincipalResolver,拓展对用户返回信息复杂查询的支持)