前一篇文章里介绍了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的知识不是本文介绍的内容。到这里保护业务方法的配置就介绍完了