一. 简单介绍

 1.1 本文目的

集成Acegi到自己的项目中, 并且将用户信息和权限放到数据库, 提供方法允许权限动态变化,变化后自动加载最新的权限
本文介绍Acegi例子的时候采用的是acegi-security-samples-tutorial-1.0.6.war
阅读本文需要对Spring有一定的了解, 如果你还没有接触过, 有些地方可能不容易理解, 这时候可能需要参考本文后附的Spring地址, 先了解一下Spring的基本知识.
本文使用的是Mysql数据库, 如果你使用其他的数据库, 可能需要修改相应的SQL.

1.2 安装与配置

解压文件后, 将acegi-security-samples-tutorial-1.0.6.war复制Your_Tomcat_Path/webapps/
点击页面上任何一个链接,都需要用户登录后访问, 可以在页面上看到可用的用户名和密码.

二. 开始集成到自己的程序中

2.1 将用户和角色放在数据库中

可能是为了演示方便, 简单的展示Acegi如何控制权限, 而不依赖于任何数据库, ACEGI给出的例子采用InMemoryDaoImpl获取用户信息, 用户和角色信息放在WEB-INF/users.properties 文件中, InMemoryDaoImpl 一次性的从该配置文件中读出用户和角色信息, 格式是: 用户名=密码, 角色名, 如第一行是:
marissa=koala,ROLE_SUPERVISOR
就是说marissa的密码是koala, 并且他的角色是ROLE_SUPERVISOR
对这个文件的解析是通过applicationContext-acegi-security.xml中如下的设置进行的:
 1 <!-- UserDetailsService is the most commonly frequently
     Acegi Security interface implemented by end users 
-->
 2 <bean id="userDetailsService"
 3 class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
 4 <property name="userProperties">
 5 <bean
 6 class="org.springframework.beans.factory.config.PropertiesFactoryBean">
 7 <property name="location"
 8 value="classpath:sers.properties" />
 9 </bean>
10 </property>
11 </bean>
12 
13 

除了InMemoryDaoImpl之外, ACEGI还提供了Jdbc和 ldap的支持, 由于使用数据库进行验证比较常见, 下面仅就jdbc实现做出介绍.
不管是InMemoryDaoImpl还是JdbcDaoImpl都是实现了UserDetailsService接口, 而这个接口里只定义了一个方法: UserDetails loadUserByUsername(String username) 就是根据用户名加载UserDetails对象, UserDetails也是一个接口, 定义了一个用户所需要的基本信息, 包括: username, password, authorities等信息

2.1.1 直接使用JdbcDaoImpl 访问数据库中的用户信息

如果ACEGI提供的信息满足你的需要, 也就是说你只需要用户的username, password等信息, 你可以直接使用ACEGI提供的Schema, 这样, 不需要任何变动, JdbcDaoImpl就可以使用了.
如果你的数据库已经定义好了, 或者不想使用ACEGI提供的Schema,那么你也可以自定义JdbcDaoImpl的查询语句
 1         <property name="usersByUsernameQuery">
 2 <value>
 3 SELECT email, password, enabled from user u where email = ?
 4 </value>
 5 </property>
 6 <property name="authoritiesByUsernameQuery">
 7 <value>
 8 SELECT u.email, r.role_name FROM user_role ur, user u, role r WHERE
 9 ur.user_id = u.user_id and ur.role_id = r.role_id and u.email = ?
10 </value>
11 </property>

2.1.2 扩展JdbcDaoImpl获取更多用户信息

