spring中的acegi

spring中的Acegi

Acegi作为web项目的一种资源保护安全机制,已经得到广泛应用。配置也十分简单,受先在web.xml加入下段代码,为其工作提供入口
<!-- Acegi Filters-->
	 <filter>
		<filter-name>Acegi Filter Chain Proxy</filter-name>
		<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
		<init-param>
			<param-name>targetClass</param-name>
			<param-value>  
             org.acegisecurity.util.FilterChainProxy
            </param-value>
		</init-param>
	</filter>

	<filter-mapping>
		<filter-name>Acegi Filter Chain Proxy</filter-name>
		<url-pattern>*.do</url-pattern>
	</filter-mapping>

只要url已.do结尾的都将进入org.acegisecurity.util.FilterToBeanProxy这个类里,进入acegi认证体系。
首先看一下doInit()方法
if ((targetBean != null) && (ctx.containsBean(targetBean))) {
	beanName = targetBean; 
} else {
	if (targetBean != null) {
		throw new ServletException("targetBean '" + targetBean + "' not found in context");
	}
	String targetClassString = this.filterConfig.getInitParameter("targetClass");

	if ((targetClassString == null) || ("".equals(targetClassString))) {
		throw new ServletException("targetClass or targetBean must be specified");
	}

	Class targetClass;
	try
	{
		targetClass = Thread.currentThread().getContextClassLoader().loadClass(targetClassString);
	} catch (ClassNotFoundException ex) {
		throw new ServletException("Class of type " + targetClassString + " not found in classloader");
	}

	Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, targetClass, true, true);

	if (beans.size() == 0) {
		throw new ServletException("Bean context must contain at least one bean of type " + targetClassString);
	}

	beanName = (String)beans.keySet().iterator().next();
}

Object object = ctx.getBean(beanName);

if (!(object instanceof Filter)) {
	throw new ServletException("Bean '" + beanName + "' does not implement javax.servlet.Filter");
}

