Spring Security 3.1.4 版本开发解读

Spring Security 是一个能够为基于 Spring 的企业应用系统提供描述性安全访问控制解决方案的安全框架。由于本人今天对此框架学习了一番,为了保留学习成果,提供以后开发使用。在这里对此开发配置流程,进行详细说明记录。

结合使用此框架需要引入如下 Jar 包,由于本人使用 maven 结构工程,只提供 POM 方式的配置引入:

<!-- 版本号 -->
<spring.security.version>3.1.4.RELEASE</spring.security.version>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-core</artifactId>
	<version>${spring.security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>${spring.security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-acl</artifactId>
	<version>${spring.security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-openid</artifactId>
	<version>${spring.security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-ldap</artifactId>
	<version>${spring.security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>${spring.security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-crypto</artifactId>
	<version>${spring.security.version}</version>
</dependency>

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>${spring.security.version}</version>
</dependency>

 接下来我们需要配置 web.xml 文件,当然你的工程必须已经是一个引入了 Spring 框架的 WebApp:

<!-- Spring Config File Path -->
<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>classpath:security-config.xml</param-value><!-- 这里配置 权限的配置文件读取地址 -->
</context-param>

<!-- Spring Secutiry 过滤链配置 -->
<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

 web.xml 文件中的过滤器 springSecurityFilterChain 的 <filter-name> 不可以更改。如果随意指定过滤器名称,启动后,会报错:

2013-09-06 13:21:58,992 DEBUG [org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(502)] - No bean named 'SecurityFilterChain' found in org.springframework.beans.factory.support.DefaultListableBeanFactory@2dc0435: defining beans [org.springframework.security.filterChains,org.springframework.security.filterChainProxy,org.springframework.security.web.DefaultSecurityFilterChain#0,org.springframework.security.web.PortMapperImpl#0,org.springframework.security.web.PortResolverImpl#0,org.springframework.security.config.authentication.AuthenticationManagerFactoryBean#0,org.springframework.security.authentication.ProviderManager#0,org.springframework.security.web.context.HttpSessionSecurityContextRepository#0,org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy#0,org.springframework.security.web.savedrequest.HttpSessionRequestCache#0,org.springframework.security.access.vote.AffirmativeBased#0,org.springframework.security.web.access.intercept.FilterSecurityInterceptor#0,org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator#0,org.springframework.security.authentication.AnonymousAuthenticationProvider#0,org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices#0,org.springframework.security.authentication.RememberMeAuthenticationProvider#0,org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint#0,org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#0,org.springframework.security.userDetailsServiceFactory,org.springframework.security.web.DefaultSecurityFilterChain#1,iFilter,org.springframework.security.authentication.dao.DaoAuthenticationProvider#0,org.springframework.security.authentication.DefaultAuthenticationEventPublisher#0,org.springframework.security.authenticationManager,userDetailsManager,accessDecisionManager,securityMetadataSource,passwdEcoder]; root of factory hierarchy
2013-9-6 13:21:58 org.apache.catalina.core.StandardContext filterStart
严重: Exception starting filter SecurityFilterChain
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'SecurityFilterChain' is defined
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:504)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1041)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:273)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:193)
	at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1008)
	at org.springframework.web.filter.DelegatingFilterProxy.initDelegate(DelegatingFilterProxy.java:217)
	at org.springframework.web.filter.DelegatingFilterProxy.initFilterBean(DelegatingFilterProxy.java:145)
	at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:179)
	at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:295)
	at org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:422)
	at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:115)
	at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4072)
	at org.apache.catalina.core.StandardContext.start(StandardContext.java:4726)
	at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1057)
	at org.apache.catalina.core.StandardHost.start(StandardHost.java:840)
	at org.apache.catalina.core.ContainerBase.start(ContainerBase.java:1057)
	at org.apache.catalina.core.StandardEngine.start(StandardEngine.java:463)
	at org.apache.catalina.core.StandardService.start(StandardService.java:525)
	at org.apache.catalina.core.StandardServer.start(StandardServer.java:754)
	at org.apache.catalina.startup.Catalina.start(Catalina.java:595)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:597)
	at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:289)
	at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:414)