如果上面提到的定制查询SQL语句不能提供足够的灵活性, 那么你可能就需要定义一个JdbcDaoImpl的子类, 如果变动不大, 通过覆盖initMappingSqlQueries方法重新定义MappingSqlQuery的实例. 而如果你需要获取更多信息, 比如userId, companyId等, 那就需要做更多的改动, 第一种改动不大, 所以不具体介绍, 下面以第二种改动为例,介绍如何实现这种需求.
我们需要三张表User, Role, User_Role, 具体的SQL如下:
 1 1 #
 2  2 # Structure for the `role` table :
 3  3 #
 4  4 DROP TABLE IF EXISTS `role`;
 5  5 CREATE TABLE `role` (
 6  6 `role_id` int(11NOT NULL auto_increment,
 7  7 `role_name` varchar(50default NULL,
 8  8 `description` varchar(20default NULL,
 9  9 `enabled` tinyint(1NOT NULL default '1',
10 10 PRIMARY KEY  (`role_id`)
11 11 );
12 12 #
13 13 # Structure for the `usertable :
14 14 #
15 15 DROP TABLE IF EXISTS `user`;
16 16 CREATE TABLE `user` (
17 17 `user_idint(11NOT NULL auto_increment,
18 18 `company_id` int(11default NULL,
19 19 `email` varchar(200default NULL,
20 20 `password` varchar(10default NULL,
21 21 `enabled` tinyint(1default NULL,
22 22 PRIMARY KEY  (`user_id`)
23 23 );
24 24 #
25 25 # Structure for the `user_role` table :
26 26 #
27 27 DROP TABLE IF EXISTS `user_role`;
28 28 CREATE TABLE `user_role` (
29 29 `user_role_id` int(11NOT NULL auto_increment,
30 30 `user_idvarchar(50NOT NULL,
31 31 `role_id` int(11NOT NULL,
32 32 PRIMARY KEY  (`user_role_id`)
33 33 );
前面讲过, UserDetailsService接口中只定义了一个方法: UserDetails loadUserByUsername(String username), UserDetails中不存在我们需要的userId 和companyId等信息, 所以我们首先需要扩展UserDetails接口, 并扩展org.acegisecurity.userdetails.User:
IUserDetails.java

UserDetailsImpl.java
 1 package org.security;
 2 import org.acegisecurity.GrantedAuthority;
 3 import org.acegisecurity.userdetails.User;
 4 /**
 5  * The class <code>UserDetailsImpl</code> extends the
    * org.acegisecurity.userdetails.User class, and provides
    * additional userId, companyId information
 6  * @author wade
 7  * 
 8  * @see IUserDetails, User
 9  */
10 public class UserDetailsImpl extends User implements IUserDetails{
11 private int user_id;
12 private int company_id;
13 private String username;
14 private GrantedAuthority[] authorities;
15 public UserDetailsImpl(String username, String password, boolean enabled,
16 boolean accountNonExpired, boolean credentialsNonExpired,
17 boolean accountNonLocked, GrantedAuthority[] authorities)
18 throws IllegalArgumentException {
19 super(username, password, enabled, accountNonExpired, credentialsNonExpired,
20 accountNonLocked, authorities);
21 setUsername(username);
22 setAuthorities(authorities);
23 }
24 public UserDetailsImpl(int userid, int companyid, String username,
                                        String password, 
boolean enabled,
25 boolean accountNonExpired, boolean credentialsNonExpired,
26 boolean accountNonLocked, GrantedAuthority[] authorities)
27 throws IllegalArgumentException {
28 super(username, password, enabled, accountNonExpired, credentialsNonExpired,
29 accountNonLocked, authorities);
30 this.user_id = userid;
31 this.company_id = companyid;
32 setUsername(username);
33 setAuthorities(authorities);
34 }
35 public int getUserId() {
36 return user_id;
37 }
38 public void setUserId(int user_id) {
39 this.user_id = user_id;
40 }
41 public int getCompanyId() {
42 return company_id;
43 }
44 public void setCompanyId(int company_id) {
45 this.company_id = company_id;
46 }
47 public String getUsername() {
48 return username;
49 }
50 public void setUsername(String username) {
51 this.username = username;
52 }
53 public GrantedAuthority[] getAuthorities() {
54 return authorities;
55 }
56 public void setAuthorities(GrantedAuthority[] authorities) {
57 this.authorities = authorities;
58 }
59 }

到此为止, 我们已经准备好了存放用户信息的类, 下面就开始动手修改取用户数据的代码.
假设我们用下面的SQL取用户信息:
SELECT u.user_id, u.company_id, email, password, enabled
FROM role r, user_role ur, user u
WHERE r.role_id = ur.role_id
and ur.user_id = u.user_id
and email = ?
limit 1
用下面的SQL取用户具有的Role列表
SELECT u.email, r.role_name
FROM user_role ur, user u, role r
WHERE ur.user_id = u.user_id
and ur.role_id = r.role_id
and u.email = ?

我们需要修改的主要是两部分:
1. 取用户和用户角色的MappingSqlQuery, 增加了查询的userId和companyId.
2. loadUserByUsername方法, 修改了返回的对象类型,和很少的内部代码.