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



到此對登錄成功以後查詢用戶信息的複雜改造完結。






發佈了44 篇原創文章 · 獲贊 34 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章