2013-9-6 13:21:58 org.apache.catalina.core.StandardContext start

 此过滤器必须配置,不然无法使用 Spring Security 框架对访问权限的控制。

 此 外,还必须在权限配置文件 security-config.xml 中,配置 <http> 并设定其属性 auto-config="true",才能保证 WebApp 工程正常的启动。下面让我们看一下,我的 security-config.xml 是如何配置的。

<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
 	xmlns:b="http://www.springframework.org/schema/beans"	
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="   
          http://www.springframework.org/schema/beans   
          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
          http://www.springframework.org/schema/security
          http://www.springframework.org/schema/security/spring-security-3.1.xsd">

	<!-- 登录页面不过滤 -->
	<http pattern="/login" security="none"/>

	<http auto-config="true">
		<!-- 登录配置 -->		
		<form-login login-page="/login" authentication-failure-url="/login?err=true" default-target-url="/demo/list" username-parameter="j_username" password-parameter="j_password" login-processing-url="/j_spring_security_check"/>
		
		<!-- 退出配置 -->
		<!-- 
			logout-url:退出请求地址。系统默认:j_spring_security_logout
			logout-success-url:退出成功,跳转地址连接。
			delete-cookies:删除 cookies 内容。
			success-handler-ref:退出回调接口。类需实现接口: LogoutSuccessHandler
			invalidate-session:如果设置为 true,用户的 Session 将会在退出时被失效。
		 -->
		<logout logout-success-url="/index.html" invalidate-session="true"/>
		
		<remember-me />
		
		<!-- 自定义过滤器, 实现用户、角色、权限、资源的数据库管理 -->
		<custom-filter ref="iFilter" before="FILTER_SECURITY_INTERCEPTOR"/>
	
	</http>
	
	<!-- 自定义过滤器 -->
	<b:bean id="iFilter" class="org.lei.core.filter.SecurityInterceptorDemo">
		<b:property name="securityMetadataSource" ref="securityMetadataSource"/><!-- FilterInvocationSecurityMetadataSource 接口实现类 -->
		<b:property name="authenticationManager" ref="authenticationManager"/><!-- 鉴定管理类 -->
		<b:property name="accessDecisionManager" ref="accessDecisionManager"/><!-- AccessDecisionManager 接口实现类 -->
	</b:bean>	
	
	<!-- 鉴定管理类配置信息 -->
	<authentication-manager alias="authenticationManager"><!-- 鉴定管理类 -->
		<authentication-provider user-service-ref="userDetailsManager"><!-- 用户详情管理类 [UserDetailsService 接口 实现类] -->
			<password-encoder ref="passwdEcoder"><!-- 用户加密解密类  -->
				<salt-source user-property="username"/>
			</password-encoder>		
		</authentication-provider>
	</authentication-manager>
	
	<!-- 用户详细信息获取接口 -->
	<b:bean id="userDetailsManager" class="org.lei.core.filter.CustomUserDetailsService"/>
	
	<!-- 访问决策器, 决定某个用户具体的角色,是否有足够的权限访问某个资源 -->
	<b:bean id="accessDecisionManager" class="org.lei.core.filter.CustomAccessDecisionManager"/>
	
	<!-- 资源源数据定义, 将所有的资源和权限的对应关系建立起来,即定义某一资源可以被哪些角色去访问。-->
	<b:bean id="securityMetadataSource" class="org.lei.core.filter.CustomSecurityMetadataSource"/>
	
	<!-- 用户详情管理类使用的加密方式 -->
	<b:bean id="passwdEcoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/><!-- PasswordEncoder 密码接口 -->

</b:beans>

 

关于 xml 配置,这里我将此文件默认的命名空间指定为 xmlns="http://www.springframework.org/schema/security" ,所以我们使用 security 定义的标签不需要添加任何前缀。如:<http>。这里需要注意的是,我使用的 xml 样式是 http://www.springframework.org/schema/security/spring-security-3.1.xsd 版本的。所以和网上大多数配置了 http://www.springframework.org/schema/security/spring-security-3.0.xsd 配置方法会不太一样。如果你按照我的配置设置,出现了报错情况,请检查下自己引用的 xsd 是那个版本的。

