最近拿出几天看了下Spring Security2,Spring Security是从Acige发展而来,网上和基本参考书上找到的例子大多是Acige的,Spring Security的不多。
1.准备
必须先理顺以下几个Spring Security核心类的作用和关系
- Authentication :认证信息
- AuthenticationManager 认证管理器
- AuthenticationProvider 验证数据提供器
- UserDetailsService:用户信息服务接口
- AccessDecisionManager 授权器
常用过滤器
AuthenticationProcessingFilter 处理form登陆的过滤器,与form登陆有关的所有操作都是在此进行的。
LogoutFilter 只处理注销请求,默认为/j_spring_security_logout。
FilterSecurityInterceptor URL拦截过滤器
2.实体类
RBAC 模型 ,一共五张表:用户、用户角色、角色、角色权限、权限,本例使用Hibernate映射。
三个实体类:User、Role、Resource。
User 继承UserDetails接口,并实现到Role的单向多对多映射
public
class
User
implements
UserDetails{
private
static
final
long
serialVersionUID
=
-
1689430377678136883L
;
private
Integer id;
private
String name;
private
String password;
private
Integer disabled;
private
Set
<
Role
>
roles
=
new
HashSet
<
Role
>
(
0
);
//
单向多对多映射,用户登陆验证时可以获得权限,非延迟加载
@Override
public
GrantedAuthority[] getAuthorities() {
//获得用户的角色列表,GrantedAuthorityImpl存储的是Role名称
List
<
GrantedAuthority
>
grantedAuthorities
=
new
ArrayList
<
GrantedAuthority
>
(roles.size());
for
(Role role :
this
.roles) { grantedAuthorities.add(
new
GrantedAuthorityImpl(role.getName())); }
return
grantedAuthorities.toArray(
new
GrantedAuthority[roles.size()]); }
Role比较简单,POJO类
public
class
Role
implements
java.io.Serializable {
private
static
final
long
serialVersionUID
=
-
953612771016471024L
;
private
Integer id;
private
String name; }
Resouce类需要单向关联Role
public
class
Resource
implements
java.io.Serializable {
private
static
final
long
serialVersionUID
=
5604673911728289859L
;
private
Integer id;
private
String type;//资源类型,例如URL
private
String value;
private
Set
<
Role
>
roles
=
new
HashSet
<
Role
>
(
0
);
//
Hibernate配置
}
3.DAO层代码
两个DAO类,UserDao和ResourceDao
UserDao类
public
interface
UserDao {
public
User loadUserByUsername(String username);
//
通过用户名获得User
}
ResourceDao类
public
interface
ResourceDao {
public
List
<
Resource
>
loadResourceByType(String type);
//
根据类型获得Resource队列,例如获得URL资源 loadResourceByType("URL")
}
4.Security Support支持类
UserDetailsServiceImpl继承org.springframework.security.userdetails.UserDetails接口,只需要实现一个方法
public
class
UserDetailsServiceImpl
implements
UserDetailsService {
private
UserDao userDao;//spring注入 @Override
public
UserDetails loadUserByUsername(String username)
throws
UsernameNotFoundException, DataAccessException {
return
userDao.loadUserByUsername(username); }//没有用户时抛出UsernameNotFoundException异常
public
void
setUserDao(UserDao userDao) {
this
.userDao
=
userDao; } }
URLDefinitionSourceFactory的目的是构造一个DefaultFilterInvocationDefinitionSource类
public
class
URLDefinitionSourceFactory
implements
FactoryBean{
/**
* 这个类的目的就是构建一个DefaultFilterInvocationDefinitionSource类, * DefaultFilterInvocationDefinitionSource是默认提供的FilterInvocationDefinitionSource实现类,省力 * 本类继承FactoryBean给DefaultFilterInvocationDefinitionSource构造的两个参数赋值 * 关于FactoryBean接口的使用,Spring新手自己查下
*/
private
ResourceDao resourceDao;
//
Spring注入
//
返回DefaultFilterInvocationDefinitionSource构造参数之一
private
UrlMatcher getUrlMatcher() {
return
new
AntUrlPathMatcher();
//
这个比RegexUrlPathMatcher简单,所以用这个,不求甚解
}
//
返回DefaultFilterInvocationDefinitionSource构造参数之二
private
LinkedHashMap
<
RequestKey, ConfigAttributeDefinition
>
getRequestMap()
throws
Exception { List
<
Resource
>
resources
=
resourceDao.loadResourceByType(
"
URL
"
);
//
取所有URL资源
LinkedHashMap
<
RequestKey, ConfigAttributeDefinition
>
requestMap
=
new
LinkedHashMap
<
RequestKey,ConfigAttributeDefinition
>
();
//
这个Map看起来比较唬人,把握两点:key为URL地址,value为允许访问该URL的角色S,URL与ROLE是多对多的关系
for
(Resource resource : resources) { Set
<
Role
>
roles
=
resource.getRoles();
int
i
=
roles.size(); String[] rolenames
=
new
String[i];
//
将该URL的的角色名称转化为一个String数组
for
(Role role : roles) { i
--
; rolenames[i]
=
role.getName(); } requestMap.put(
new
RequestKey(resource.getValue()),
new
ConfigAttributeDefinition(rolenames));
//
key为URL地址,用RequestKey封装
//
value为ConfigAttributeDefinition,可以理解为一个数组,该数组中存放ConfigAttribute
//
每个ConfigAttribute封装一个角色名称
}
return
requestMap; } @Override
public
Object getObject()
throws
Exception {
//
工厂方法
LinkedHashMap
<
RequestKey, ConfigAttributeDefinition
>
requestMap
=
getRequestMap();
//
取参数一
UrlMatcher matcher
=
getUrlMatcher();
//
取参数二
DefaultFilterInvocationDefinitionSource definitionSource
=
new
DefaultFilterInvocationDefinitionSource( matcher, requestMap);
//
构造完成
return
definitionSource; } @Override
public
Class
<
DefaultFilterInvocationDefinitionSource
>
getObjectType() {
//
FactoryBean接口方法
return
DefaultFilterInvocationDefinitionSource.
class
; } @Override
public
boolean
isSingleton() {
//
FactoryBean接口方法
return
true
; }
public
void
setResourceDao(ResourceDao resourceDao) {
this
.resourceDao
=
resourceDao; } }
5.applicationContext-security.xml配置文件
<?
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/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.6.xsd"
>
<!--
支持类之一 userDatailsService接口实现
-->
<
beans:bean
id
="userDatailsService"
class
="com.kaqike.security.security.support.UserDetailsServiceImpl"
>
<
beans:property
name
="userDao"
ref
="userDao"
/>
</
beans:bean
>
<!--
支持类之二 DefaultFilterInvocationDefinitionSource工厂类
-->
<
beans:bean
id
="URLDefinitionSourceFactory"
class
="com.kaqike.security.security.URLDefinitionSourceFactory"
>
<
beans:property
name
="resourceDao"
ref
="resourceDao"
/>
</
beans:bean
>
<!--
http 是Spring Security的关键配置,各个拦截器在此实现
-->
<
http
auto-config
='true'
>
<
concurrent-session-control
max-sessions
="1"
exception-if-maximum-exceeded
="true"
/>
</
http
>
<!--
userDatailsService配置,使用md5验证
-->
<
authentication-provider
user-service-ref
="userDatailsService"
>
<
password-encoder
hash
="md5"
></
password-encoder
>
</
authentication-provider
>
<
authentication-manager
alias
="authenticationManager"
/>
<!--
授权器
-->
<
beans:bean
id
="accessDecisionManager"
class
="org.springframework.security.vote.AffirmativeBased"
>
<
beans:property
name
="allowIfAllAbstainDecisions"
value
="false"
/>
<
beans:property
name
="decisionVoters"
>
<
beans:list
>
<
beans:bean
class
="org.springframework.security.vote.RoleVoter"
/>
<
beans:bean
class
="org.springframework.security.vote.AuthenticatedVoter"
/>
</
beans:list
>
</
beans:property
>
</
beans:bean
>
<!--
URL资源拦截器
-->
<
beans:bean
id
="URLSecurityInterceptor"
class
="org.springframework.security.intercept.web.FilterSecurityInterceptor"
>
<
beans:property
name
="authenticationManager"
ref
="authenticationManager"
/>
<
beans:property
name
="accessDecisionManager"
ref
="accessDecisionManager"
/>
<
beans:property
name
="objectDefinitionSource"
ref
="URLDefinitionSourceFactory"
/>
<
beans:property
name
="observeOncePerRequest"
value
="false"
/>
<
custom-filter
after
="LAST"
/>
</
beans:bean
>
<!--
登录成功监听器
-->
<
beans:bean
class
="com.kaqike.security.listener.LoginSuccessListener"
></
beans:bean
>
<!--
认证日志监听器
-->
<
beans:bean
id
="loggerListener"
class
="org.springframework.security.event.authentication.LoggerListener"
/>
</
beans:beans
>
6.其他
pom.xml文件
<
project
xmlns
="http://maven.apache.org/POM/4.0.0"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<
modelVersion
>
4.0.0
</
modelVersion
>
<
groupId
>
kaqike
</
groupId
>
<
artifactId
>
spring_security
</
artifactId
>
<
version
>
0.0.1-SNAPSHOT
</
version
>
<
name
>
spring_security
</
name
>
<
description
>
first spring security app
</
description
>
<
dependencies
>
<
dependency
>
<
groupId
>
org.springframework
</
groupId
>
<
artifactId
>
spring
</
artifactId
>
<
version
>
${spring.version}
</
version
>
<
type
>
jar
</
type
>
<
scope
>
compile
</
scope
>
<
exclusions
>
<
exclusion
>
<
artifactId
>
commons-logging
</
artifactId
>
<
groupId
>
commons-logging
</
groupId
>
</
e