`
fantasyeye
  • 浏览: 64157 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

Spring Security 3.1.4 版本开发解读

    博客分类:
  • Java
阅读更多

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 对象,第三个参数就是该访问路径对应的角色集合。然后可以根据 用户角色和菜单角色对比,判断该用户是否具有该路径的访问资格。如果没有,抛出异常。

 

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

附件为整理时画的图。

 

  • 大小: 57.8 KB
分享到:
评论
1 楼 w156445045 2013-10-26  
请问下SpringSecurity 的版本需要跟Spring的版本统一嘛?谢谢。
还有 您有相关版本的中文文档嘛,谢谢。

相关推荐

    linux基础进阶笔记

    linux基础进阶笔记,配套视频:https://www.bilibili.com/list/474327672?sid=4493093&spm_id_from=333.999.0.0&desc=1

    IMG20241115211541.jpg

    IMG20241115211541.jpg

    Sen2_ARI_median.txt

    GEE训练教程——Landsat5、8和Sentinel-2、DEM和各2哦想指数下载

    毕业设计&课设_基于 flask-whoosh-jieba 的代码,涉及文件管理及问题修复.zip

    该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

    基于springboot家政预约平台源码数据库文档.zip

    基于springboot家政预约平台源码数据库文档.zip

    Ucharts添加stack和折线图line的混合图

    Ucharts添加stack和折线图line的混合图

    基于springboot员工在线餐饮管理系统源码数据库文档.zip

    基于springboot员工在线餐饮管理系统源码数据库文档.zip

    2015-2021年新能源汽车分地区、分类型、分级别销量逐月数据和进出口数据-最新出炉.zip

    新能源汽车进出口数据 1、时间跨度:2018-2020年 2、指标说明:包含如下指标的进出口数据:混合动力客车(10座及以上)、纯电动客车(10座及以上)、非插电式混合动力乘用车、插电式混合动力乘用车、纯电动乘用车 二、新能源汽车进出口月销售数据(分地区、分类型、分 级别) 1、数据来源:见资料内说明 2、时间跨度:2014年1月-2021年5月 4、指标说明: 包含如下指标 2015年1月-2021年5月新能源乘用车终端月度销量(分类型)部分内容如下: 新能源乘用车(单月值、累计值 )、插电式混合动力 月度销量合计(狭义乘用车轿车、SUV、MPV、交叉型乘用车); 月度销量同比增速(狭义乘用车轿车、SUV、MPV、交叉型乘用车); 累计销量合计(狭义乘用车轿车、SUV、IPV、交叉型乘用车); 累计销量同比增速(狭义乘用车轿车、SUV、MPV、交叉型乘用车); 累计结构变化(狭义乘用车轿车、SUV、IPV、交叉型乘用车); 2015年1月-2021年5月新能源乘用车终端月度销量(分地区)内容如下: 更多见资源内

    中心主题-241121215200.pdf

    中心主题-241121215200.pdf

    蓝奏云下载链接与密码整理

    内容概要:本文档提供了多个蓝奏云下载链接及其对应解压密码,帮助用户快速获取所需文件。 适合人群:需要从蓝奏云下载文件的互联网用户。 使用场景及目标:方便地记录并分享蓝奏云上文件的下载地址和密码,提高下载效率。 阅读建议:直接查看并使用提供的链接和密码即可。若遇到失效情况,请尝试联系上传者确认更新后的链接。

    Javaweb仓库管理系统项目源码.zip

    基于Java web 实现的仓库管理系统源码,适用于初学者了解Java web的开发过程以及仓库管理系统的实现。

    Python-文件重命名-自定义添加文字-重命名

    资源名称:Python-文件重命名-自定义添加文字-重命名 类型:windows—exe可执行工具 环境:Windows10或以上系统 功能: 1、点击按钮 "源原文"【浏览】表示:选择重命名的文件夹 2、点击按钮 "保存文件夹"【浏览】表示:保存的路径(为了方便可选择保存在 源文件中 ) 3、功能①:在【头部】添加自定义文字 4、功能②:在【尾部】添加自定义文字 5、功能③:输入源字符 ;输入替换字符 可以将源文件中的字符替换自定义的 6、功能④:自动加上编号_1 _2 _3 优点: 1、非常快的速度! 2、已打包—双击即用!无需安装! 3、自带GUI界面方便使用!

    JDK8安装包,为各位学习的朋友免费提供

    JDK8安装包

    Centos-7yum的rpm包

    配合作者 一同使用 作者地址没有次下载路径 https://blog.csdn.net/weixin_52372189/article/details/127471149?fromshare=blogdetail&sharetype=blogdetail&sharerId=127471149&sharerefer=PC&sharesource=weixin_45375332&sharefrom=from_link

    setup_python_geospatial_analysis.ipynb

    GEE训练教程

    毕业设计&课设_文成公主微信公众号全栈工程,含技术栈、架构及部署流程等内容.zip

    该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

    基于springboot交通感知与车路协同系统源码数据库文档.zip

    基于springboot交通感知与车路协同系统源码数据库文档.zip

    基于springboot+vue 雅妮电影票购买系统源码数据库文档.zip

    基于springboot+vue 雅妮电影票购买系统源码数据库文档.zip

    使用 HTML5 实现拖放交互:音效与提示功能的完整实现

    为了更好地理解 HTML5 的拖放功能,我们设计了一个简单有趣的示例:将水果从水果区拖放到购物笼中,实时更新数量和价格,并在所有水果被成功放置后,播放音效并显示提示。

    毕业设计&课设_基于 SSM 的大学生综合成绩测评系统(含信息及数据库脚本,体现系统架构及功能设计).zip

    该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过严格测试运行成功才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

Global site tag (gtag.js) - Google Analytics