<?xml version="1.0" encoding="UTF-8"?>
<b:beans xmlns="http://www.springframework.org/schema/security"
 	xmlns:b="http://www.springframework.org/schema/beans"	
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="   
          http://www.springframework.org/schema/beans   
          http://www.springframework.org/schema/beans/spring-beans-3.0.xsd   
          http://www.springframework.org/schema/security
          http://www.springframework.org/schema/security/spring-security-3.1.xsd">

……

</b:beans>

 

如果你不需要对某些访问路径,进行权限控制。可以配置:

<!-- 登录页面不过滤 -->
<http pattern="/login" security="none"/>

 

配置中,对登录页面的访问,不进行权限控制。如果你还有其他的配置需求,可以继续添加。

现在我们说一说最重要的配置, <http> 标签。其标签中,可以配置很多项目,因为自己研究有限,就只说说我知道的那几块。

<form-login /> 登录表单标签

<!-- 
	login-page:登录页面地址
	authentication-failure-url:登录失败页面地址
	default-target-url:登录成功跳转页面地址
	login-processing-url:登录表单提交地址。系统默认: j_spring_security_check
	username-parameter:表单中,用户名参数提交名称。系统默认: j_username
	password-parameter:表单中,用户密码参数提交名称。系统默认: j_password
 -->
<form-login login-page="/login" authentication-failure-url="/login?err=true" default-target-url="/demo/list" username-parameter="j_username" password-parameter="j_password" login-processing-url="/j_spring_security_check"/>

此类会调用到 UsernamePasswordAuthenticationFilter 类 中的 attemptAuthentication 方法:

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        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);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);

        return this.getAuthenticationManager().authenticate(authRequest);
    }

 如方法中,最先对请求方式的判断。所以提交的表单时,必须以 Post 方式提交。之后,再通过

obtainUsername 和 obtainPassword 方法获取提交的用户名和密码。

public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "j_username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "j_password";

private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;

protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(passwordParameter);
}

protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(usernameParameter);
}

 由上面提供的源码片段,你应该明白些什么了吧。呵呵~

此类的调用在 AbstractAuthenticationProcessingFilter 的 doFilter 方法中。

 

<logout /> 退出登录标签

<!-- 
	logout-url:退出请求地址。系统默认:j_spring_security_logout
	logout-success-url:退出成功,跳转地址连接。
	delete-cookies:删除 cookies 内容。
	success-handler-ref:退出回调接口。类需实现接口: LogoutSuccessHandler
	invalidate-session:如果设置为 true,用户的 Session 将会在退出时被失效。
 -->
<logout logout-success-url="/index.html" invalidate-session="true"/>

 这里我没有做太多研究,基本配置是可以满足日常开发了,若以后有扩充,我到时候再补充吧。

 

重点中的重点来了,如何实现将你设计的用户、权限、角色关系,适用于 Spring Security 框架来帮你管理呢。这里我先把我理解的,此框架的原理描述下:

1、一个 URL 访问地址称为资源,能不能访问这个资源是取决于权限。所以,在 Spring Security 框架中,需要维护一个资源和权限的映射关系,这种关系是 1..n 的,即 一个资源对应多个权限。

2、权限,这里也可以理解为角色。角色必定有自己的角色名称,而对于 Spring Security 要的就是这个角色名称,让它和资源实现映射关系。比如:

角色A和角色B,都可以访问地址 /xxx/list。在 Spring Security 框架中,其维护结构为 key=/xxx/list, value=[角色A, 角色B]。框架可以通过访问地址找到哪些角色可以访问。

3、用户在完成登录后,用户信息中会保存自己拥有的权限,也就是角色。当用户每次产生访问行为时,都会和此访问资源对应的权限比较,如果访问资源存在此角色,即用户可以正常访问,反之报错。

以上大体就是 Spring Security 框架验证的一个过程,下面我们就开始介绍如何使用和开发自定义的验证流程:

第一步,先配置自定义过滤器,在 <http> 标签中。