this.delegate = ((Filter)object);
由上面代码可知,我们使用参数targetClass来指定我们的delegate对象。
由web.xml配置可知delegate为org.acegisecurity.util.FilterChainProxy对象,而正是这个bean是在我们的spring中所配置的。
<bean id="filterChainProxy"
		class="org.acegisecurity.util.FilterChainProxy">
		<property name="filterInvocationDefinitionSource">
			<value>
				CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
				PATTERN_TYPE_APACHE_ANT
				/**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
			</value>
		</property>
	</bean>
其中filterInvocationDefinitionSource是主要进行认证的类,组合在FilterChainProxy中,这些bean也配在spring中。
来看一下FilterChainProxy的dofilter方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
	FilterInvocation fi = new FilterInvocation(request, response, chain);

	ConfigAttributeDefinition cad = this.filterInvocationDefinitionSource.getAttributes(fi);

	if (cad == null) {
		if (logger.isDebugEnabled()) {
			logger.debug(fi.getRequestUrl() + " has no matching filters");
		}

		chain.doFilter(request, response);

		return;
	}

	Filter[] filters = obtainAllDefinedFilters(cad);

	if (filters.length == 0) {
		if (logger.isDebugEnabled()) {
		  logger.debug(fi.getRequestUrl() + " has an empty filter list");
		}

		chain.doFilter(request, response);

		return;
	}

	VirtualFilterChain virtualFilterChain = new VirtualFilterChain(fi, filters);
	virtualFilterChain.doFilter(fi.getRequest(), fi.getResponse());
}
 obtainAllDefinedFilters(cad)将所有的filter加载到一个chain里,然后每个filter进行验证,下面是filter的spring配置
<bean id="httpSessionContextIntegrationFilter"
		class="org.acegisecurity.context.HttpSessionContextIntegrationFilter" />

	<bean id="logoutFilter"
		class="org.acegisecurity.ui.logout.LogoutFilter">
		<constructor-arg value="/pages/jsp/loginH.jsp" />
		<constructor-arg>
			<list>
				<bean
					class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler" />
			</list>
		</constructor-arg>
		<property name="filterProcessesUrl" value="/j_acegi_logout"/>
		
	</bean>

	<bean id="authenticationProcessingFilter"
		class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
		<property name="authenticationManager" ref="authenticationManager" />
		<property name="authenticationFailureUrl" value="/pages/jsp/loginH.jsp?loginerror=1" />
		<property name="defaultTargetUrl" value="/index.do" />
		<property name="filterProcessesUrl" value="/j_acegi_security_check" />
	</bean>

	<bean id="securityContextHolderAwareRequestFilter"
		class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter" />

	<bean id="anonymousProcessingFilter"
		class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
		<property name="key" value="changeThis" />
		<property name="userAttribute"
			value="anonymousUser,ROLE_ANONYMOUS" />
	</bean>
	
	<bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
        <property name="key" value="changeThis"/>
	</bean>

	<bean id="exceptionTranslationFilter"
		class="org.acegisecurity.ui.ExceptionTranslationFilter">
		<property name="authenticationEntryPoint">
			<bean
				class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
				<property name="loginFormUrl" value="/pages/jsp/loginH.jsp" />
			</bean>
		</property>
		<property name="accessDeniedHandler">
			<bean
				class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
				<property name="errorPage" value="/pages/jsp/accessDenied.jsp" />
			</bean>
		</property>
	</bean>

	<bean id="filterInvocationInterceptor"
		class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
		<property name="authenticationManager"
			ref="authenticationManager" />
		<property name="accessDecisionManager"
			ref="accessDecisionManager" />
		<!--property name="objectDefinitionSource">
			<value>
			CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
			PATTERN_TYPE_APACHE_ANT		
			/**=ROLE_ANONYMOUS,ROLE_1
			</value>
			</property-->
		<property name="objectDefinitionSource"
			ref="filterDefinitionSource" />
	</bean>
这里先讲一下用户登录授权的filte(rauthenticationProcessingFilter),他的授权行为由已成员authenticationManager来完成。来看其认证方法。
public Authentication attemptAuthentication(HttpServletRequest request)
throws AuthenticationException
{
	String username = obtainUsername(request);
	String password = obtainPassword(request);

	if (username == null) {
		username = "";
	}

	if (password == null) {
		password = "";
	}

	username = username.trim();

	UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);

	request.getSession().setAttribute("ACEGI_SECURITY_LAST_USERNAME", username);

	setDetails(request, authRequest);

	return getAuthenticationManager().authenticate(authRequest);
}
authenticationManager完整配置如下
<bean id="authenticationManager"
		class="org.acegisecurity.providers.ProviderManager">
		<property name="providers">
			<list>		<!-- 一般用户认证 -->
				
				<ref local="anonymousAuthenticationProvider"/>
				<ref local="daoAuthenticationProvider" />
			</list>
		</property>
	</bean>
<bean id="anonymousAuthenticationProvider" class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
        <property name="key" value="changeThis"/>
	</bean>
<bean id="daoAuthenticationProvider"
		class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
		<property name="userDetailsService" ref="userDetailsService" />
		<!-- UserCache property will activate the cache, it is not 
			mandatory but increases performance by cacheing the user 
			details retrieved from user-base -->
		<property name="userCache" ref="userCache" />
		<property name="passwordEncoder" ref="passwordEncoder" />
	</bean>
<!-- 使用Md5算法加密 -->
	<bean id="passwordEncoder"
		class="org.acegisecurity.providers.encoding.Md5PasswordEncoder" />


	<bean id="userDetailsService"
		class="com.impl.CustomUserDetailsServiceImpl">
		<!--  property name="userIdIncluded" value="true" /-->
		<property name="dataSource" ref="dataSource" />
		<property name="usersByUsernameQuery">
			<value>
				SELECT USER_LOGINNAME,USER_LOGINPWD,1 FROM dfl_user WHERE USER_LOGINNAME=?
				AND USER_STATUS='1'
			</value>
		</property>
		<property name="authoritiesByUsernameQuery">
			<value>
				SELECT u.USER_LOGINNAME,r.CODE from dfl_user u, SYS_ROLES r,
				SYS_USER_ROLE ur WHERE u.user_id=ur.USER_ID and r.id=ur.ROLE_ID
				and u.USER_LOGINNAME = ?
			</value>
		</property>
	</bean>
	<bean id="userCache"
		class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
		<property name="cache">
			<bean
				class="org.springframework.cache.ehcache.EhCacheFactoryBean">
				<property name="cacheManager">
					<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />
				</property>
				<property name="cacheName" value="userCache" />
			</bean>
		</property>
	</bean>
可见,authenticationManager也只是所有认证方法的聚合,具体使用哪种认证方式由providers决定,这里配置了两种(一种是普遍的查数据库方式还有一种是匿名方式)

说一下数据库操作的认证方式
使用的是org.acegisecurity.providers.dao.DaoAuthenticationProvider,其方法为
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
	Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));

	String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();

	boolean cacheWasUsed = true;
	UserDetails user = this.userCache.getUserFromCache(username);

	if (user == null) {
		cacheWasUsed = false;
		try {
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
		} catch (UsernameNotFoundException notFound) {
		if (this.hideUserNotFoundExceptions) {
			throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
		}

			throw notFound;
		}

		Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
	}

	this.preAuthenticationChecks.check(user);
	try {
		additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
	} catch (AuthenticationException exception) {
		if (cacheWasUsed) {
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
		} else {
			throw exception;
		}
	}

	this.postAuthenticationChecks.check(user);

	if (!(cacheWasUsed)) {
		this.userCache.putUserInCache(user);
	}

	Object principalToReturn = user;

	if (this.forcePrincipalAsString) {
		principalToReturn = user.getUsername();
	}

	return createSuccessAuthentication(principalToReturn, authentication, user);
}
该方法分别从cache和数据库里拿user信息,之后比对password,成功返回认证成功,失败则抛出异常。
在这里retrieveUser方法变得十分重要,他负责获取数据库的user信息
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException
{
	UserDetails loadedUser;
try {
	loadedUser = getUserDetailsService().loadUserByUsername(username);
} catch (DataAccessException repositoryProblem) {
	throw new AuthenticationServiceException(repositoryProblem.getMessage(), repositoryProblem);
}

if (loadedUser == null) {
	throw new AuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
}

	return loadedUser;
}
Acegi提供UserDetails接口给我们实现,所有与数据库的交互将交给UserDetails的实现类去完成。上诉方法通过调用loadUserByUsername获得user

下面是UserDetailsServiceImpl的部分代码
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException
{
	List users = this.usersByUsernameMapping.execute(username);

	if (users.size() == 0) {
		throw new UsernameNotFoundException("User not found");
	}

	User user = (User)users.get(0);

	List dbAuths = this.authoritiesByUsernameMapping.execute(user.getUsername());

	addCustomAuthorities(user.getUsername(), dbAuths);
	if (dbAuths.size() == 0) {
		throw new UsernameNotFoundException("User has no GrantedAuthority");
	}

	GrantedAuthority[] arrayAuths = (GrantedAuthority[])dbAuths.toArray(new GrantedAuthority[dbAuths.size()]);

	User retUser = null;
	if (!(this.userIdIncluded))
		retUser = new User(user.getUsername(), user.getPassword(), user.isEnabled(), true, true, true, arrayAuths);
	else
		retUser = new User(user.getUserId(), user.getUsername(), user.getPassword(), user.isEnabled(), true, true, true, arrayAuths);
	Map attributes = new HashMap();
	addCustomAttributes(user.getUsername(), attributes);
	if (attributes.size() > 0) {
		retUser.setAttributes(attributes);
	}
	return retUser;
}
这里比较简单使用jdbc方式,MappingSqlQuery来获取数据库记录,之后返回org.acegisecurity.userdetails.User即可。

filterInvocationInterceptor使用来保证url反问的安全性
url访问的可行性由accessDecisionManager来处理,其实accessDecisionManager也是各类策略的集合,真正决定url是否可以访问由单个决策类来完成。
<bean id="accessDecisionManager"
		class="org.acegisecurity.vote.AffirmativeBased">
		<property name="allowIfAllAbstainDecisions" value="false" />
		<property name="decisionVoters">
			<list>
				<bean class="org.acegisecurity.vote.RoleVoter" />
				<bean class="org.acegisecurity.vote.AuthenticatedVoter" />
			</list>
		</property>
	</bean>
可以先看一下org.acegisecurity.vote.AffirmativeBased的部分代码,代码遍历每个voter类,调用他们的vote方法。返回1则成功,-1则表示拒绝访问。
public void decide(Authentication authentication, Object object, ConfigAttributeDefinition config)
throws AccessDeniedException
{
	Iterator iter = getDecisionVoters().iterator();
	int deny = 0;

	while (iter.hasNext()) {
		AccessDecisionVoter voter = (AccessDecisionVoter)iter.next();
		int result = voter.vote(authentication, object, config);

		switch (result) {
			case 1:
				return;
			case -1:
				++deny;
		}

	}

	if (deny > 0) {
		throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
	}

	checkAllowIfAllAbstainDecisions();
}
filterInvocationInterceptor权限资源的获取
ConfigAttributeDefinition attr = obtainObjectDefinitionSource().getAttributes(object);
而obtainObjectDefinitionSource正是我们需要实现从数据库获取权限信息的类,先看一下acegi提供的父类
public abstract class AbstractFilterInvocationDefinitionSource
implements FilterInvocationDefinitionSource
{
	public ConfigAttributeDefinition getAttributes(Object object)
	throws IllegalArgumentException
	{
		if ((object == null) || (!(supports(object.getClass())))) {
		throw new IllegalArgumentException("Object must be a FilterInvocation");
	}

		String url = ((FilterInvocation)object).getRequestUrl();

		return lookupAttributes(url);
	}

	public abstract ConfigAttributeDefinition lookupAttributes(String paramString);

	public boolean supports(Class clazz)
	{
	return FilterInvocation.class.isAssignableFrom(clazz);
	}
}
由此可见只需实现lookupAttributes方法即可,这里有使用者自行实现,在最后返回ConfigAttributeDefinition的类型即可。















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值