知识大全 Spring Security学习总结二

Posted 资源

篇首语:智者不为愚者谋,勇者不为怯者死。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 Spring Security学习总结二相关的知识,希望对你有一定的参考价值。

Spring Security学习总结二  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

  前一篇文章里介绍了Spring Security的一些基础知识 相信你对Spring Security的工作流程已经有了一定的了解 如果你同时在读源代码 那你应该可以认识的更深刻 在这篇文章里 我们将对Spring Security进行一些自定义的扩展 比如自定义实现UserDetailsService 保护业务方法以及如何对用户权限等信息进行动态的配置管理

  一 自定义UserDetailsService实现

  UserDetailsService接口 这个接口中只定义了唯一的UserDetails loadUserByUsername(String username)方法 它通过用户名来获取整个UserDetails对象

  前一篇文章已经介绍了系统提供的默认实现方式InMemoryDaoImpl 它从配置文件中读取用户的身份信息(用户名 密码等) 如果你的客户想修改用户信息 就需要直接修改配置文件(你需要告诉用户配置文件的路径 应该在什么地方修改 如何把明文密码通过MD 加密以及如何重启服务器等) 听起来是不是很费劲啊!

  在实际应用中 我们可能需要提供动态的方式来获取用户身份信息 最常用的莫过于数据库了 当然也可以是LDAP服务器等 本文首先介绍系统提供的一个默认实现类JdbcDaoImpl( springframework security userdetails jdbc JdbcDaoImpl) 它通过用户名从数据库中获取用户身份信息 修改配置文件 将userDetailsService Bean的配置修改如下

 <bean id= userDetailsService  class= springframework security userdetails jdbc JdbcDaoImpl  p:dataSource ref= dataSource  p:usersByUsernameQuery= select userName  passWord  enabled  from users where userName=?  p:authoritiesByUsernameQuery= select   u userName r roleName from users u roles   r users_roles ur where u userId=ur userId and   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]——> <!——[if supportFields]——> JdbcDaoImp数据库表

  如果我们需要获取用户的其它信息就需要自己来扩展系统的默认实现 首先应该了解一下UserDetailsService实现的原理 还是要回到源代码 以下是JdbcDaoImpl类的部分代码

   private class UsersByUsernameMapping extends MappingSqlQuery            protected UsersByUsernameMapping(DataSource ds)                   super(ds  usersByUsernameQuery);                  declareParameter(new SqlParameter(Types VARCHAR));                  pile();                  protected Object mapRow(ResultSet rs  int rownum) throws SQLException                 String username = rs getString( );                String password = rs getString( );                boolean enabled = rs getBoolean( );                UserDetails user = new User(username  password  enabled  true                   true  true  new GrantedAuthority[] new GrantedAuthorityImpl( HOLDER ));                return user;           

  也许你已经看出什么来了 对了 系统返回的UserDetails对象就是从这里来的 这就是读源代码的好处 DaoAuthenticationProvider提供者通过调用自己的authenticate(Authentication authentication)方法将用户在登录页面输入的用户信息与这里从数据库获取的用户信息进行匹配 如果匹配成功则将用户的权限信息赋给Authentication对象并将其存放在SecurityContext中 供其它请求使用

  那么我们要扩展获得更多的用户信息 就要从这里下手了(数据库表这里不在列出来 可以参考项目的WebRoot/db目录下的schema sql文件) 比如我们自己的数据库设计中是通过一个loginId和用户名来登录或者我们需要额外ID EMAIL地址等信息 MySecurityJdbcDaoImpl实现如下

   protectedclass UsersByUsernameMapping extends MappingSqlQuery              protected UsersByUsernameMapping(DataSource ds)                  super(ds  usersByUsernameQuery);                 declareParameter(new SqlParameter(Types VARCHAR));                 pile();                      protected Object mapRow(ResultSet rs  int rownum) throws SQLException                // TODO Auto generated method stub               String userName = rs getString( );               String passWord = rs getString( );               boolean enabled = rs getBoolean( );               Integer userId = rs getInt( );               String email = rs getString( );           MyUserDetails user = new MyUser(userName  passWord  enabled  true                                                   true true  new GrantedAuthority[]new                                                  GrantedAuthorityImpl( HOLDER ));               user setEmail(email);               user setUserId(userId);               return user;               

  如果你已经看过源代码 你会发现这里只是其中的一部分代码 具体的实现请看项目的MySecurityJdbcDaoImpl类实现 以及MyUserDetails和MyUser类 这里步在一一列出

  如果使用Hibernate来操作数据库 你也可以从你的DAO中获取用户信息 最后你只要将存放了用户身份信息和权限信息的列表(List)返回给系统就可以

  每当用户请求一个受保护的资源时 就会调用认证管理器以获取用户认证信息 但是如果我们的用户信息保存在数据库中 那么每次请求都从数据库中获取信息将会影响系统性能 那么将用户信息进行缓存就有必要了 下面就介绍如何在Spring Security中使用缓存

  二缓存用户信息

  查看AuthenticationProvider接口的实现类AbstractUserDetailsAuthenticationProvider抽象类(我们配置文件中配置的DaoAuthenticationProvider类继承了该类)的源代码 会有一行代码

 UserDetails user = this userCache getUserFromCache(username);

  DaoAuthenticationProvider认证提供者使用UserCache接口的实现来实现对用户信息的缓存 修改DaoAuthenticationProvider的配置如下

 <bean id= daoAuthenticationProvider  class= springframework security providers dao DaoAuthenticationProvider      p:userCache ref= userCache      p:passwordEncoder ref= passwordEncoder      p:userDetailsService  ref= userDetailsService />

  这里我们加入了对userCache Bean的引用 userCache使用Ehcache来实现对用户信息的缓存 userCache配置如下

   <bean id= userCache    class= springframework security providers dao cache EhCacheBasedUserCache        p:cache ref= cache />   <bean id= cache    class= springframework cache ehcache EhCacheFactoryBean        p:cacheManager ref= cacheManager        p:cacheName= userCache />   <bean id= cacheManager    class= springframework cache ehcache EhCacheManagerFactoryBean      p:configLocation= classpath:ehcache xml >  </bean>

  我们这里使用的是EhCacheBasedUserCache 也就是用EhCache实现缓存的 另外系统还提供了一个默认的实现类NullUserCache类 我们可以通过源代码了解到 无论上面使用这个类都返回一个null值 也就是不使用缓存

  三保护业务方法

  从第一篇文章中我们已经了解到 Spring Security使用Servlet过滤器来拦截用户的请求来保护WEB资源 而这里却是使用Spring 框架的AOP来提供对方法的声明式保护 它通过一个拦截器来拦截方法调用 并调用方法安全拦截器来保护方法

  在介绍之前 我们先回忆一下过滤器安全拦截器是如何工作的 过滤器安全拦截器首先调用AuthenticationManager认证管理器认证用户信息 如果用过认证则调用AccessDecisionManager访问决策管理器来验证用户是否有权限访问objectDefinitionSource中配置的受保护资源

  首先看看如何配置方法安全拦截器 它和过滤器安全拦截器一方继承自AbstractSecurityInterceptor抽象类(请看源代码) 如下

 <bean id= methodSecurityInterceptor  class= springframework sethod aopalliance MethodSecurityInterceptor      p:authenticationManager ref= authenticationManager      p:accessDecisionManager ref= accessDecisionManager >      <property name= objectDefinitionSource >         <value>                  test service UserService get*=ROLE_SUPERVISOR         </value>      </property>  </bean>

  这段代码是不是很眼熟啊 哈哈~ 这和我们配置的过滤器安全拦截器几乎完全一样 方法安全拦截器的处理过程实际和过滤器安全拦截器的实现机制是相同的 这里就在累述 详细介绍请参考< Spring Security 学习总结一>中相关部分 但是也有不同的地方 那就是这里的objectDefinitionSource的配置 在等号前面的不在是URL资源 而是需要保护的业务方法 等号后面还是访问该方法需要的用户权限 我们这里配置的 test service UserService get*表示对 test service包下UserService类的所有以get开头的方法都需要ROLE_SUPERVISOR权限才能调用 这里使用了提供的实现方法MethodSecurityInterceptor 系统还给我们提供了aspectj的实现方式 这里不在介绍(我也正在学…) 读者可以参考其它相关资料

  之前已经提到过了 Spring Security使用Spring 框架的AOP来提供对方法的声明式保护 即拦截方法调用 那么接下来就是创建一个拦截器 配置如下

   <bean id= autoProxyCreator    class= springframework aop framework autoproxy BeanNameAutoProxyCreator >       <property name= interceptorNames >          <list>          <value>methodSecurityInterceptor</value>          </list>       </property>       <property name= beanNames >          <list>             <value>userService</value>         </list>      </property>  </bean>

  userService是我们在applicationContext xml中配置的一个Bean AOP的知识不是本文介绍的内容 到这里保护业务方法的配置就介绍完了

  四将资源放在数据库中

  现在 你的用户提出了新的需求 它们需要自己可以给系统用户分配或者取消权限 其实这个并不是什么新鲜事 作为开发者 你也应该为用户提供这样的功能 那么我们就需要这些受保护的资源和用户权限等信息都是动态的 你可以选择把它们存放在数据库中或者LDAP服务器上 本文以数据库为例 介绍如何实现用户权限的动态控制

  通过前面的介绍 你可能也注意到了 不管是MethodSecurityInterceptor还是FilterSecurityInterceptor都使用authenticationManager和accessDecisionManager属性用于验证用户 并且都是通过使用objectDefinitionSource属性来定义受保护的资源 不同的是过滤器安全拦截器将URL资源与权限关联 而方法安全拦截器将业务方法与权限关联

  你猜对了 我们要做的就是自定义这个objectDefinitionSource的实现 首先让我们来认识一下系统为我们提供的ObjectDefinitionSource接口 objectDefinitionSource属性正是指向此接口的实现类 该接口中定义了 个方法 ConfigAttributeDefinition getAttributes(Object object)方法用户获取保护资源对应的权限信息 该方法返回一个ConfigAttributeDefinition对象(位于 springframework security包下) 通过源代码我们可以知道 该对象中实际就只有一个List列表 我们可以通过使用ConfigAttributeDefinition类的构造函数来创建这个List列表 这样 安全拦截器就通过调用getAttributes(Object object)方法来获取ConfigAttributeDefinition对象 并将该对象和当前用户拥有的Authentication对象传递给accessDecisionManager(访问决策管理器 请查看 springframework security vote包下的AffirmativeBased类 该类是访问决策管理器的一个实现类 它通过一组投票者来决定用户是否有访问当前请求资源的权限) 访问决策管理器在将其传递给AffirmativeBased类维护的投票者 这些投票者从ConfigAttributeDefinition对象中获取这个存放了访问保护资源需要的权限信息的列表 然后遍历这个列表并与Authentication对象中GrantedAuthority[]数据中的用户权限信息进行匹配 如果匹配成功 投票者就会投赞成票 否则就投反对票 最后访问决策管理器来统计这些投票决定用户是否能访问该资源 是不是又觉得乱了 还是那句话 如果你结合源代码你现在一定更明白了

  说了这么些 那我们到底应该如何来实现这个ObjectDefinitionSource接口呢?

  首先还是说说Acegi Seucrity x版本 acegisecurity intercept web和 acegisethod包下AbstractFilterInvocationDefinitionSource和AbstractMethodDefinitionSource两个抽象类 这两个类分别实现了FilterInvocationDefinitionSource和MethodDefinitionSource接口 而这两个接口都继承自ObjectDefinitionSource接口并实现了其中的方法 两个抽象类都使用方法模板模式来实现 将具体的实现方法交给了子类

  提示 两个抽象类实现了各自接口的 getAttributes(Object object)方法并在此方法中调用lookupAttributes(Method method)方法 而实际该方法在抽象类中并没有具体的实现 而是留给了子类去实现

  在Acegi Seucrity x版本中 系统为我们提供了默认的实现 MethodDefinitionMap类用于返回方法的权限信息 而PathBasedFilterInvocationDefinitionMap类和RegExpBasedFilterInvocationDefinitionMap类用于返回URL资源对应的权限信息 也就是ConfigAttributeDefinition对象 现在也许明白一点儿了吧 我们只要按照这三个类的实现方式(也就是 模仿 从后面的代码中你可以看到)从数据库中获取用户信息和权限信息然后封装成一个ConfigAttributeDefinition对象返回即可(其实就是一个List列表 前面已经介绍过了) 相信通过Hibernate从数据库中获取一个列表应该是再容易不过的了

  回到Spring Security 系统为我们提供的默认实现有些变化 DefaultFilterInvocationDefinitionSource和DelegatingMethodDefinitionSource两个类 从名字也可以看出来它们分别是干什么的了 这两个类分别实现了FilterInvocationDefinitionSource和MethodDefinitionSource接口 而这两个接口都继承自ObjectDefinitionSource接口并实现了其中的方法 这和 x版本中一样 它们都是从配置文件中得到资源和相应权限的信息

  通过上面的介绍 你或许更名白了一些 那我们下面要做的就是实现系统的FilterInvocationDefinitionSource和MethodDefinitionSource接口 只是数据源不是从配置文件中读取配置信息是数据库而已

  我们这里对比著Acegi Seucrity x版本中的实现 我个人认为它更好理解 还是请你好好看看源代码

   自定义FilterInvocationDefinitionSource

  在 中 系统没有在系统抽象类 所以我们还是使用 x中的实现方式 首先通过一个抽象类来实现ObjectDefinitionSource接口 代码如下

   public ConfigAttributeDefinition getAttributes(Object object)                 throws IllegalArgumentException              if (object == null || !(this supports(object getClass())))                  thrownew IllegalArgumentException( Object must be a FilterInvocation );                        String url = ((FilterInvocation)object) getRequestUrl();           returnthis lookupAttributes(url);            publicabstract ConfigAttributeDefinition lookupAttributes(String url);    @SuppressWarnings( unchecked )    publicabstract Collection getConfigAttributeDefinitions();    @SuppressWarnings( unchecked )    publicboolean supports(Class clazz)         return FilterInvocation class isAssignableFrom(clazz);    

  这段代码你也可以在 中找到 getAttributes方法的入口参数是一个Object对象 这是由系统传给我们的 因为是URL资源的请求 所有可以将这个Object对象强制转换为FilterInvocation对象 并通过调用它的getRequestUrl()方法来获取用户当前请求的URL地址 然后调用子类需要实现的lookupAttributes方法并将该URL地址作为参数传给该方法 下面是具体的实现类DataBaseFilterInvocationDefinitionSource类的代码 也就是我们需要实现抽象父类的lookupAttributes方法

    @Override        public ConfigAttributeDefinition lookupAttributes(String url)                // TODO Auto generated method stub               //初始化数据 从数据库读取            cacheManager initResourceInCache();          if (isUseAntPath())              int firstQuestionMarkIndex = url lastIndexOf( ? );             if (firstQuestionMarkIndex !=  )                  url = url substring(  firstQuestionMarkIndex);                                 //将URL在比较前都转换为小写          if (isConvertUrlToLowercaseBeforeComprison())              url = url toLowerCase();                    //获取所有的URL          List<String> urls = cacheManager getUrlResources();          //倒叙排序 如果不进行排序 如果用户使用浏览器的导航工具访问页面可能出现问题          //例如 访问被拒绝后用户刷新页面          Collections sort(urls);          Collections reverse(urls);          GrantedAuthority[] authorities = new GrantedAuthority[ ];          //将请求的URL与配置的URL资源进行匹配 并将正确匹配的URL资源对应的权限          //取出          for (String resourceName_url : urls)              boolean matched = false;             //使用ant匹配URL             if (isUseAntPath())                  matched = pathMatcher match(resourceName_url  url);              else //perl 编译URL                 Pattern pliedPattern = null;                 Perl Compiler piler = new Perl Compiler();                 try                     pliedPattern = pile(resourceName_url  Perl Compiler READ_ONLY_MASK);                  catch (MalformedPatternException e)                     e printStackTrace();                                  matched = matcher matches(url  pliedPattern);                          //匹配正确 获取响应权限             if (matched)                  //获取正确匹配URL资源对应的权限                 ResourcDetail detail = cacheManager getResourcDetailFromCache(resourceName_url);                 authorities = detail getAuthorities();                 break;                                //将权限封装成ConfigAttributeDefinition对象返回(使用ConfigAttributeEditor)             if (authorities length >  )                  String authTemp =  ;               for (GrantedAuthority grantedAuthority : authorities)                   authTemp += grantedAuthority getAuthority() +  ;                              String authority = authTemp substring(  (authTemp length()    ));               System out println(authority);               ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor();               attributeEditor setAsText(authority trim());               return (ConfigAttributeDefinition)attributeEditor getValue();                      returnnull;     

  我们这里同样使用了缓存 它参考自系统的UseCache接口的实现 这里不在介绍 你可以查看本例的源代码和系统的实现和本例的配置文件 这里将用户请求的URL地址与从数据库中获取的受保护的URL资源使用ant和perl 匹配(这取决与你的配置) 如果匹配成功则从缓存中获取访问该资源需要的权限信息 并将其封装成ConfigAttributeDefinition对象返回 这里使用 springframework security ConfigAttributeEditor类 该类提供了一个setAsText(String s) 该方法收取一个字符串作为参数 在该方法中创建ConfigAttributeDefinition对象并将字符串参数传递给ConfigAttributeDefinition类的构造函数来初始化该对象 详细的实现还是请你看源代码 现在我们在配置文件添加自己的实现 如下

 <bean id= objectDefinitionSource       class= security intercept web DataBaseFilterInvocationDefinitionSource       p:convertUrlToLowercaseBeforeComprison= true       p:useAntPath= true       p:cacheManager ref= securityCacheManager />

  convertUrlToLowercaseBeforeComprison属性定义了在匹配之前将URL都转换为小写 useAntPath属性定义使用Ant方式匹配URL cacheManager属性定义了指向另一个Bean的引用 我们使用它从缓存中获取相应的信息

   自定义MethodDefinitionSource

  将方法资源存放在数据库中的实现与URL资源类似 这里不在累述 下面是DataBaseMethodInvocationDefinitionSource的源代码 读者可以参考注释进行阅读(该类也是继承自一个自定义的抽象类AbstractMethodDefinitionSource)

   public ConfigAttributeDefinition lookupAttributes(Method method  Class targetClass)              // TODO Auto generated method stub             //初始化资源并缓存             securityCacheManager initResourceInCache();             //获取所有方法资源           List<String> methods = securityCacheManager getMethodResources();           //权限集合           Set<GrantedAuthority> authSet = new HashSet<GrantedAuthority>();           //遍历方法资源 并获取匹配的资源名称 然后从缓存中获取匹配正确           //的资源对应的权限(ResourcDetail对象的GrantedAuthority[]对象数据)           for (String resourceName_method : methods)                if (isMatch(targetClass  method  resourceName_method))                   ResourcDetail detail = securityCacheManager getResourcDetailFromCache(resourceName_method);                  if (detail == null)                       break;                                    GrantedAuthority[] authorities = detail getAuthorities();                  if (authorities == null || authorities length ==  )                       break;                                    authSet addAll(Arrays asList(authorities));                                     if (authSet size() >  )                String authString =  ;               for (GrantedAuthority grantedAuthority : authSet)                   authString += grantedAuthority getAuthority() +  ;                              String authority = authString substring(  (authString length()    ));               System out println( >>>>>>>>>>>>>>>  + authority);               ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor();               attributeEditor setAsText(authority trim());               return (ConfigAttributeDefinition)attributeEditor getValue();                      returnnull;    

  isMatch方法用于对用户当前调用的方法与受保护的方法进行匹配 与URL资源类似 请参考代码 下面是applicationContext security xml文件中的配置 请查看该配置文件

 <bean id= methodDefinitionSource      class= sethod DataBaseMethodInvocationDefinitionSource      p:securityCacheManager ref= securityCacheManager />

  securityCacheManager属性定义了指向另一个Bean的引用 我们使用它从缓存中获取相应的信息 这个Bean和前一节中介绍的一样 只是这里我们获取的是方法保护定义资源

cha138/Article/program/Java/ky/201311/28719

相关参考

知识大全 spring security3.1 不再支持 filters=none 解决办法

  最近关于springsecurity不再支持filters=none我们有了新的解决办法方法如下  [java]  <security:autoconfig=true>  <!l

知识大全 Spring Security 2资源-角色查找源码研究及性能改进

SpringSecurity2资源-角色查找源码研究及性能改进  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来

知识大全 Spring柜架基础总结

Spring柜架基础总结  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  &n

知识大全 Spring总结实例之消息与事件

Spring总结实例之消息与事件  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  Spring的消

知识大全 Struts&Spring&Hibernate面试总结

Struts&Spring&Hibernate面试总结  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧

知识大全 Spring 依赖注入原理学习

Spring依赖注入原理学习  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  首先我们来看看Spr

知识大全 Spring自动装配的学习

Spring自动装配的学习  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  在xml配置文件中au

知识大全 Spring的IoC学习笔记之BeanFactoryPostProcessor

Spring的IoC学习笔记之BeanFactoryPostProcessor  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,

知识大全 对于Spring初学者的学习建议

对于Spring初学者的学习建议  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!一首先Java的基础

知识大全 Spring Integration学习笔记(一)

SpringIntegration学习笔记(一)  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!