<!-- 自定义过滤器, 实现用户、角色、权限、资源的数据库管理 -->
<custom-filter ref="iFilter" before="FILTER_SECURITY_INTERCEPTOR"/>

 一看 ref="iFilter",就知道引用了一个类。看下面配置:

<!-- 自定义过滤器 -->
<b:bean id="iFilter" class="org.lei.core.filter.SecurityInterceptorDemo">
	<b:property name="securityMetadataSource" ref="securityMetadataSource"/><!-- FilterInvocationSecurityMetadataSource 接口实现类 -->
	<b:property name="authenticationManager" ref="authenticationManager"/><!-- 鉴定管理类 -->
	<b:property name="accessDecisionManager" ref="accessDecisionManager"/><!-- AccessDecisionManager 接口实现类 -->
</b:bean>

 编写自定义过滤器,必须继承 org.springframework.security.access.intercept.AbstractSecurityInterceptor 和 实现 javax.servlet.Filter 接口。重写 doFilter 方法并新增属性 FilterInvocationSecurityMetadataSource 对象的 getter 和 setter 方法,用于 Spring 注入。

package org.lei.core.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;

/**
 * 
 * @ClassName SecurityInterceptorDemo
 * @Description TODO
 * @author 
 * @date 2013-9-5 上午10:20:11
 * @version 1.0
 *
 */
public class SecurityInterceptorDemo extends AbstractSecurityInterceptor
		implements Filter {
	
	private FilterInvocationSecurityMetadataSource securityMetadataSource;

	/**
	 * @param filterConfig
	 * @throws ServletException
	 */
	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		
	}

	/**
	 * @param request
	 * @param response
	 * @param chain
	 * @throws IOException
	 * @throws ServletException
	 */
	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}
	
	
	public void invoke(FilterInvocation fi) {
		InterceptorStatusToken token = super.beforeInvocation(fi);
		
		try {
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		} catch (Exception e) {
			super.afterInvocation(token, null);
		}
		
	}


	/**
	 * 
	 */
	@Override
	public void destroy() {
	}

	/**
	 * @return
	 */
	@Override
	public Class<? extends Object> getSecureObjectClass() {
		return FilterInvocation.class;
	}

	/**
	 * @return
	 */
	@Override
	public SecurityMetadataSource obtainSecurityMetadataSource() {
		return this.securityMetadataSource;
	}

	public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource securityMetadataSource) {
		this.securityMetadataSource = securityMetadataSource;
	}

}

 

如之前的配置,我们需要 Spring 帮我们注入 securityMetadataSource、authenticationManager、accessDecisionManager 三个类对象。那他们分别是做什么的呢?现在听我慢慢道来。

securityMetadataSource 这个类型的接口,提供了根据访问资源获取角色集合的接口,也就是说此类维护着,资源和角色的关系并提供外界使用。 securityMetadataSource 必须是 FilterInvocationSecurityMetadataSource 接口实现类。看看我写的自定义实现类:

package org.lei.core.filter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import javax.servlet.http.HttpServletRequest;

import org.apache.log4j.Logger;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.AntPathRequestMatcher;
import org.springframework.security.web.util.RequestMatcher;

/**
 * 
 * @ClassName CustomSecurityMetadataSource
 * @Description TODO
 * @author zhuzhonglei
 * @date 2013-9-5 上午10:50:30
 * @version 1.0
 *
 */
