error=true" default-target-url="/work" />
如果使用老的Acegi的Bean的定义方式,可能像这样:
Xml代码
1.2. class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
3. 4. ref="authenticationManager"/>
5. 6. value="/login.jsp?
error=1"/>
7.
8. 9. value="/j_acegi_security_check"/>
10.
11.
这样的例子很多,有兴趣的读者可以一一进行比较。
2)基于命名空间的配置,我们无需再担心由于过滤器链的顺序而导致的错误
以前,Acegi在缺乏默认内置配置的情况下,你需要自己来定义所有的bean,并指定这些bean在过滤器链中的顺序。
一旦顺序错了,很容易发生错误。
而现在,过滤器链的顺序被默认指定,你不需要在担心由于顺序的错误而导致的错误。
3.过滤器链在哪里
到目前为止,我们都还没有讨论过整个SpringSecurity的核心部分:
过滤器链。
在原本Acegi的配置中,我们大概是这样配置我们的过滤器链的:
Xml代码
1.2. class="org.acegisecurity.util.FilterChainProxy">
3.
4.
5. CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
6. PATTERN_TYPE_APACHE_ANT
7. /common/**=#NONE#
8. /css/**=#NONE#
9. /images/**=#NONE#
10. /js/**=#NONE#
11. /login.jsp=#NONE#
12. /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,exceptionTranslationFilter,filterSecurityInterceptor
13.
14.
15.
其中,每个过滤器链都将对应于Spring配置文件中的bean的id。
现在,在SpringSecurity中,我们将看不到这些配置,这些配置都被内置在节点中。
让我们来看看这些默认的,已经被内置的过滤器:
这些过滤器已经被Spring容器默认内置注册,这也就是我们不再需要在配置文件中定义那么多bean的原因。
同时,过滤器顺序在使用命名空间的时候是被严格执行的。
它们在初始化的时候就预先被排好序。
不仅如此,SpringSecurity规定,你不能替换那些元素自己使用而创建出的过滤器,比如HttpSessionContextIntegrationFilter,ExceptionTranslationFilter或FilterSecurityInterceptor。
当然,这样的规定是否合理,有待进一步讨论。
因为实际上在很多时候,我们希望覆盖过滤器链中的某个过滤器的默认行为。
而SpringSecurity的这种规定在一定程度上限制了我们的行为。
不过SpringSecurity允许你把你自己的过滤器添加到队列中,使用custom-filter元素,并且指定你的过滤器应该出现的位置:
Xml代码
1.bean id="myFilter" class="com.mycompany.MySpecialAuthenticationFilter">
2.
3.
bean>
不仅如此,你还可以使用after或before属性,如果你想把你的过滤器添加到队列中另一个过滤器的前面或后面。
可以分别在position属性使用"FIRST"或"LAST"来指定你想让你的过滤器出现在队列元素的前面或后面。
这个特性或许能够在一定程度上弥补SpringSecurity的死板规定,而在之后的应用中,我也会把它作为切入点,对资源进行管理。
另外,我需要补充一点的是,对于在http/intercept-url中没有进行定义的URL,将会默认使用系统内置的过滤器链进行权限认证。
所以,你并不需要在http/intercept-url中额外定义一个类似/**的匹配规则。
使用数据库对用户和权限进行管理
一般来说,我们都有使用数据库对用户和权限进行管理的需求,而不会把用户写死在配置文件里。
所以,我们接下来就重点讨论使用数据库对用户和权限进行管理的方法。
用户和权限的关系设计
在此之前,我们首先需要讨论一下用户(User)和权限(Role)之间的关系。
SpringSecurity在默认情况下,把这两者当作一对多的关系进行处理。
所以,在SpringSecurity中对这两个对象所采用的表结构关系大概像这样:
Java代码
1.CREATE TABLE users (
2. username VARCHAR(50) NOT NULL PRIMARY KEY,
3. password VARCHAR(50) NOT NULL,
4. enabled BIT NOT NULL
5.);
6.
7.CREATE TABLE authorities (
8. username VARCHAR(50) NOT NULL,
9. authority VARCHAR(50) NOT NULL
10.);
不过这种设计方式在实际生产环境中基本上不会采用。
一般来说,我们会使用逻辑主键ID来标示每个User和每个Authorities(Role)。
而且从典型意义上讲,他们之间是一个多对多的关系,我们会采用3张表来表示,下面是我在MySQL中建立的3张表的schema示例:
Java代码
1.CREATE TABLE `user` (
2. `id` int(11) NOT NULL auto_increment,
3. `name` varchar(255) default NULL,
4. `password` varchar(255) default NULL,
5. `disabled` int
(1) NOT NULL,
6. PRIMARY KEY (`id`)
7.) ENGINE=InnoDB DEFAULT CHARSET=utf8;
8.
9.CREATE TABLE `role` (
10. `id` int(11) NOT NULL auto_increment,
11. `name` varchar(255) default NULL,
12. PRIMARY KEY (`id`)
13.) ENGINE=InnoDB DEFAULT CHARSET=utf8;
14.
15.CREATE TABLE `user_role` (
16. `user_id` int(11) NOT NULL,
17. `role_id` int(11) NOT NULL,
18. PRIMARY KEY (`user_id`,`role_id`),
19. UNIQUE KEY `role_id` (`role_id`),
20. KEY `FK143BF46AF6AD4381` (`user_id`),
21. KEY `FK143BF46A51827FA1` (`role_id`),
22. CONSTRAINT `FK143BF46A51827FA1` FOREIGN KEY (`role_id`) REFERENCES `role` (`id`),
23. CONSTRAINT `FK143BF46AF6AD4381` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
24.) ENGINE=InnoDB DEFAULT CHARSET=utf8;
通过配置SQL来模拟用户和权限
有了数据库的表设计,我们就可以在SpringSecurity中,通过配置SQL,来模拟用户和权限,这依然通过来完成:
Xml代码
1.
2. 3. users-by-username-query="SELECT U.username, U.password, U.accountEnabled AS 'enabled' FROM User U where U.username=?
"
4. 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=?
"/>
5.
这里给出的是一个使用SQL进行模拟用户和权限的示例。
其中你需要为运行SQL准备相应的dataSource。
这个dataSource应该对应于Spring中的某个bean的定义。
从这段配置模拟用户和权限的情况来看,实际上SpringSecurity对于用户,需要username,password,accountEnabled三个字段。
对于权限,它需要的是username和authority2个字段。
也就是说,如果我们能够通过其他的方式,模拟上面的这些对象,并插入到SpringSecurity中去,我们同样能够实现用户和权限的认证。
接下来,我们就来看看我们如何通过自己的实现,来完成这件事情。
通过扩展SpringSecurity的默认实现来进行用户和权限的管理
事实上,SpringSecurity提供了2个认证的接口,分别用于模拟用户和权限,以及读取用户和权限的操作方法。
这两个接口分别是:
UserDetails和UserDetailsService。
Java代码
1.public interface UserDetails extends Serializable {
2.
3. GrantedAuthority[] getAuthorities();
4.
5. String getPassword();
6.
7. String getUsername();
8.
9. boolean isAccountNonExpired();
10.
11. boolean isAccountNonLocked();
12.
13. boolean isCredentialsNonExpired();
14.
15. boolean isEnabled();
16.}
Java代码
1.public interface UserDetailsService {
2. UserDetails loadUserByUsername(String username)
3. throws UsernameNotFoundException, DataAccessException;
4.}
非常清楚,一个接口用于模拟用户,另外一个用于模拟读取用户的过程。
所以我们可以通过实现这两个接口,来完成使用数据库对用户和权限进行管理的需求。
在这里,我将给出一个使用Hibernate来定义用户和权限之间关系的示例。
1.定义User类和Role类,使他们之间形成多对多的关系
Java代码
1.@Entity
2.@Proxy(lazy = false)
3.@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
4.public class User {
5.
6. private static final long serialVersionUID = 8026813053768023527L;
7.
8. @Id
9. @GeneratedValue
10. private Integer id;
11.
12. private String name;
13.
14. private String password;
15.
16. private boolean disabled;
17.
18. @ManyToMany(targetEntity = Role.class, fetch = FetchTy