使用JdbcDaoImpl和InMemoryDaoImpl类似,它们的基本原理都是根据Authentication中待认证的用户名查询出代表真实系统用户的UserDetails对象。只不过,InMemoryDaoImpl通过查询驻留于内存中的格式化用户信息列表完成的,而JdbcDaoImpl则通过查询数据库达到目的。
在介绍JdbcDaoImpl的具体使用之前,我们先在数据库中创建用户信息表和用户权限表,并初始化一些测试的用户数据:
代码清单 6 创建用户及权限的SQL脚本
你可以简单地运行随书光盘的chapter17/schema/mysql/db.sql脚本文件完成用户安全相关信息表的创建工作。CREATE TABLE T_USER ( ①用户信息表 USER_ID INTEGER NOT NULL AUTO_INCREMENT, USERNAME VARCHAR(30) NOT NULL, PASSWORD VARCHAR(30) DEFAULT NULL, STATUS TINYINT(1) NOT NULL DEFAULT '0', PRIMARY KEY (`USER_ID`), UNIQUE KEY `USERNAME` (`USERNAME`) ); CREATE TABLE T_USER_PRIV (②用户权限表 USER_ID INTEGER NOT NULL DEFAULT '0', PRIV_NAME VARCHAR(30) DEFAULT NULL, PRIMARY KEY (USER_ID, PRIV_NAME) ); INSERT INTO T_USER (USER_ID, USERNAME, PASSWORD, STATUS) VALUES ③用户数据 (1,'tom','tom',1), (2,'john','john',1); INSERT INTO T_USER_PRIV (USER_ID, PRIV_NAME) VALUES ④用户授权表 (1,'PRIV_1'), (1,'PRIV_2'), (1,'PRIV_COMMON'), (2,'PRIV_1'), (2,'PRIV_COMMON'); COMMIT;
下面,我们用JdbcDaoImpl替换InMemoryDaoImpl,从数据库中获取UserDetails对象,其代码如代码清单 7所示:
代码清单 7 applicationContext-acegi-plugin.xml
基于数据库存储的用信息获取
… <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider"> ①调整为从数据库获取UserDetails对象的服务类 <property name="userDetailsService" ref="userDetailsService" /> </bean> <bean id="userDetailsService" class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">② <property name="dataSource" ref="dataSource" /> ②-1 数据源 <property name="usersByUsernameQuery"> ②-2 根据用户名查询用户的SQL语句 <value> SELECT username,password, status FROM t_user WHERE username = ? </value> </property> <property name="authoritiesByUsernameQuery"> ②-3 根据用户名查询用户权限记录的SQL语句 <value> SELECT u.username,p.priv_name FROM t_user u,t_user_priv p WHERE u.user_id =p.user_id AND u.username = ? </value> </property> </bean>
这种替换的难度很小,仅需要配置一个JdbcDaoImpl Bean并将其作为daoAuthenticationProvider的userDetailsService的实现即可。
JdbcDaoImpl其实是基于Spring JDBC技术进行数据库访问的,它继承于org.springframework.jdbc.core.support.JdbcDaoSupport。所以JdbcDaoImpl必不可少的一个属性是dataSource,它指定一个保存用户信息的数据源。
JdbcDaoImpl通过usersByUsernameQuery和authoritiesByUsernameQuery属性定义查询用户信息和用户权限的SQL语句。实际上,JdbcDaoImpl为以上两个属性提供了默认的SQL语句,分别是:
"SELECT username,password,enabled FROM users WHERE username = ?"
和
"SELECT username,authority FROM authorities WHERE username = ?"
一般情况下,我们会使用自己的用户和权限表,所以你需要自定义以上两个属性。JdbcDaoImpl将Authentication中待认证用户名作为这两个SQL语句的username参数值,并执行查询返回结果,然后根据列索引的方式从结果集中取得数据构造UserDetails对象,所以在构造SQL语句时必须保持用户信息字段位于正确的列位置,字段名可以任意指定。
应该说,JdbcDaoImpl还不是非常实用的UserDetailsService实现类,因为用户对象(UserDetails)除包含用户名/密码、是否激活、权限等信息外,还经常需要包含一些诸如email、telephone等业务相关的信息。而JdbcDaoImpl通用类并不知道我们具体的业务需求,所以我们往往需要通过实现UserDetailsService接口提供自己的实现类来完成这些工作。编写一个自己的UserDetailsService实现类不费什么力气,你只要实现接口中的UserDetails loadUserByUsername(String username)方法根据用户名获取UserDetails对象即可。在编写好自己的UserDetailsService后,在Spring配置并替换①处的userDetailsService属性即可,限于篇幅,我们不再展开论述,读者可以试着自行完成。
小结
Acegi通过过滤器对请求实施拦截,当发现用户没有登录时,将强制用户进行系统登录,通过一定的接口规范,Acegi就可以获取操作者的用户身份,并进而获取用户的权限。在下一篇文章中,我们将接着介绍身份认证的高级话题。