微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!
spring security使用数据库管理用户权限
<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数据库,从中获取用户权限信息。[1]<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>最终的配置文件如下所示:<?xml version="1.0" encoding="UTF-8"?><beans:beans xmlns="http://www.springframework.org/schema/security"xmlns:beans="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/securityhttp://www.springframework.org/schema/security/spring-security.xsd"><http auto-config='true'><intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /><intercept-url pattern="/**" access="ROLE_USER" /></http><authentication-manager><authentication-provider><jdbc-user-service data-source-ref="dataSource"/></authentication-provider></authentication-manager>//连接数据库<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></beans:beans>数据库表结构Spring Security默认情况下需要两张表,用户表和权限表。以下是hsqldb中的建表语句:create table users(1username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(50) not null,enabled boolean not null);create table authorities (2username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));create unique index ix_auth_username on authorities (username,authority);31.users:用户表。包含username用户登录名,password登陆密码,enabled用户是否被禁用三个字段。其中username用户登录名为主键。 2.authorities:权限表。包含username用户登录名,authorities对应权限两个字段。其中username字段与users用户表的主键使用外键关联。3.对authorities权限表的username和authority创建唯一索引,提高查询效率Spring Security会在初始化时,从这两张表中获得用户信息和对应权限,将这些信息保存到缓存中。其中users表中的登录名和密码用来控制用户的登录,而权限表中的信息用来控制用户登陆后是否有权限访问受保护的系统资源。我们在示例中预先初始化了一部分数据:insert into users(username,password,enabled) values('admin','admin',true);insert into users(username,password,enabled) values('user','user',true);insert into authorities(username,authority) values('admin','ROLE_ADMIN');insert into authorities(username,authority) values('admin','ROLE_USER');insert into authorities(username,authority) values('user','ROLE_USER');这个实现和之前将用户和用户权限写在配置文件中明显方便很多。尤其是用户数量过多时。不过这种方法是保持最基本的表结构,也是默认的表结构。最好在理解原理基础上自定义数据库。(方法后续讲到。)
spring security方法一 自定义数据库表结构
Spring Security默认提供的表结构太过简单了,其实就算默认提供的表结构很复杂,也无法满足所有企业内部对用户信息和权限信息管理的要求。基本上每个企业内部都有一套自己的用户信息管理结构,同时也会有一套对应的权限信息体系,如何让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角色表之间为多对多关系,就是说一个用户可以有多个角色。ER图如下所示:初始化数据创建两个用户,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,Spring Security所需要的数据只是为了处理两种情况,一是判断登录用户是否合法,二是判断登陆的用户是否有权限访问受保护的系统资源。我们所要做的工作就是在现有数据结构的基础上,为Spring Security提供这两种数据。处理用户登陆select username,password,status as enabledfrom userwhere username=?检验用户权限select u.username,r.name as authorityfrom user ujoin user_role uron u.id=ur.user_idjoin role ron r.id=ur.role_idwhere u.username=?"/>将语句配置到xml中<authentication-provider><jdbc-user-service data-source-ref="dataSource"1users-by-username-query="select username,password,status as enabledfrom userwhere username=?"2authorities-by-username-query="select u.username,r.name as authorityfrom user ujoin user_role uron u.id=ur.user_idjoin role ron r.id=ur.role_idwhere u.username=?"/></authentication-provider>users-by-username-query为根据用户名查找用户,系统通过传入的用户名查询当前用户的登录名,密码和是否被禁用这一状态。authorities-by-username-query为根据用户名查找权限,系统通过传入的用户名查询当前用户已被授予的所有权限。
spring security使用数据库资源
国内对权限系统的基本要求是将用户权限和被保护资源都放在数据库里进行管理,在这点上Spring Security并没有给出官方的解决方案,为此我们需要对Spring Security进行扩展。、数据库表结构这次我们使用五张表,user用户表,role角色表,resc资源表相互独立,它们通过各自之间的连接表实现多对多关系。我们自己定义的表结构。-- 资源create table resc(id bigint,name varchar(50),res_type varchar(50),res_string varchar(200),priority integer,descn varchar(200));alter table resc add constraint pk_resc primary key(id);alter table resc alter column id bigint generated by default as identity(start with 1);-- 角色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 resc_role(resc_id bigint,role_id bigint);alter table resc_role add constraint pk_resc_role primary key(resc_id, role_id);alter table resc_role add constraint fk_resc_role_resc foreign key(resc_id) references resc(id);alter table resc_role add constraint fk_resc_role_role foreign key(role_id) references role(id);-- 用户角色连接表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);ER图如下表示图 5.1. 数据库表关系我们在已有表结构中插入一些数据。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 resc(id,name,res_type,res_string,priority,descn) values(1,'','URL','/admin.jsp',1,'');insert into resc(id,name,res_type,res_string,priority,descn) values(2,'','URL','/**',2,'');insert into resc_role(resc_id,role_id) values(1,1);insert into resc_role(resc_id,role_id) values(2,1);insert into resc_role(resc_id,role_id) values(2,2);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没有提供从数据库获得获取资源信息的方法,实际上Spring Security甚至没有为我们留一个半个的扩展接口,所以我们这次要费点儿脑筋了。首先,要搞清楚需要提供何种类型的数据,然后,寻找可以让我们编写的代码替换原有功能的切入点,实现了以上两步之后,就可以宣布大功告成了。1.从配置文件上可以看到,Spring Security所需的数据应该是一系列URL网址和访问这些网址所需的权限:<intercept-url pattern="/login.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY" /><intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /><intercept-url pattern="/**" access="ROLE_USER" />select re.res_string,r.namefrom role rjoin resc_role rron r.id=rr.role_idjoin resc reon re.id=rr.resc_idorder by re.priority完整代码package com.family168.springsecuritybook.ch005;import java.sql.ResultSet;import java.sql.SQLException;import java.util.Collection;import java.util.LinkedHashMap;import java.util.List;import java.util.Map;import javax.sql.DataSource;import org.springframework.beans.factory.FactoryBean;import org.springframework.jdbc.core.support.JdbcDaoSupport;import org.springframework.jdbc.object.MappingSqlQuery;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.access.ConfigAttributeEditor;import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;import org.springframework.security.web.access.intercept.RequestKey;import org.springframework.security.web.util.AntPathRequestMatcher;import org.springframework.security.web.util.RequestMatcher;public class JdbcFilterInvocationDefinitionSourceFactoryBeanextends JdbcDaoSupport implements FactoryBean {private String resourceQuery;public boolean isSingleton() {return true;}public Class getObjectType() {return FilterInvocationSecurityMetadataSource.class;}public Object getObject() {return new DefaultFilterInvocationSecurityMetadataSource(this.buildRequestMap());}protected Map<String, String> findResources() {ResourceMapping resourceMapping = new ResourceMapping(getDataSource(),resourceQuery);Map<String, String> resourceMap = new LinkedHashMap<String, String>();for (Resource resource : (List<Resource>) resourceMapping.execute()) {String url = resource.getUrl();String role = resource.getRole();if (resourceMap.containsKey(url)) {String value = resourceMap.get(url);resourceMap.put(url, value + "," + role);} else {resourceMap.put(url, role);}}return resourceMap;}protected LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> buildRequestMap() {LinkedHashMap<RequestMatcher, Collection<ConfigAtt
spring security 控制用户信息用户加密 缓存用户信息
1. MD5加密任何一个正式的企业应用中,都不会在数据库中使用明文来保存密码的,我们在之前的章节中都是为了方便起见没有对数据库中的用户密码进行加密,这在实际应用中是极为幼稚的做法。可以想象一下,只要有人进入数据库就可以看到所有人的密码,这是一件多么恐怖的事情,为此我们至少要对密码进行加密,这样即使数据库被攻破,也可以保证用户密码的安全。最常用的方法是使用MD5算法对密码进行摘要加密,这是一种单项加密手段,无法通过加密后的结果反推回原来的密码明文。首先我们要把数据库中原来保存的密码使用MD5进行加密:<authentication-provider><password-encoder hash="md5"/><jdbc-user-service data-source-ref="dataSource"/></authentication-provider>启用MD5算法。用户登录时,输入的密码是明文,需要使用password-encoder将明文转换成md5形式,然后再与数据库中的已加密密码进行比对。这些配置对普通客户不会造成任何影响,他们只需要输入自己的密码,Spring Security会自动加以演算,将生成的结果与数据库中保存的信息进行比对,以此来判断用户是否可以登陆。这样,我们只添加了一行配置,就为系统带来了密码加密的功能。2. 盐值加密上面的实例在现实使用中还存在着一个不小的问题。虽然md5算法是不可逆的,但是因为它对同一个字符串计算的结果是唯一的,所以一些人可能会使用“字典攻击”的方式来攻破md5加密的系统[5]。这虽然属于暴力解密,却十分有效,因为大多数系统的用户密码都不回很长。实际上,大多数系统都是用admin作为默认的管理员登陆密码,所以,当我们在数据库中看到“21232f297a57a5a743894a0e4a801fc3”时,就可以意识到admin用户使用的密码了。因此,md5在处理这种常用字符串时,并不怎么奏效。为了解决这个问题,我们可以使用盐值加密“salt-source”。修改配置文件:<authentication-provider><password-encoder hash="md5"><salt-source user-property="username"/></password-encoder><jdbc-user-service data-source-ref="dataSource"/></authentication-provider>盐值的原理非常简单,就是先把密码和盐值指定的内容合并在一起,再使用md5对合并后的内容进行演算,这样一来,就算密码是一个很常见的字符串,再加上用户名,最后算出来的md5值就没那么容易猜出来了。因为攻击者不知道盐值的值,也很难反算出密码原文。3. 用户信息缓存介于系统的用户信息并不会经常改变,因此使用缓存就成为了提升性能的一个非常好的选择。Spring Security内置的缓存实现是基于ehcache的,为了启用缓存功能,我们要在配置文件中添加相关的内容。<authentication-provider><password-encoder hash="md5"><salt-source user-property="username"/></password-encoder><jdbc-user-service data-source-ref="dataSource" cache-ref="userCache"/></authentication-provider>我们在jdbc-user-service部分添加了对userCache的引用,它将使用这个bean作为用户权限缓存的实现。对userCache的配置如下所示:<beans:bean id="userCache" class="org.springframework.security.core.userdetails.cache.EhCacheBasedUserCache"><beans:property name="cache" ref="userEhCache"/></beans:bean><beans:bean id="userEhCache" class="org.springframework.cache.ehcache.EhCacheFactoryBean"><beans:property name="cacheManager" ref="cacheManager"/><beans:property name="cacheName" value="userCache"/></beans:bean><beans:bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/> EhCacheBasedUserCache是Spring Security内置的缓存实现,它将为jdbc-user-service提供缓存功能。它所引用的userEhCache来自spring提供的EhCacheFactoryBean和EhCacheManagerFactoryBean,对于userCache的缓存配置放在ehcache.xml中:<ehcache><diskStore path="java.io.tmpdir"/><defaultCachemaxElementsInMemory="1000"eternal="false"timeToIdleSeconds="120"timeToLiveSeconds="120"overflowToDisk="true"/><cachename="userCache"maxElementsInMemory="100"//内存中最多存放100个对象eternal="false"//不是永久缓存timeToIdleSeconds="600"//最大空闲时间是600stimeToLiveSeconds="3600"//最大活动时间是3600soverflowToDisk="true"//如果内存对象溢出则保存到磁盘/></ehcache> 如果想了解有关ehcache的更多配置,可以访问它的官方网站http://ehcache.sf.net/。这样,我们就为用户权限信息设置好了缓存,当一个用户多次访问应用时,不需要每次去访问数据库了,ehcache会将对应的信息缓存起来,这将极大的提高系统的相应速度,同时也避免数据库符合过高的风险。 注意cache-ref隐藏着一个陷阱,如果不看代码,我们也许会误认为cache-ref会在JdbcUserDetailsManager中设置对应的userCache,然后只要直接执行JdbcUserDetailsManager中的方法,就可以自动维护用户缓存。可惜,cache-ref实际上是在JdbcUserDetailsManager的基础上,生成了一个CachingUserService,这个CachedUserDetailsService会拦截loadUserByUsername()方法,实现读取用户信息的缓存功能。我们在cache-ref中引用的UserCache实际上是放在CacheUserDetailsService中,而不是放到了原有的JdbcUserDetailsManager中,这就会导致JdbcUserDetailsManager中对用户缓存的操作全部失效。 4. 获取当前用户信息如果只是想从页面上显示当前登陆的用户名,可以直接使用Spring Security提供的taglib。<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %><div>username : <sec:authentication property="name"/></div>如果想在程序中获得当前登陆用户对应的对象。UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();如果想获得当前登陆用户所拥有的所有权限。Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) userDetails.getAuthorities();;关于UserDetails是如何放到SecuirtyContext中去的,以及Spring Security所使用的TheadLocal模式,我们会在后面详细介绍。这里我们已经了解了如何获得当前登陆用户的信息。