前一篇文章里介绍了Spring
Security的一些基础知识,相信你对Spring
Security的工作流程已经有了一定的了解,如果你同时在读源代码,那你应该可以认识的更深刻。在这篇文章里,我们将对Spring
Security进行一些自定义的扩展,比如自定义实现UserDetailsService
,保护业务方法以及如何对用户权限等信息进行动态的配置管理。
说明:
如果你通过Google搜索,可以找到很
多类似主题的文章,本文的目的在于通过这些实例来介绍的工作
原理,我觉得这些才是最重要的。相信你在读完本文之后应该可以按照自己的想法去扩展Spring
Secu
rity,这也是我写这两篇文章的目的。希望能对 初学者们有所帮助。
|
|
废话少说,咱们直接进入正题。
一
自定义UserDetailsService实现
UserDetailsService
接口,这个接口中只定义了唯一的UserDetails
loadUserByUsername(String username)
方法,它通过用户名来获取整个UserDetails
对象。
前一篇文章已经介绍了系统提供的默认实现方式
InMemoryDaoImpl,它从配置文件中读取用户的身份信息(用户名,密码等),如果你的客户想修改用户信息,就需要直接修改配置文件(你需要告诉用户配置文件的路径,应该在什么地方修改,如何把明文密码通过
MD5加密以及如何重启服务器等)。听起来是不是很费劲啊!
在实际应用中,我们可能需要提供动态的方式来获取用户身份信息,最常用的莫过于数据库了,当然也可以是
LDAP服务器等。本文首先介绍系统提供的一个默认实现类
JdbcDaoImpl
(
org.springframework.security.userdetails.jdbc.
JdbcDaoImpl
),它通过用户名从数据库中获取用户身份信息,修改配置文件,将
userDetailsService Bean
的配置修改如下:
<!---->1
<
bean
id
="userDetailsService"
2
class
="org.springframework.security.userdetails.jdbc.JdbcDaoImpl"
3
p:dataSource-ref
="dataSource"
4
p:usersByUsernameQuery
="select userName, passWord, enabled, from users where userName=?"
5
p:authoritiesByUsernameQuery
="select
6
u.userName,r.roleName from users u,roles
7
r,users_roles ur where u.userId=ur.userId and
8
r.roleId=ur.roleId and u.userName=?"
/>
JdbcDaoImpl
类继承自Spring
Framework的JdbcDaoSupport类并实现了UserDetailsService接口,因为从数据库中读取信息,所以首先需要一个数据源对象,这里不在多说,这里需要重点介绍的是usersByUsernameQuery
和authoritiesByUsernameQuery
,属性,它们的值都是一条SQL语句,JdbcDaoImpl类通过SQL从数据库中检索相应的信息,usersByUsernameQuery属性定义了通过用户名检索用户信息的SQL语句,包括用户名,密码以及用户是否可用,authoritiesByUsernameQuery属性定义了通过用户名检索用户权限信息的SQL语句,这两个属性都引用一个
MappingSqlQuery(请参考
Spring Framework相关资料)
实
例,
MappingSqlQuery的
mapRow()
方法将一个
ResultSet(结果集)中的字段映射
为
一个
领
域
对
象
,
Spring
Security
为我们提供了默认的数据库表,如下图所示
(摘自《Spring in Action
》)
:
图
<!--[if supportFields]-->1
<!--[if supportFields]-->
JdbcDaoImp
数据库表
如果我们需要获取用户的其它信息就需要自己来扩展系统的默认实现,首先应该了解一下UserDetailsService实现的原理,还是要回到源代码,以下是JdbcDaoImpl类的部分代码:
<!----> 1
private
class
UsersByUsernameMapping
extends
MappingSqlQuery {
2
3
protected
UsersByUsernameMapping(DataSource ds) {
4
5
super
(ds, usersByUsernameQuery);
6
7
declareParameter(
new
SqlParameter(Types.VARCHAR));
8
9
compile();
10
11
}
12
13
protected
Object mapRow(ResultSet rs,
int
rownum)
throws
SQLException {
14
15
String username
=
rs.getString(
1
);
16
17
String password
=
rs.getString(
2
);
18
19
boolean
enabled
=
rs.getBoolean(
3
);
20
21
UserDetails user
=
new
User(username, password, enabled,
true
,
22
23
true
,
true
,
new
GrantedAuthority[] {
new
GrantedAuthorityImpl(
"
HOLDER
"
)});
24
25
return
user;
26
}
27
28
}
也许你已经看出什么来了,对了,系统返回的UserDetails对象就是从这里来的,这就是读源代码的好处,DaoAuthenticationProvider
提供者通过调用自己的authenticate(Authentication
authentication)
方法将用户在登录页面输入的用户信息与这里从数据库获取的用户信息进行匹配,如果匹配成功则将用户的权限信息赋给Authentication对象并将其存放在SecurityContext中,供其它请求使用。
那么我们要扩展获得更多的用户信息,就要从这里下手了(数据库表这里不在列出来,可以参考项目的WebRoot/db目录下的schema.sql
文件)。比如我们自己的数据库设计中是通过一个loginId和用户名来登录或者我们需要额外ID,EMAIL地址等信息,
MySecurityJdbcDaoImpl
实现如下:
<!----> 1
protectedclass UsersByUsernameMapping
extends
MappingSqlQuery {
2
3
protected
UsersByUsernameMapping(DataSource ds) {
4
5
super
(ds, usersByUsernameQuery);
6
7
declareParameter(
new
SqlParameter(Types.VARCHAR));
8
9
compile();
10
11
}
12
13
protected
Object mapRow(ResultSet rs,
int
rownum)
throws
SQLException {
14
15
//
TODO Auto-generated method stub
16
17
String userName
=
rs.getString(
1
);
18
19
String passWord
=
rs.getString(
2
);
20
21
boolean
enabled
=
rs.getBoolean(
3
);
22
23
Integer userId
=
rs.getInt(
4
);
24
25
String email
=
rs.getString(
5
);
26
27
MyUserDetails user
=
new
MyUser(userName, passWord, enabled,
true
,
28
true
,
true
,
new
GrantedAuthority[]{
new
29
GrantedAuthorityImpl(
"
HOLDER
"
)});
30
31
user.setEmail(email);
32
33
user.setUserId(userId);
34
35
return
user;
36
37
}
38
39
}
如果你已经看过源代码,你会发现这里只是其中的一部分代码
,具体的实现请看项目的
MySecurityJdbcDaoImpl
类实现,以及
MyUserDetails
和
MyUser
类,这里步在一一列出。
如果使用Hibernate来操作数据库,你也可以从你的DAO中获取用户信息,最后你只要将存放了用户身份信息和权限信息的列表(List)
返回给系统就可以。
提示:
这里没有介绍更多的细节问题,主要还是想你自己能通过读源代码来加深理解,本人水平也有限,
相信你从源代码中能悟出更多的有价值的东西。
|
|
每当用户请求一个受保护的资源时,就会调用认证管理器以获取用户认证信息,但是如果我们的用户信息保存在数据库中,那么每次请求都从数据库中获取信息将会影响系统性能,那么将用户信息进行缓存就有必要了,下面就介绍如何在Spring
Security中使用缓存。
二
缓存用户信息
查看
AuthenticationProvider
接口的实现类
AbstractUserDetailsAuthenticationProvider
抽象类(我们配置文件中配置的
DaoAuthenticationProvider
类继承了该类)的源代码,会有一行代码:
<!---->1
UserDetails user
=
this
.userCache.getUserFromCache(username);
DaoAuthenticationProvider认证提供者使用
UserCache接口
的实现来实现对用户信息的缓存,修改
DaoAuthenticationProvider的配置如下:
<!---->1
<
bean
id
="daoAuthenticationProvider"
2
class
="org.springframework.security.providers.dao.DaoAuthenticationProvider"
3
p:userCache-ref
="userCache"
4
p:passwordEncoder-ref
="passwordEncoder"
5
p:userDetailsService- ref
="userDetailsService"
/>
这里我们加入了对
userCache Bean
的引用,
userCache使用
Ehcache
来实现对用户信息的缓存。
userCache配置如下:
<!----> 1
<
bean
id
="userCache"
2
class
="org.springframework.security.providers.dao.cache.EhCacheBasedUserCache"
3
p:cache-ref
="cache"
/>
4
<
bean
id
="cache"
5
class
="org.springframework.cache.ehcache.EhCacheFactoryBean"
6
p:cacheManager-ref
="cacheManager"
7
p:cacheName
="userCache"
/>
8
<
bean
id
="cacheManager"
9
class
="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"
10
p:configLocation
="classpath:ehcache.xml"
>
11
</
bean
>
我们这里使用的是
EhCacheBasedUserCache
,也就是用
EhCache
实现缓存的,另外系统还提供了一个默认的实现类
NullUserCache
类,我们可以通过源代码了解到,无论上面使用这个类都返回一个
null
值,也就是不使用缓存。
三
保护业务方法
从第一篇文章中我们已经了解到,Spring
Security使用Servlet过滤器来拦截用户的请求来保护WEB资源,而这里却是使用Spring
框架的AOP来提供对方法的声明式保护。它通过一个拦截器来拦截方法调用,并调用方法安全拦截器来保护方法。
在介绍之前,我们先回忆一下过滤器安全拦截器是如何工作的。过滤器安全拦截器
首先调用AuthenticationManager
认证管理器认证用户信息,如果用过认证则调用AccessDecisionManager
访问决策管理器来验证用户是否有权限访问objectDefinitionSource
中配置的受保护资源。
首先看看如何配置方法安全拦截器,它和过滤器安全拦截器一方继承自AbstractSecurityInterceptor
抽象类(请看源代码),如下:
<!---->1
<
bean
id
="methodSecurityInterceptor"
2
class
="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor"
3
p:authenticationManager-ref
="authenticationManager"
4
p:accessDecisionManager-ref
="accessDecisionManager"
>
5
<
property
name
="objectDefinitionSource"
>
6
<
value
>
com.test.service.UserService.get*=ROLE_SUPERVISOR
7
</
value
>
8
</
property
>
9
</
bean
>
这段代码是不是很眼熟啊,哈哈~,这和我们配置的过滤器安全拦截器几乎完全一样,方法安全拦截器的处理过程实际和过滤器安全拦截器的实现机制是相同的,这里就在累述,详细介绍请参考<
Spring Security
学习总结一>中相关部分。但是也有不同的地方,那就是这里的
objectDefinitionSource的配置,在等号前面的不在是
URL资源,而是需要保护的业务方法,等号后面还是访问该方法需要的用户权限。我们这里配置的
com.test.service.UserService.get*
表示对
com.test.service
包下
UserService
类的所有以
get
开头的方法都需要
ROLE_SUPERVISOR
权限才能调用。这里使用了
提供的实现方法
MethodSecurityInterceptor
,系统还给我们提供了
aspectj
的实现方式,这里不在介绍(我也正在学
…
),读者可以参考其它相关资料。
之前已经提到过了,Spring
Security使用Spring 框架的AOP来提供对方法的声明式保护,即拦截方法调用,那么接下来就是创建一个拦截器,配置如下:
<!----> 1
<
bean
id
="autoProxyCreator"
2
class
="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"
>
3
<
property
name
="interceptorNames"
>
4
<
list
>
5
<
value
>
methodSecurityInterceptor
</
value
>
6
</
list
>
7
</
property
>
8
<
property
name
="beanNames"
>
9
<
list
>
10
<
value
>
userService
</
value
>
11
</
list
>
12
</
property
>
13
</
bean
>
userService
是我们在applicationContext.xml中配置的一个Bean,AOP的知识不是本文介绍的内容。到这里保护业务方法的配置就介绍完了。
四
将资源放在数据库中
现在,你的用户提出了新的需求,它们需要自己可以给系统用户分配或者取消权限。其实这个并不是什么新鲜事,作为开发者,你也应该为用户提供这样的功能。那么我们就需要这些受保护的资源和用户权限等信息都是动态的,你可以选择把它们存放在数据库中或者LDAP服务器上,本文以数据库为例,介绍如何实现用户权限的动态控制。
通过前面的介绍,你可能也注意到了,不管是
MethodSecurityInterceptor
还是FilterSecurityInterceptor
都使用authenticationManager
和accessDecisionManager
属性用于验证用户,并且都是通过使用objectDefinitionSource
属性来定义受保护的资源。不同的是过滤器安全拦截器将URL资源与权限关联,而方法安全拦截器将业务方法与权限关联。
你猜对了,我们要做的就是自定义这个
objectDefinitionSource
的实现,首先让我们来认识一下系统为我们提供的ObjectDefinitionSource接口
,objectDefinitionSource属性正是指向此接口的实现类。该接口中定义了3
个方法,ConfigAttributeDefinition getAttributes(Object
object)
方法用户获取保护资源对应的权限信息,该方法返回一个ConfigAttributeDefinition对象(位于org.springframework.security
包下),通过源代码我们可以知道,该对象中实际就只有一个List列表,我们可以通过使用ConfigAttributeDefinition类的构造函数来创建这个List列表,这样,安全拦截器就通过调用getAttributes(Object
object)
方法来获取ConfigAttributeDefinition对象,并将该对象和当前用户拥有的Authentication
对象传递给accessDecisionManager
(访问决策管理器,请查看org.springframework.security.vote
包下的AffirmativeBased
类,该类是访问决策管理器的一个实现类,它通过一组投票者来决定用户是否有访问当前请求资源的权限),访问决策管理器在将其传递给AffirmativeBased类维护的投票者,这些投票者从ConfigAttributeDefinition对象中获取这个存放了访问保护资源需要的权限信息的列表,然后遍历这个列表并与Authentication
对象中GrantedAuthority[]
数据中的用户权限信息进行匹配,如果匹配成功,投票者就会投赞成票,否则就投反对票,最后访问决策管理器来统计这些投票决定用户是否能访问该资源。是不是又觉得乱了,还是那句话,如果你结合源代码你现在一定更明白了。
分享到:
相关推荐
在“Spring Security学习总结2_2”中,我们可能涉及了对Spring Security更深入的探讨,特别是关于其核心概念和实际应用的细节。 首先,Spring Security的核心组件包括Security Context(安全上下文)、...
"springsecurity-namespace"可能指的是Spring Security的XML命名空间配置。在Spring Security的早期版本中,使用XML配置是最常见的实践。例如,你可能会看到以下片段: ```xml **" access="hasRole('ROLE_ADMIN')...
1. **Filter Security Interceptor**:这是SpringSecurity的主要过滤器,负责检查请求并决定是否允许访问。它会根据预定义的访问控制规则进行判断。 2. **Authentication Manager**:处理用户认证的组件,可以使用...
【Spring Security 学习总结】 Spring Security 是一个强大的和高度可定制的身份验证和访问控制框架,用于保护基于 Java 的应用程序。本学习总结文档主要针对初学者,旨在剖析一个不安全的应用程序并阐述如何通过 ...
在"Spring Security学习总结一(补命名空间配置)"的文件中,可能涵盖了如何在Spring Security的XML配置中补充命名空间的步骤。命名空间的引入是为了简化配置,例如`<http>`元素用于配置安全拦截和访问规则,`...
SpringSecurity学习总结一.pdf
总结来说,"REST-spring-security"项目展示了如何使用Spring Security来保护RESTful Web服务,包括身份验证、授权、访问控制和安全配置。这个项目可以帮助开发者理解如何在实际应用中实现安全的REST API,确保服务的...
### Spring Security 学习总结 #### 一、Spring Security 概述 Spring Security 是一个功能强大的安全框架,它为基于 Java 的应用程序提供了认证(authentication)和授权(authorization)功能。在 Spring ...
《Spring Security 3.1 学习指南及资源解析》 Spring Security是Java平台上的一款强大且高度可定制的安全框架,广泛应用于企业级Web应用的安全管理。本篇文章将围绕"Spring Security 3.1"这一主题,深入探讨其核心...
### Spring Security 概述与应用实践 #### 一、引言 在当今互联网时代,网络安全问题日益凸显,尤其是Web应用程序的...同时,结合实际案例的学习,能够帮助我们更好地理解和掌握Spring Security的核心概念与使用技巧。
总结,Spring Security 3的源码分析是一个深度学习的过程,涵盖了安全领域的多个方面。通过理解其内部工作机制,开发者可以更好地利用这一强大的框架,为应用程序提供安全的保障。同时,源码分析也能帮助开发者解决...
对于初学者,建议从官方文档或教程开始学习,逐步熟悉Spring Security的安装、配置和使用流程。 #### Security命名空间配置 Spring Security通过XML命名空间简化了安全相关的配置。这部分文档详细介绍了如何使用...
spring security 学习总结暑假的时候在学习了Spring安全并成功运用到了项目中。在实践中摸索出了一套结合JSON +智威汤逊(JSON网络令牌)+Spring引导+ Spring Security的技术的权限方案趁着国庆假期记录一下。 内容 ...
### Spring Security3中文教程知识点概览 #### 一、安全核心概念与起步 Spring Security是Spring框架中的一个重要组成部分,主要用于为Web应用提供安全防护。它不仅提供了强大的认证和授权功能,还支持各种加密...
总结起来,"springSecurityTest.zip"文件提供了一个完整的环境,让你可以动手实践Spring Security的基础用法。通过学习其中的代码、笔记和文档,你将理解Spring Security如何与Spring框架协同工作,如何设置认证和...