public class CustomSecurityMetadataSource implements
		FilterInvocationSecurityMetadataSource {

	/**
     * LOGGER 日志对象
     */
    private final static Logger LOGGER = Logger.getLogger(CustomSecurityMetadataSource.class);
    
    private HashMap<String, Collection<ConfigAttribute>> map = new HashMap<String, Collection<ConfigAttribute>>();
    
	/** 
	 * 加载资源,初始化资源变量
	 * 
	 */
	private void loadResourceDefine() {
		
		Collection<ConfigAttribute> array = new ArrayList<ConfigAttribute>(4);
		
		ConfigAttribute cfg = new SecurityConfig("a1");
		array.add(cfg);
		
		cfg = new SecurityConfig("a2");
		array.add(cfg);
		
		cfg = new SecurityConfig("a3");
		array.add(cfg);
		
		cfg = new SecurityConfig("a4");
		array.add(cfg);
		map.put("/demo/list", array);
		
		array = new ArrayList<ConfigAttribute>(4);
		
		cfg = new SecurityConfig("n1");
		array.add(cfg);
		
		cfg = new SecurityConfig("n2");
		array.add(cfg);
		map.put("/demo/news", array);
		
	}
	
	
	public CustomSecurityMetadataSource() {
		loadResourceDefine();
	}
	
	/**
	 * 根据路径获取访问权限的集合接口
	 * @param object
	 * @return
	 * @throws IllegalArgumentException
	 */
	@Override
	public Collection<ConfigAttribute> getAttributes(Object object)
			throws IllegalArgumentException {
		
		LOGGER.info(object);
		
		HttpServletRequest request = ((FilterInvocation)object).getHttpRequest();
		
		RequestMatcher matcher = null;
		String resUrl = null;
		for (Iterator<String> iter = map.keySet().iterator(); iter.hasNext(); ) {
			resUrl = iter.next();
			matcher = new AntPathRequestMatcher(resUrl);
			if (null != resUrl && matcher.matches(request)) {
				return map.get(resUrl);
			}
		}
		
		return null;
	}

	/**
	 * @return
	 */
	@Override
	public Collection<ConfigAttribute> getAllConfigAttributes() {
		return null;
	}

	/**
	 * @param clazz
	 * @return
	 */
	@Override
	public boolean supports(Class<?> clazz) {
		return true;
	}

}

 

我在 loadResourceDefine 方法中,初始化了资源。将资源和权限以 Map 的形式做了映射。一个地址会对应一组权限。然后实现了接口方法 public Collection<ConfigAttribute> getAttributes(Object object) ,可以通过此方法获取权限集合。这个接口的调用在 AbstractSecurityInterceptor 的 beforeInvocation 方法中。

 

authenticationManager 实现类由 Spring 提供,我们使用配置对其声明:

<!-- 鉴定管理类配置信息 -->
<authentication-manager alias="authenticationManager"><!-- 鉴定管理类 -->
	<authentication-provider user-service-ref="userDetailsManager"><!-- 用户详情管理类 [UserDetailsService 接口 实现类] -->
	        <password-encoder ref="passwdEcoder"><!-- 用户加密解密类  -->
		        <salt-source user-property="username"/>
	        </password-encoder>		
	</authentication-provider>
</authentication-manager>

该配置中 userDetailsManager 和 passwdEcoder。userDetailsManager 是用户登录时,通过此接口获取 UserDetails 接口对象。配置接口实现类:

<!-- 用户详细信息获取接口 -->
<b:bean id="userDetailsManager" class="org.lei.core.filter.CustomUserDetailsService"/>

 自定义 CustomUserDetailsService 类,实现接口 UserDetailsService。

import java.util.ArrayList;

import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

/**
 * 
 * @ClassName CustomUserDetailsService
 * @Description TODO
 * @author 
 * @date 2013-9-5 下午1:19:33
 * @version 1.0
 *
 */
public class CustomUserDetailsService implements UserDetailsService {

	/**
	 * @param username
	 * @return
	 * @throws UsernameNotFoundException
	 * @throws DataAccessException
	 */
	@Override
	public UserDetails loadUserByUsername(String username)
			throws UsernameNotFoundException, DataAccessException {
		
		ArrayList<GrantedAuthority> array = new ArrayList<GrantedAuthority>();
		GrantedAuthority ga = new SimpleGrantedAuthority("a1");
		array.add(ga);
		
		return new User("demo", "8ae29a58361c8b3ec237ae8419df7e58", true, true, true, true, array);
	}

}

 实现类中,我们可以通过 loadUserByUsername 方法,根据用户名找到该用户的基本信息和角色信息。并创建 UserDetails 实现类对象返回。我们在这里设置了角色集合对象 array 并将其赋值给了User 对象。

passwdEcoder 的应用使用类 Spring 提供的 Md5PasswordEncoder,配置如下:

<!-- 用户详情管理类使用的加密方式 -->
<b:bean id="passwdEcoder" class="org.springframework.security.authentication.encoding.Md5PasswordEncoder"/><!-- PasswordEncoder 密码接口 -->

 这个加密如配置一样,使用的加盐方式。将用户名和密码合并进行加密。具体加密方式,看源码得知:

protected String mergePasswordAndSalt(String password, Object salt, boolean strict) {
        if (password == null) {
            password = "";
        }

        if (strict && (salt != null)) {
            if ((salt.toString().lastIndexOf("{") != -1) || (salt.toString().lastIndexOf("}") != -1)) {
                throw new IllegalArgumentException("Cannot use { or } in salt.toString()");
            }
        }

        if ((salt == null) || "".equals(salt)) {
            return password;
        } else {
            return password + "{" + salt.toString() + "}";
        }
    }

 例如:密码为 123456,用户名为 demo, 那就会将 123456{demo} 进行MD5 加密比较。所以,在新增用户或设置密码时,也要按照这样的方式加密存入数据库,不然用户登录这块,密码将验证不通过。

现 在,我们分别实现了 FilterInvocationSecurityMetadataSource 接口和 UserDetailsService 接口。FilterInvocationSecurityMetadataSource 接口实现类维护着资源与权限的映射关系,而 UserDetailsService 接口又维护着将登陆用户信息封装到用户对象中。这时候,当用户需要访问某个资源是,我们就可以通过这两个对象在 accessDecisionManager 引用的 AccessDecisionManager 接口实现类中,进行比较了。

我们先在配置文件中,配置该类:

<!-- 访问决策器, 决定某个用户具体的角色,是否有足够的权限访问某个资源 -->
<b:bean id="accessDecisionManager" class="org.lei.core.filter.CustomAccessDecisionManager"/>

 实现类 CustomAccessDecisionManager :

package org.lei.core.filter;

import java.util.Collection;
import java.util.Iterator;

import org.apache.log4j.Logger;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;

/**
 * 
 * @ClassName CustomAccessDecisionManager
 * @Description TODO
 * @author 
 * @date 2013-9-5 上午11:46:35
 * @version 1.0
 *
 */
public class CustomAccessDecisionManager implements AccessDecisionManager {

	/**
     * LOGGER 日志对象
     */
    private final static Logger LOGGER = Logger.getLogger(CustomAccessDecisionManager.class);
	
	/**
	 * @param authentication
	 * @param object
	 * @param configAttributes
	 * @throws AccessDeniedException
	 * @throws InsufficientAuthenticationException
	 */
	@Override
	public void decide(Authentication authentication, Object object,
			Collection<ConfigAttribute> configAttributes)
			throws AccessDeniedException, InsufficientAuthenticationException {
		
		LOGGER.info("CustomAccessDecisionManager.decide");
		
		if (null == configAttributes || configAttributes.size() <= 0) {
			return;
		}
		
		ConfigAttribute c = null;
		String needRole = null;
		for (Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
			c = iter.next();
			
			needRole = c.getAttribute();
			
			LOGGER.info("菜单访问权限:" + needRole);
			for (GrantedAuthority ga : authentication.getAuthorities()) {
				if (needRole.trim().equals(ga.getAuthority())) {
					return;
				}
			}
		}
		
		throw new AccessDeniedException("结束,没有权限!");
	}

	/**
	 * @param attribute
	 * @return
	 */
	@Override
	public boolean supports(ConfigAttribute attribute) {
		return true;
	}

	/**
	 * @param clazz
	 * @return
	 */
	@Override
	public boolean supports(Class<?> clazz) {
		return true;
	}

}

 decide 方法接受三个参数,第一个使用户拥有的角色,第二个参数就是在 过滤器中新建的 FilterInvocation 对象,第三个参数就是该访问路径对应的角色集合。然后可以根据 用户角色和菜单角色对比,判断该用户是否具有该路径的访问资格。如果没有,抛出异常。

 

以上就是大体的开发流程和基本原理了,在这里做已记录。

附件为整理时画的图。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值