转载自其它网站
Spring Security已经成为Spring Framework下最成熟的安全系统,它为我们提供了强大而灵活的企业级安全服务
目录
(一)Spring Security2.0概述
(二)Security2.0相对于Acegi的使用
(三)Security2.0使用数据库对用户、权限和资源的管理
(四)Spring Security2.0对象的访问
概述
在认识Spring Security之前,所有的权限验证逻辑都混杂在业务逻辑中,用户的每个操作以前可能都需要对用户是否有进行该项操作的权限进行判断,来达到认证授权的目的。类似这样的权限验证逻辑代码被分散在系统的许多地方,难以维护。AOP(Aspect Oriented Programming)和Spring Security为我们的应用程序很好的解决了此类问题,正如系统日志,事务管理等这些系统级的服务一样,我们应该将它作为系统一个单独的"切面"进行管理,以达到业务逻辑与系统级的服务真正分离的目的,Spring Security将系统的安全逻辑从业务中分离出来。
2007年底Acegi Security正式成为Spring Portfolio项目,并更名为Spring Security.Spring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
security2.0相对于1.0配置的简化
spring security 2.0的配置文件可以使用spring2.0的命名空间配置,大大减少了配置量,最显著的一点就是省掉了那个长长的filter串。 使用它,可以通过附加xml架构,为传统的spring beans应用环境语法做补充。 你可以在spring参考文档得到更多信息。 命名空间元素可以简单的配置单个bean,或使用更强大的,定义一个备用配置语法,这可以更加紧密的匹配问题域,隐藏用户背后的复杂性。 简单元素可能隐藏事实,多种bean和处理步骤添加到应用环境中。
1.0时需要先配置一个filter代理,由filter代理来执行acegi其他的功能 filter:
Xml代码
1. <bean id="filterChainProxy"
2. class="org.acegisecurity.util.FilterChainProxy">
3. <property name="filterInvocationDefinitionSource">
4. 所需要的Filter都必须按顺序这样排列号
5. <value>
6. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
7. PATTERN_TYPE_APACHE_ANT
8. /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,
9. exceptionTranslationFilter,filterSecurityInterceptor
10. </value>
11. </property>
12. </bean>
web.xml里配置filter代理:
Xml代码
1. <filter>
2. <filter-name>AcegiFilterChainProxy</filter-name>
3. <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
4. <init-param>
5. <param-name>targetClass</param-name>
6. <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
7. </init-param>
8. </filter>
9. <filter-mapping>
10. <filter-name>AcegiFilterChainProxy</filter-name>
11. <url-pattern>/*</url-pattern>
12. </filter-mapping>
然后再一个一个配置相应的功能filter,相当繁琐。
2.0只需要
Xml代码
1. <security:http auto-config="true">
2. </security:http>
web.xml里:
在 web.xml文件里加入Filter声明 Xml代码
1. <filter>
2. <filter-name>springSecurityFilterChain</filter-name>
3. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
4. </filter>
5. <filter-mapping>
6. <filter-name>springSecurityFilterChain</filter-name>
7. <url-pattern>/*</url-pattern>
8. </filter-mapping>
这个Filter会拦截所有的URL请求,并且对这些URL请求进行Spring Security的验证。 ss就会自动代理几个基本的功能filter(如AuthenticationProcessingFilter),filter也有了默认配置,不必在一个个去配置
注意,springSecurityFilterChain这个名称是由命名空间默认创建的用于处理web安全的一个内部的bean的id。所以你在你的Spring配置文件中,不应该再使用这个id作为你的bean
与Acegi的配置不同,Acegi需要自行声明一个Spring的bean来作为Filter的实现,而使用Spring Security后,无需再额外定义bean,而是使用<http>元素进行配置。
A 使用最小的<http>配置
1. <http auto-config='true'>
2. <intercept-url pattern="/**" access="ROLE_USER" />
3. </http>
这段配置表示:我们要保护应用程序中的所有URL,只有拥有ROLE_USER角色的用户才能访问。你可以使用多个<intercept- url>元素为不同URL的集合定义不同的访问需求,它们会被归入一个有序队列中,每次取出{zx0}匹配的一个元素使用。 所以你必须把期望使用的匹配条件放到最上边。
B 配置 UserDetailsService来指定用户和权限
1. <authentication-provider>
2. <user-service>
3. <user name="downpour" password="downpour" authorities="ROLE_USER, ROLE_ADMIN" />
4. <user name="robbin" password="robbin" authorities="ROLE_USER" />
5. <user name="QuakeWang" password="QuakeWang" authorities="ROLE_ADMIN" />
6. </user-service>
7. </authentication-provider>
在这里,downpour拥有ROLE_USER和ROLE_ADMIN的权限,robbin拥有ROLE_USER权限,QuakeWang拥有 ROLE_ADMIN的权限
C 使用security命名空间需要将spring配置文件的头上加上security命名空间的xsd:
Xml代码
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">
...
</beans>
D 剖析基本配置元素
(1)有关auto-config配置属性
在上面用到的auto-config属性,其实是下面这些配置的缩写:
1. <http>
2. <intercept-url pattern="/**" access="ROLE_USER" />
3. <form-login />
4. <anonymous />
5. <http-basic />
6. <logout />
7. <remember-me />
8. </http>
这些元素分别与登录认证,匿名认证,基本认证,注销处理和remember-me对应。 他们拥有各自的属性,可以改变他们的具体行为。这样,在Acegi中所熟悉的元素又出现了。只是在这里,我们使用的是命名空间而已。
(2)与Acegi比较
a.基于命名空间的配置更加简洁,可维护性更强
例如,基于命名空间进行登录认证的配置代码,可能像这样:
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" default-target-url="/work" />
如果使用老的Acegi的Bean的定义方式,可能像这样:
1. <bean id="authenticationProcessingFilter"
2. class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
3. <property name="authenticationManager"
4. ref="authenticationManager"/>
5. <property name="authenticationFailureUrl"
6. value="/login.jsp?error=1"/>
7. <property name="defaultTargetUrl" value="/work"/>
8. <property name="filterProcessesUrl"
9. value="/j_acegi_security_check"/>
10. <property name="rememberMeServices" ref="rememberMeServices"/>
11. </bean>
b.基于命名空间的配置,我们无需再担心由于过滤器链的顺序而导致的错误
以前,Acegi在缺乏默认内置配置的情况下,你需要自己来定义所有的bean,并指定这些bean在过滤器链中的顺序。一旦顺序错了,很容易发生错误。而现在,过滤器链的顺序被默认指定,你不需要在担心由于顺序的错误而导致的错误。
(3)过滤器链在哪里
在Spring Security中,将看不到"长长"的过滤器链的配置,这些配置都被内置在<http>节点中。这些过滤器已经被Spring容器默认内置注册,这也就是我们不再需要在配置文件中定义那么多bean的原因。 同时,过滤器顺序在使用命名空间的时候是被严格执行的。它们在初始化的时候就预先被排好序。不仅如此,Spring Security规定,你不能替换那些<http>元素自己使用而创建出的过滤器,
****************不过事实上ss的默认实现是很简陋的,提供的demo里都是使用内存数据库,权限配置也都是写死到配置文件里,根本无法在项目中应用,进行相应的扩展是不可避免的。
Spring Security允许你把你自己的过滤器添加到队列中,使用custom-filter元素,并且指定你的过滤器应该出现的位置:<security:custom-filter position="alias"/>,position为相应filter的别名。
对应关系:
CHANNEL_FILTER ChannelProcessingFilter
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter
SESSION_CONTEXT_INTEGRATION_FILTER HttpSessionContextIntegrationFilter
LOGOUT_FILTER LogoutFilter
X509_FILTER X509PreAuthenticatedProcessigFilter
PRE_AUTH_FILTER Subclass of AstractPreAuthenticatedProcessingFilter
CAS_PROCESSING_FILTER CasProcessingFilter
AUTHENTICATION_PROCESSING_FILTER AuthenticationProcessingFilter
BASIC_PROCESSING_FILTER BasicProcessingFilter
SERVLET_API_SUPPORT_FILTER classname
REMEMBER_ME_FILTER RememberMeProcessingFilter
ANONYMOUS_FILTER AnonymousProcessingFilter
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter
NTLM_FILTER NtlmProcessingFilter
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor
SWITCH_USER_FILTER SwitchUserProcessingFilter
比如:
Xml代码
1. <beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter">
2. <custom-filter position="AUTHENTICATION_PROCESSING_FILTER"/>
3. </beans:bean>
不仅如此,你还可以使用after或before属性,如果你想把你的过滤器添加到队列中另一个过滤器的前面或后面。 可以分别在position属性使用"FIRST"或"LAST"来指定你想让你的过滤器出现在队列元素的前面或后面。
这个特性或许能够在一定程度上弥补Spring Security的死板规定,而在之后的应用中,我也会把它作为切入点,对资源进行管理。
另外,对于在http/intercept-url中没有进行定义的URL,将会默认使用系统内置的过滤器链进行权限认证。*所以,并不需要在http/intercept-url中额外定义一个类似/*的匹配规则。
使用数据库对用户、权限和资源进行管理
一般来说,我们都有使用数据库对用户和权限进行管理的需求,而不会把用户写死在配置文件里。
(1)用户和权限的关系设计
从典型意义上讲,他们之间是一个多对多的关系,采用3张表来表示,下面是我在MySQL中建立的3张表的 schema示例:
CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) default NULL,
`password` varchar(255) default NULL,
`disabled` int(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `role` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user_role` (
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
UNIQUE KEY `role_id` (`role_id`),
KEY `FK143BF46AF6AD4381` (`user_id`),
KEY `FK143BF46A51827FA1` (`role_id`),
CONSTRAINT `FK143BF46A51827FA1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`),
CONSTRAINT `FK143BF46AF6AD4381` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(2)通过配置SQL来模拟用户和权限
有了数据库的表设计,我们就可以在Spring Security中,通过配置SQL,来模拟用户和权限,这依然通过<authentication-provider>来完成:<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="SELECT U.username, U.password, U.accountEnabled AS 'enabled' FROM User U where U.username=?"
authorities-by-username-query="SELECT U.username, R.name as 'authority' FROM User U JOIN Authority A ON u.id = A.userId JOIN Role R ON R.id = A.roleId WHERE U.username=?"/>
</authentication-provider>
这里给出的是一个使用SQL进行模拟用户和权限的示例。其中你需要为运行SQL准备相应的dataSource。这个dataSource应该对应于Spring中的某个bean的定义。
从这段配置模拟用户和权限的情况来看,实际上Spring Security对于用户,需要username,password,accountEnabled三个字段。对于权限,它需要的是username和 authority2个字段。
也就是说,如果我们能够通过其他的方式,模拟上面的这些对象,并插入到Spring Security中去,我们同样能够实现用户和权限的认证。接下来,我们就来看看我们如何通过自己的实现,来完成这件事情。
(3)通过扩展Spring Security 的默认实现来进行用户和权限的关系
事实上,Spring Security提供了2个认证的接口,分别用于模拟用户和权限,以及读取用户和权限的操作方法。这两个接口分别是:UserDetails和 UserDetailsService。
public interface UserDetails extends Serializable {
GrantedAuthority[] getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
public interface UserDetailsService {
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException;
}
非常清楚,一个接口用于模拟用户,另外一个用于模拟读取用户的过程。所以我们可以通过实现这两个接口,来完成使用数据库对用户和权限进行管理的需求。
重新定义authentication-provider节点即可。如果你使用Spring 2.5的Annotation配置功能,你甚至可以不需要在配置文件中定义securityManager的bean。
<authentication-provider user-service-ref="securityManager">
<password-encoder hash="md5"/>
</authentication-provider>
(4)使用数据库对资源进行管理
在完成了使用数据库来进行用户和权限的管理之后,我们再来看看http配置的部分。在实际应用中,我们不可能使用类似/**的方式来指定URL与权限ROLE的对应关系,而是会针对某些URL,指定某些特定的ROLE。而URL与ROLE之间的映射关系{zh0}可以进行扩展和配置。而URL属于资源的一种,所以接下来,我们就来看看如何使用数据库来对权限和资源的匹配关系进行管理,并且将认证匹配加入到Spring Security中去。
A 权限和资源的设计
上面我们讲到,用户(User)和权限(Role)之间是一个多对多的关系。那么权限(Role)和资源(Resource)之间呢?其实他们之间也是一个典型的多对多的关系,我们同样用3张表来表示:CREATE TABLE `role` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) default NULL,
`description` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `resource` (
`id` int(11) NOT NULL auto_increment,
`type` varchar(255) default NULL,
`value` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `role_resource` (
`role_id` int(11) NOT NULL,
`resource_id` int(11) NOT NULL,
PRIMARY KEY (`role_id`,`resource_id`),
KEY `FKAEE599B751827FA1` (`role_id`),
KEY `FKAEE599B7EFD18D21` (`resource_id`),
CONSTRAINT `FKAEE599B751827FA1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`),
CONSTRAINT `FKAEE599B7EFD18D21` FOREIGN KEY (`resource_id`) REFERENCES `resource` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在这里Resource可能分成多种类型,比如MENU,URL,METHOD等等。
B 针对资源的认证
针对资源的认证,实际上应该由Spring Security中的FilterSecurityInterceptor这个过滤器来完成。不过内置的 FilterSecurityInterceptor的实现往往无法满足我们的要求,所以传统的Acegi的方式,我们往往会替换 FilterSecurityInterceptor的实现,从而对URL等资源进行认证。
不过在Spring Security中,由于默认的拦截器链内置了FilterSecurityInterceptor,而且上面我们也提到过,这个实现无法被替换。这就使我们犯了难。我们如何对资源进行认证呢?
实际上,我们虽然无法替换FilterSecurityInterceptor的默认实现,不过我们可以再实现一个类似的过滤器,并将我们自己的过滤器作为一个customer-filter,加到默认的过滤器链的{zh1},从而完成整个过滤检查。
注意,由于我们所实现的,是FilterSecurityInterceptor中的一个开放接口,所以我们实际上定义了一个新的bean,并通过<custom-filter after="LAST" />插入到过滤器链中去。
Spring Security对象的访问
(1)访问当前登录用户
Spring Security提供了一个线程安全的对象:SecurityContextHolder,通过这个对象,我们可以访问当前的登录用户。我写了一个类,可以通过静态方法去读取:
return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
(2)访问当前用户所拥有的权限
用户所拥有的所有权限,其实是通过UserDetails接口中的getAuthorities()方法获得的。只要实现这个接口,就能实现需求。
(3)访问当前用户所能够访问的资源
这就涉及到用户(User),权限(Role)和资源(Resource)三者之间的对应关系。同样可以通过实现UserDetails接口中的 getRoleResources()方法来获得
Spring Security已经成为Spring Framework下最成熟的安全系统,它为我们提供了强大而灵活的企业级安全服务
目录
(一)Spring Security2.0概述
(二)Security2.0相对于Acegi的使用
(三)Security2.0使用数据库对用户、权限和资源的管理
(四)Spring Security2.0对象的访问
概述
在认识Spring Security之前,所有的权限验证逻辑都混杂在业务逻辑中,用户的每个操作以前可能都需要对用户是否有进行该项操作的权限进行判断,来达到认证授权的目的。类似这样的权限验证逻辑代码被分散在系统的许多地方,难以维护。AOP(Aspect Oriented Programming)和Spring Security为我们的应用程序很好的解决了此类问题,正如系统日志,事务管理等这些系统级的服务一样,我们应该将它作为系统一个单独的"切面"进行管理,以达到业务逻辑与系统级的服务真正分离的目的,Spring Security将系统的安全逻辑从业务中分离出来。
2007年底Acegi Security正式成为Spring Portfolio项目,并更名为Spring Security.Spring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
security2.0相对于1.0配置的简化
spring security 2.0的配置文件可以使用spring2.0的命名空间配置,大大减少了配置量,最显著的一点就是省掉了那个长长的filter串。 使用它,可以通过附加xml架构,为传统的spring beans应用环境语法做补充。 你可以在spring参考文档得到更多信息。 命名空间元素可以简单的配置单个bean,或使用更强大的,定义一个备用配置语法,这可以更加紧密的匹配问题域,隐藏用户背后的复杂性。 简单元素可能隐藏事实,多种bean和处理步骤添加到应用环境中。
1.0时需要先配置一个filter代理,由filter代理来执行acegi其他的功能 filter:
Xml代码
1. <bean id="filterChainProxy"
2. class="org.acegisecurity.util.FilterChainProxy">
3. <property name="filterInvocationDefinitionSource">
4. 所需要的Filter都必须按顺序这样排列号
5. <value>
6. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
7. PATTERN_TYPE_APACHE_ANT
8. /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,basicProcessingFilter,securityContextHolderAwareRequestFilter,anonymousProcessingFilter,
9. exceptionTranslationFilter,filterSecurityInterceptor
10. </value>
11. </property>
12. </bean>
web.xml里配置filter代理:
Xml代码
1. <filter>
2. <filter-name>AcegiFilterChainProxy</filter-name>
3. <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
4. <init-param>
5. <param-name>targetClass</param-name>
6. <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
7. </init-param>
8. </filter>
9. <filter-mapping>
10. <filter-name>AcegiFilterChainProxy</filter-name>
11. <url-pattern>/*</url-pattern>
12. </filter-mapping>
然后再一个一个配置相应的功能filter,相当繁琐。
2.0只需要
Xml代码
1. <security:http auto-config="true">
2. </security:http>
web.xml里:
在 web.xml文件里加入Filter声明 Xml代码
1. <filter>
2. <filter-name>springSecurityFilterChain</filter-name>
3. <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
4. </filter>
5. <filter-mapping>
6. <filter-name>springSecurityFilterChain</filter-name>
7. <url-pattern>/*</url-pattern>
8. </filter-mapping>
这个Filter会拦截所有的URL请求,并且对这些URL请求进行Spring Security的验证。 ss就会自动代理几个基本的功能filter(如AuthenticationProcessingFilter),filter也有了默认配置,不必在一个个去配置
注意,springSecurityFilterChain这个名称是由命名空间默认创建的用于处理web安全的一个内部的bean的id。所以你在你的Spring配置文件中,不应该再使用这个id作为你的bean
与Acegi的配置不同,Acegi需要自行声明一个Spring的bean来作为Filter的实现,而使用Spring Security后,无需再额外定义bean,而是使用<http>元素进行配置。
A 使用最小的<http>配置
1. <http auto-config='true'>
2. <intercept-url pattern="/**" access="ROLE_USER" />
3. </http>
这段配置表示:我们要保护应用程序中的所有URL,只有拥有ROLE_USER角色的用户才能访问。你可以使用多个<intercept- url>元素为不同URL的集合定义不同的访问需求,它们会被归入一个有序队列中,每次取出{zx0}匹配的一个元素使用。 所以你必须把期望使用的匹配条件放到最上边。
B 配置 UserDetailsService来指定用户和权限
1. <authentication-provider>
2. <user-service>
3. <user name="downpour" password="downpour" authorities="ROLE_USER, ROLE_ADMIN" />
4. <user name="robbin" password="robbin" authorities="ROLE_USER" />
5. <user name="QuakeWang" password="QuakeWang" authorities="ROLE_ADMIN" />
6. </user-service>
7. </authentication-provider>
在这里,downpour拥有ROLE_USER和ROLE_ADMIN的权限,robbin拥有ROLE_USER权限,QuakeWang拥有 ROLE_ADMIN的权限
C 使用security命名空间需要将spring配置文件的头上加上security命名空间的xsd:
Xml代码
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">
...
</beans>
D 剖析基本配置元素
(1)有关auto-config配置属性
在上面用到的auto-config属性,其实是下面这些配置的缩写:
1. <http>
2. <intercept-url pattern="/**" access="ROLE_USER" />
3. <form-login />
4. <anonymous />
5. <http-basic />
6. <logout />
7. <remember-me />
8. </http>
这些元素分别与登录认证,匿名认证,基本认证,注销处理和remember-me对应。 他们拥有各自的属性,可以改变他们的具体行为。这样,在Acegi中所熟悉的元素又出现了。只是在这里,我们使用的是命名空间而已。
(2)与Acegi比较
a.基于命名空间的配置更加简洁,可维护性更强
例如,基于命名空间进行登录认证的配置代码,可能像这样:
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" default-target-url="/work" />
如果使用老的Acegi的Bean的定义方式,可能像这样:
1. <bean id="authenticationProcessingFilter"
2. class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
3. <property name="authenticationManager"
4. ref="authenticationManager"/>
5. <property name="authenticationFailureUrl"
6. value="/login.jsp?error=1"/>
7. <property name="defaultTargetUrl" value="/work"/>
8. <property name="filterProcessesUrl"
9. value="/j_acegi_security_check"/>
10. <property name="rememberMeServices" ref="rememberMeServices"/>
11. </bean>
b.基于命名空间的配置,我们无需再担心由于过滤器链的顺序而导致的错误
以前,Acegi在缺乏默认内置配置的情况下,你需要自己来定义所有的bean,并指定这些bean在过滤器链中的顺序。一旦顺序错了,很容易发生错误。而现在,过滤器链的顺序被默认指定,你不需要在担心由于顺序的错误而导致的错误。
(3)过滤器链在哪里
在Spring Security中,将看不到"长长"的过滤器链的配置,这些配置都被内置在<http>节点中。这些过滤器已经被Spring容器默认内置注册,这也就是我们不再需要在配置文件中定义那么多bean的原因。 同时,过滤器顺序在使用命名空间的时候是被严格执行的。它们在初始化的时候就预先被排好序。不仅如此,Spring Security规定,你不能替换那些<http>元素自己使用而创建出的过滤器,
****************不过事实上ss的默认实现是很简陋的,提供的demo里都是使用内存数据库,权限配置也都是写死到配置文件里,根本无法在项目中应用,进行相应的扩展是不可避免的。
Spring Security允许你把你自己的过滤器添加到队列中,使用custom-filter元素,并且指定你的过滤器应该出现的位置:<security:custom-filter position="alias"/>,position为相应filter的别名。
对应关系:
CHANNEL_FILTER ChannelProcessingFilter
CONCURRENT_SESSION_FILTER ConcurrentSessionFilter
SESSION_CONTEXT_INTEGRATION_FILTER HttpSessionContextIntegrationFilter
LOGOUT_FILTER LogoutFilter
X509_FILTER X509PreAuthenticatedProcessigFilter
PRE_AUTH_FILTER Subclass of AstractPreAuthenticatedProcessingFilter
CAS_PROCESSING_FILTER CasProcessingFilter
AUTHENTICATION_PROCESSING_FILTER AuthenticationProcessingFilter
BASIC_PROCESSING_FILTER BasicProcessingFilter
SERVLET_API_SUPPORT_FILTER classname
REMEMBER_ME_FILTER RememberMeProcessingFilter
ANONYMOUS_FILTER AnonymousProcessingFilter
EXCEPTION_TRANSLATION_FILTER ExceptionTranslationFilter
NTLM_FILTER NtlmProcessingFilter
FILTER_SECURITY_INTERCEPTOR FilterSecurityInterceptor
SWITCH_USER_FILTER SwitchUserProcessingFilter
比如:
Xml代码
1. <beans:bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter">
2. <custom-filter position="AUTHENTICATION_PROCESSING_FILTER"/>
3. </beans:bean>
不仅如此,你还可以使用after或before属性,如果你想把你的过滤器添加到队列中另一个过滤器的前面或后面。 可以分别在position属性使用"FIRST"或"LAST"来指定你想让你的过滤器出现在队列元素的前面或后面。
这个特性或许能够在一定程度上弥补Spring Security的死板规定,而在之后的应用中,我也会把它作为切入点,对资源进行管理。
另外,对于在http/intercept-url中没有进行定义的URL,将会默认使用系统内置的过滤器链进行权限认证。*所以,并不需要在http/intercept-url中额外定义一个类似/*的匹配规则。
使用数据库对用户、权限和资源进行管理
一般来说,我们都有使用数据库对用户和权限进行管理的需求,而不会把用户写死在配置文件里。
(1)用户和权限的关系设计
从典型意义上讲,他们之间是一个多对多的关系,采用3张表来表示,下面是我在MySQL中建立的3张表的 schema示例:
CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) default NULL,
`password` varchar(255) default NULL,
`disabled` int(1) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `role` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user_role` (
`user_id` int(11) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`user_id`,`role_id`),
UNIQUE KEY `role_id` (`role_id`),
KEY `FK143BF46AF6AD4381` (`user_id`),
KEY `FK143BF46A51827FA1` (`role_id`),
CONSTRAINT `FK143BF46A51827FA1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`),
CONSTRAINT `FK143BF46AF6AD4381` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
(2)通过配置SQL来模拟用户和权限
有了数据库的表设计,我们就可以在Spring Security中,通过配置SQL,来模拟用户和权限,这依然通过<authentication-provider>来完成:<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="SELECT U.username, U.password, U.accountEnabled AS 'enabled' FROM User U where U.username=?"
authorities-by-username-query="SELECT U.username, R.name as 'authority' FROM User U JOIN Authority A ON u.id = A.userId JOIN Role R ON R.id = A.roleId WHERE U.username=?"/>
</authentication-provider>
这里给出的是一个使用SQL进行模拟用户和权限的示例。其中你需要为运行SQL准备相应的dataSource。这个dataSource应该对应于Spring中的某个bean的定义。
从这段配置模拟用户和权限的情况来看,实际上Spring Security对于用户,需要username,password,accountEnabled三个字段。对于权限,它需要的是username和 authority2个字段。
也就是说,如果我们能够通过其他的方式,模拟上面的这些对象,并插入到Spring Security中去,我们同样能够实现用户和权限的认证。接下来,我们就来看看我们如何通过自己的实现,来完成这件事情。
(3)通过扩展Spring Security 的默认实现来进行用户和权限的关系
事实上,Spring Security提供了2个认证的接口,分别用于模拟用户和权限,以及读取用户和权限的操作方法。这两个接口分别是:UserDetails和 UserDetailsService。
public interface UserDetails extends Serializable {
GrantedAuthority[] getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
public interface UserDetailsService {
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException;
}
非常清楚,一个接口用于模拟用户,另外一个用于模拟读取用户的过程。所以我们可以通过实现这两个接口,来完成使用数据库对用户和权限进行管理的需求。
重新定义authentication-provider节点即可。如果你使用Spring 2.5的Annotation配置功能,你甚至可以不需要在配置文件中定义securityManager的bean。
<authentication-provider user-service-ref="securityManager">
<password-encoder hash="md5"/>
</authentication-provider>
(4)使用数据库对资源进行管理
在完成了使用数据库来进行用户和权限的管理之后,我们再来看看http配置的部分。在实际应用中,我们不可能使用类似/**的方式来指定URL与权限ROLE的对应关系,而是会针对某些URL,指定某些特定的ROLE。而URL与ROLE之间的映射关系{zh0}可以进行扩展和配置。而URL属于资源的一种,所以接下来,我们就来看看如何使用数据库来对权限和资源的匹配关系进行管理,并且将认证匹配加入到Spring Security中去。
A 权限和资源的设计
上面我们讲到,用户(User)和权限(Role)之间是一个多对多的关系。那么权限(Role)和资源(Resource)之间呢?其实他们之间也是一个典型的多对多的关系,我们同样用3张表来表示:CREATE TABLE `role` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) default NULL,
`description` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `resource` (
`id` int(11) NOT NULL auto_increment,
`type` varchar(255) default NULL,
`value` varchar(255) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `role_resource` (
`role_id` int(11) NOT NULL,
`resource_id` int(11) NOT NULL,
PRIMARY KEY (`role_id`,`resource_id`),
KEY `FKAEE599B751827FA1` (`role_id`),
KEY `FKAEE599B7EFD18D21` (`resource_id`),
CONSTRAINT `FKAEE599B751827FA1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`),
CONSTRAINT `FKAEE599B7EFD18D21` FOREIGN KEY (`resource_id`) REFERENCES `resource` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
在这里Resource可能分成多种类型,比如MENU,URL,METHOD等等。
B 针对资源的认证
针对资源的认证,实际上应该由Spring Security中的FilterSecurityInterceptor这个过滤器来完成。不过内置的 FilterSecurityInterceptor的实现往往无法满足我们的要求,所以传统的Acegi的方式,我们往往会替换 FilterSecurityInterceptor的实现,从而对URL等资源进行认证。
不过在Spring Security中,由于默认的拦截器链内置了FilterSecurityInterceptor,而且上面我们也提到过,这个实现无法被替换。这就使我们犯了难。我们如何对资源进行认证呢?
实际上,我们虽然无法替换FilterSecurityInterceptor的默认实现,不过我们可以再实现一个类似的过滤器,并将我们自己的过滤器作为一个customer-filter,加到默认的过滤器链的{zh1},从而完成整个过滤检查。
注意,由于我们所实现的,是FilterSecurityInterceptor中的一个开放接口,所以我们实际上定义了一个新的bean,并通过<custom-filter after="LAST" />插入到过滤器链中去。
Spring Security对象的访问
(1)访问当前登录用户
Spring Security提供了一个线程安全的对象:SecurityContextHolder,通过这个对象,我们可以访问当前的登录用户。我写了一个类,可以通过静态方法去读取:
return (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
(2)访问当前用户所拥有的权限
用户所拥有的所有权限,其实是通过UserDetails接口中的getAuthorities()方法获得的。只要实现这个接口,就能实现需求。
(3)访问当前用户所能够访问的资源
这就涉及到用户(User),权限(Role)和资源(Resource)三者之间的对应关系。同样可以通过实现UserDetails接口中的 getRoleResources()方法来获得