一. 简单介绍
1.1 本文目的
集成Acegi到自己的项目中, 并且将用户信息和权限放到数据库, 提供方法允许权限动态变化,变化后自动加载最新的权限
本文介绍Acegi例子的时候采用的是acegi-security-samples-tutorial-1.0.6.war
阅读本文需要对Spring有一定的了解, 如果你还没有接触过, 有些地方可能不容易理解, 这时候可能需要参考本文后附的Spring地址, 先了解一下Spring的基本知识.
本文使用的是Mysql数据库, 如果你使用其他的数据库, 可能需要修改相应的SQL.
本文及所附的全部代码放在http://acegi-test.sourceforge.net/
1.2 安装与配置
项目主页: http://www.acegisecurity.org/
下载地址: http://sourceforge.net/project/showfiles.php?group_id=104215
解压文件后, 将acegi-security-samples-tutorial-1.0.6.war复制Your_Tomcat_Path/webapps/
启动Tomcat, 访问http://localhost:8080/acegi-security-samples-tutorial-1.0.6/
点击页面上任何一个链接,都需要用户登录后访问, 可以在页面上看到可用的用户名和密码.
二. 开始集成到自己的程序中
2.1 将用户和角色放在数据库中
可能是为了演示方便, 简单的展示Acegi如何控制权限, 而不依赖于任何数据库, ACEGI给出的例子采用InMemoryDaoImpl获取用户信息, 用户和角色信息放在WEB-INF/users.properties 文件中, InMemoryDaoImpl 一次性的从该配置文件中读出用户和角色信息, 格式是: 用户名=密码, 角色名, 如第一行是:
marissa=koala,ROLE_SUPERVISOR
就是说marissa的密码是koala, 并且他的角色是ROLE_SUPERVISOR
对这个文件的解析是通过applicationContext-acegi-security.xml中如下的设置进行的:
<!-- UserDetailsService is the most commonly frequently Acegi Security interface implemented by end users -->
<bean id="userDetailsService"
class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userProperties">
<bean
class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="location"
value="classpath:users.properties" />
</bean>
</property>
</bean>
除了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的查询语句
<property name="usersByUsernameQuery">
<value>
SELECT email, password, enabled from user u where email = ?
</value>
</property>
<property name="authoritiesByUsernameQuery">
<value>
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 = ?
</value>
</property>
2.1.2 扩展JdbcDaoImpl获取更多用户信息
如果上面提到的定制查询SQL语句不能提供足够的灵活性, 那么你可能就需要定义一个JdbcDaoImpl的子类, 如果变动不大, 通过覆盖initMappingSqlQueries方法重新定义MappingSqlQuery的实例. 而如果你需要获取更多信息, 比如userId, companyId等, 那就需要做更多的改动, 第一种改动不大, 所以不具体介绍, 下面以第二种改动为例,介绍如何实现这种需求.
我们需要三张表User, Role, User_Role, 具体的SQL如下:
#
# Structure for the `role` table :
#
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`role_id` int(11) NOT NULL auto_increment,
`role_name` varchar(50) default NULL,
`description` varchar(20) default NULL,
`enabled` tinyint(1) NOT NULL default '1',
PRIMARY KEY (`role_id`)
);
#
# Structure for the `user` table :
#
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`user_id` int(11) NOT NULL auto_increment,
`company_id` int(11) default NULL,
`email` varchar(200) default NULL,
`password` varchar(10) default NULL,
`enabled` tinyint(1) default NULL,
PRIMARY KEY (`user_id`)
);
#
# Structure for the `user_role` table :
#
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`user_role_id` int(11) NOT NULL auto_increment,
`user_id` varchar(50) NOT NULL,
`role_id` int(11) NOT NULL,
PRIMARY KEY (`user_role_id`)
);
前面讲过, UserDetailsService接口中只定义了一个方法: UserDetails loadUserByUsername(String username), UserDetails中不存在我们需要的userId 和companyId等信息, 所以我们首先需要扩展UserDetails接口, 并扩展org.acegisecurity.userdetails.User:
IUserDetails.java
package org.security;
import org.acegisecurity.GrantedAuthority;
/**
* The class <code>IUserDetails</code> extends the org.acegisecurity.userdetails.UserDetails interface, and provides additional userId, companyId information<br><br>
* @author wade
* @see UserDetails
*/
public interface IUserDetails extends org.acegisecurity.userdetails.UserDetails{
public int getUserId();
public void setUserId(int user_id);
public int getCompanyId();
public void setCompanyId(int company_id);
public String getUsername();
public void setUsername(String username);
public GrantedAuthority[] getAuthorities();
public void setAuthorities(GrantedAuthority[] authorities);
}
UserDetailsImpl.java
package org.security;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.userdetails.User;
/**
* The class <code>UserDetailsImpl</code> extends the org.acegisecurity.userdetails.User class, and provides additional userId, companyId information
* @author wade
*
* @see IUserDetails, User
*/
public class UserDetailsImpl extends User implements IUserDetails{
private int user_id;
private int company_id;
private String username;
private GrantedAuthority[] authorities;
public UserDetailsImpl(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, GrantedAuthority[] authorities)
throws IllegalArgumentException {
super(username, password, enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);
setUsername(username);
setAuthorities(authorities);
}
public UserDetailsImpl(int userid, int companyid, String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked, GrantedAuthority[] authorities)
throws IllegalArgumentException {
super(username, password, enabled, accountNonExpired, credentialsNonExpired,
accountNonLocked, authorities);
this.user_id = userid;
this.company_id = companyid;
setUsername(username);
setAuthorities(authorities);
}
public int getUserId() {
return user_id;
}
public void setUserId(int user_id) {
this.user_id = user_id;
}
public int getCompanyId() {
return company_id;
}
public void setCompanyId(int company_id) {
this.company_id = company_id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public GrantedAuthority[] getAuthorities() {
return authorities;
}
public void setAuthorities(GrantedAuthority[] authorities) {
this.authorities = authorities;
}
}
到此为止, 我们已经准备好了存放用户信息的类, 下面就开始动手修改取用户数据的代码.
假设我们用下面的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方法, 修改了返回的对象类型,和很少的内部代码.
AcegiJdbcDaoImpl.java
package org.security.acegi;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import javax.sql.DataSource;
import org.acegisecu