接续上一篇的简单hello world的例子,我们继续介绍通过数据库动态配置用户权限的方法。
二、使用数据库管理用户权限
上一章节中,我们把用户信息和权限信息放到了xml 文件中,这是为了演示如何使用最小的配置就可以使用Spring Security,而实际开发中,用户信息和权限信息通常是被保存在数据库中的,为此Spring Security 提供了通过数据库获得用户权限信息的方式。
1、修改配置文件
为了从数据库中获取用户权限信息,我们所需要的仅仅是修改配置文件中的authentication-provider 部分。将上一章配置文件中的 user-service 替换为jdbc-user-service,替换内容如下所示:
原:
<authentication-provider>
<user-service>
<user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="user" password="user" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
改成:
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"/>
</authentication-provider>
现在只要再为jdbc-user-service提供一个dataSource就可以让Spring Security使用数据库中的权限信息了。在此我们使用spring创建一个演示用的dataSource实现,这个dataSource会连接到hsqldb数据库,从中获取用户权限信息。
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<beans:property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<beans:property name="url" value="jdbc:hsqldb:res:/hsqldb/test"/>
<beans:property name="username" value="sa"/>
<beans:property name="password" value=""/>
</beans:bean>
Spring Security 默认情况下需要两张表,用户表和权限表。
users:用户表。包含username 用户登录名,password 登陆密码,enabled用户是否被禁用三个字段。其中username 用户登录名为主键。
authorities:权限表。包含username 用户登录名,authorities 对应权限两个字段。其中username 字段与users 用户表的主键使用外键关联。
对authorities 权限表的username 和authority 创建唯一索引,提高查询效率。
Spring Security 会在初始化时,从这两张表中获得用户信息和对应权限,将这些信息保存到缓存中。其中users 表中的登录名和密码用来控制用户的登录,而权限表中的信息用来控制用户登陆后是否有权限访问受保护的系统资源。
在authorities:权限表中对用户配置权限,如通过以下的sql语句可以为admin用户赋予ROLE_ADMIN和ROLE_USER 权限,sql如下:
insert into authorities(username,authority) values('admin','ROLE_ADMIN');
insert into authorities(username,authority) values('admin','ROLE_USER');
三、自定义数据库表结构
Spring-Security提供的表结构基本是无法满足企业级的应用的,我们必须要更加自由的数据库表结构设计。
假设我们实际使用的表结构如下所示:
-- 角色
create table role(
id bigint,
name varchar(50),
descn varchar(200)
);
alter table role add constraint pk_role primary key(id);
alter table role alter column id bigint generated by default as identity(start with 1);
-- 用户
create table user(
id bigint,
username varchar(50),
password varchar(50),
status integer,
descn varchar(200)
);
alter table user add constraint pk_user primary key(id);
alter table user alter column id bigint generated by default as identity(start with 1);
-- 用户角色连接表
create table user_role(
user_id bigint,
role_id bigint
);
alter table user_role add constraint pk_user_role primary key(user_id, role_id);
alter table user_role add constraint fk_user_role_user foreign key(user_id) references user(id);
alter table user_role add constraint fk_user_role_role foreign key(role_id) references role(id);
上述共有三张表,其中user 用户表,role 角色表为保存用户权限数据的主表,user_role 为关联表。user 用户表,role 角色表之间为多对多关系,就是说一个用户可以有多个角色。
初始化数据:
创建两个用户,admin 和user。admin 用户拥有“管理员”角色,user 用户拥有“用户”角色。
insert into user(id,username,password,status,descn) values(1,'admin','admin',1,'管理员');
insert into user(id,username,password,status,descn) values(2,'user','user',1,'用户');
insert into role(id,name,descn) values(1,'ROLE_ADMIN','管理员角色');
insert into role(id,name,descn) values(2,'ROLE_USER','用户角色');
insert into user_role(user_id,role_id) values(1,1);
insert into user_role(user_id,role_id) values(1,2);
insert into user_role(user_id,role_id) values(2,2);
获得自定义用户权限信息:
接下来我们需要通过Spring-Security来进行用户验证,验证1:用户登录是否合法(用户名以及密码是否正确),验证2:
当前用户具有什么权限,是否能够访问相应的资源。
我们所要做的工作就是在现有数据结构的基础上,为Spring Security 提供这两种数据。
当用户登陆时,系统需要判断用户登录名是否存在,登陆密码是否正确,当前用户是否被禁用。我们使用下列SQL 来提取这三个信息。
select username,password,status as enabled from user where username=?
检验用户权限:
select u.username,r.name as authority from user u join user_role ur on u.id=ur.user_id join role r
on r.id=ur.role_id where u.username=?
将这两条SQL 语句配置到xml 中,就可以让Spring Security 从我们自定义的表结构中提取数据了。最终配置文件如下所示:
<authentication-provider>
<jdbc-user-service data-source-ref="dataSource"
users-by-username-query="select username,password,status as enabled from user where username=?"
authorities-by-username-query="select u.username,r.name as authority from user u join user_role ur on u.id=ur.user_id join role r on r.id=ur.role_id where u.username=?"/>
</authentication-provider>
users-by-username-query 为根据用户名查找用户,系统通过传入的用户名查询当前用户的登录名,密码和是否被禁用这一状态。
authorities-by-username-query 为根据用户名查找权限,系统通过传入的用户名查询当前用户已被授予的所有权限。
四、自定义登陆页面
自己实现一个 login.jsp,放在src/main/webapp/目录下。在 xml 中的http 标签中添加一个form-login 标签。
<http auto-config='true'>
<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" /> --1--
<intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />
<intercept-url pattern="/**" access="ROLE_USER" />
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" --2--
default-target-url="/" />
</http>
1:让没登陆的用户也可以访问login.jsp,当access设置为IS_AUTHENTICATED_ANONYMOUSLY,任何人都能访问此资源。
2:login-page 表示用户登陆时显示我们自定义的login.jsp。使用我们自定义的jsp进行登录,authentication-failure-url 表示用户登陆失败时,跳转到哪个页面。default-target-url表示登陆成功时,跳转到哪个页面。
以下是我们创建的 login.jsp 页面的主要代码。
<div class="error ${param.error == true ? '' : 'hide'}">
登陆失败<br>
${sessionScope['SPRING_SECURITY_LAST_EXCEPTION'].message}
</div>
<form action="${pageContext.request.contextPath}/j_spring_security_check " style="width:260px;text-align:center;">
<fieldset>
<legend>登陆</legend>
用户: <input type="text" name="j_username " style="width:150px;"
value="${sessionScope['SPRING_SECURITY_LAST_USERNAME']}"/><br />
密码: <input type="password" name="j_password " style="width:150px;" /><br />
<input type="checkbox" name="_spring_security_remember_me " />两周之内不必登陆
<br />
<input type="submit" value="登陆"/>
<input type="reset" value="重置"/>
</fieldset>
</form>
其中:/j_spring_security_check,提交登陆信息的URL 地址,j_username,输入登陆名的参数名称。j_password,输入密码的参数名称._spring_security_remember_me,选择是否允许自动登录的参数名称(可以直接把这个参数设置为一个checkbox,无需设置value,SpringSecurity 会自行判断它是否被选中。)
以上介绍了自定义页面上Spring Security 所需的基本元素,这些参数名称都采用了Spring Security 中默认的配置值,如果有特殊需要还可以通过配置文件进行修改。
经过以上配置,我们终于使用了一个自己创建的登陆页面替换了原来 SpringSecurity 默认提供的登录页面了。我们不仅仅是做个样子,而是实际配置了各个Spring Security 所需的参数,真正将自定义登陆页面与Spring Security 紧紧的整合在了一起。