`
kong0itey
  • 浏览: 306049 次
社区版块
存档分类
最新评论

《Spring Security3》第六章第二部分翻译(自定义AuthenticationProvider)(转载)

阅读更多

实现自定义的 AuthenticationProvider

在很多场景下,你的应用需要跳出 Spring Security 功能的边界,可能会需要实现自己的 AuthenticationProvider 。回忆在第二章中 AuthenticationProvider 的角色,在整个认证过程中,它接受安全实体请求提供的凭证(即 Authentication 对象或 authentication token )并校验其正确性和合法性。

通过AuthenticationProvider 实现一个简单的单点登录

通常,应用允许以用户或客户端方式登录。但是,有一种场景也很常见,尤其是在广泛使用的应用中,即允许系统中的不同用户以多种方式登录。

假设我们的系统与一个简单的“单点登录”提供者进行集成,用户名和密码分别在 HTTP 头中的 j_username j_password 发送。除此以外, j_signature 头信息包含了用户名和密码的随意编码算法形成的字符串,以辅助安全请求。

【不要使用这个例子作为真实单点登录的解决方案。这个例子很牵强,只是为了说明实现一个完全自定义 AuthenticationProvider 的步骤。真正的 SSO 解决方案显然会更安全并涉及到几次的握手以建立可信任的凭证。 Spring Security 支持几种 SSO 解决方案,包括中心认证服务( CAS )和 SiteMinder ,我们将会在第十章:使用中心认证服务实现单点登录中介绍。实际上, Spring Security 提供了一个类似的过滤器用来进行 SiteMinder 请求头的认证,即 o.s.s.web.authentication.preauth.RequestHeaderAuthenticationFilter ,也是这种类型功能的一个好例子。】

对于 admin 用户的登录,我们的算法期望在请求头中包含如下的数据:

请求头

j_username

admin

j_password

admin

j_signature

admin|+|admin

一般来讲, AuthenticationProvider 将会寻找特定的 AuthenticationToken ,而后者会在过滤器链中位置比较靠前的 servlet filter 里面进行填充赋值(明确会在 AuthenticationManager 访问检查执行之前),如下图描述:


 

在这种方式中, AuthenticationToken 的提供者与其消费者 AuthenticationProvider 有一点脱离关系了。所以,在实现自定义 AuthenticationProvider 时,通常还需要实现一个自定义的 servlet 过滤器,其作用是提供特定的 AuthenticationToken

自定义认证 token

我们实现自定义的方法会尽可能的使用 Spring Security 的基本功能。基于此,我们会扩展并增强基本类如 UsernamePasswordAuthenticationToken ,并添加一个新的域来存储我们已编码的签名字符串。最终的类 com.packtpub.springsecurity.security.SignedUsernamePasswordAuthenticationToken ,如下:

 

Java代码  收藏代码
  1. package  com.packtpub.springsecurity.security;  
  2. // imports omitted   
  3.   public   class  SignedUsernamePasswordAuthenticationToken   
  4. extends  UsernamePasswordAuthenticationToken {  
  5.     private  String requestSignature;  
  6.     private   static   final   long  serialVersionUID =   
  7.     3145548673810647886L;  
  8.   /**  
  9.    * Construct a new token instance with the given principal,   
  10. credentials, and signature.  
  11.   *   
  12.   * @param principal the principal to use  
  13.   * @param credentials the credentials to use  
  14.   * @param signature the signature to use  
  15.   */   
  16.   public  SignedUsernamePasswordAuthenticationToken(String principal,  
  17.     String credentials, String signature) {  
  18.     super (principal, credentials);  
  19.     this .requestSignature = signature;  
  20.     }  
  21.   public   void  setRequestSignature(String requestSignature) {  
  22.     this .requestSignature = requestSignature;  
  23.     }  
  24.   public  String getRequestSignature() {  
  25.     return  requestSignature;  
  26.     }  
  27. }   

          我们可以看到 SignedUsernamePasswordAuthenticationToken 是一个简单的 POJO 类,扩展自 UsernamePasswordAuthenticationToken Tokens 并不需要太复杂——它们的主要目的就是为后面的校验封装凭证信息。

实现对请求头处理的 servlet 过滤器

         现在,我们要写 servlet 过滤器的代码,它负责将请求头转换成我们新定义的 token 。同样的,我们扩展对应的 Spring Security 基本类。在本例中, o.s.s.web.authentication.

AbstractAuthenticationProcessingFilter 满足我们的要求。

【基本过滤器 AbstractAuthenticationProcessingFilter Spring Security 中是很多进行认证过滤器的父类(包括 OpenID 、中心认证服务以及基于 form 的用户名和密码登录)。这个类提供了标准的认证逻辑并适当织入了其它重要的资源如 RememberMeServices ApplicationEventPublisher (本章的后面将会讲解到)。】

          现在,让我们看一下代码:

 

Java代码  收藏代码
  1. // imports omitted   
  2.   public   class  RequestHeaderProcessingFilter  extends    
  3.     AbstractAuthenticationProcessingFilter {  
  4.   private  String usernameHeader =  "j_username" ;  
  5.   private  String passwordHeader =  "j_password" ;  
  6.   private  String signatureHeader =  "j_signature" ;  
  7.   protected  RequestHeaderProcessingFilter() {  
  8.     super ( "/j_spring_security_filter" );  
  9.     }  
  10.   @Override   
  11.   public  Authentication attemptAuthentication   
  12.     (HttpServletRequest request,HttpServletResponse response)   
  13.     throws  AuthenticationException,  
  14.       IOException, ServletException {  
  15.     String username = request.getHeader(usernameHeader);  
  16.     String password = request.getHeader(passwordHeader);  
  17.     String signature = request.getHeader(signatureHeader);  
  18.     SignedUsernamePasswordAuthenticationToken authRequest =   
  19.       new  SignedUsernamePasswordAuthenticationToken   
  20.       (username, password, signature);  
  21.     return   this .getAuthenticationManager().authenticate(authRequest);  
  22.     }  
  23.   // getters and setters omitted below   
  24. }   

           可以看到,这个比较简单的过滤器查找三个已命名的请求头,正如我们已经规划的那样(如果需要的话,可以通过 bean 属性进行配置),并监听默认的 URL   /j_spring_security_filter 。正如其它的 Spring Security 过滤器那样,这是一个虚拟的 URL 并被我们的过滤器的基类 AbstractAuthenticationProcessingFilter 所识别,基于此这个过滤器采取行动尝试创建 Authentication token 并认证用户的请求。

【区分参与认证 token 流程的各个组件。在这个功能中,很容易被这些术语、接口和类的名字搞晕。代表要认证的 token 接口是 o.s.s.core.Authentication ,这个接口的实现将以后缀 AuthenticationToken 结尾。这是一个很简单的方式来区分 Spring Security 提供的认证实现类。】

在本例中,我们尽可能将错误检查最小化(译者注:即参数的合法性与完整性的检查)。可能在实际的应用中,会校验是否所有头信息都提供了以及在发现用户提供信息不正确的时候要抛出异常或对用户进行重定向。

一个细小的配置变化是需要将我们的过滤器插入到过滤器链中:

 

Xml代码  收藏代码
  1. < http   auto-config = "true"  ... >   
  2.   < custom-filter   ref = "requestHeaderFilter"    
  3.   before = "FORM_LOGIN_FILTER" />   
  4. </ http >   

 你可以看到过滤器代码最后请求 AuthenticationManager 来进行认证。这将最终委托配置的 AuthenticationProvider ,它们中的一个要支持检查 SignedUsernamePasswordAuthenticationToken 。接下来,我们需要书写一个 AuthenticationProvider 来做这件事情。

实现基于请求头的 AuthenticationProvider

现在,我们写一个 AuthenticationProvider 的实现类,即 com.packtpub.springsecurity.security.SignedUsernamePasswordAuthenticationProvider ,负责校验我们自定义 Authentication token 的签名。

 

Java代码  收藏代码
  1. package  com.packtpub.springsecurity.security;  
  2. // imports omitted   
  3.   public   class  SignedUsernamePasswordAuthenticationProvider   
  4.   extends  DaoAuthenticationProvider {  
  5.     @Override   
  6.     public   boolean  supports(Class<?  extends  Object> authentication) {  
  7.       return  (SignedUsernamePasswordAuthenticationToken . class .isAssignableFrom(authentication));  
  8.     }  
  9.   @Override   
  10.   protected   void  additionalAuthenticationChecks   
  11.   (UserDetails userDetails,  
  12.     UsernamePasswordAuthenticationToken authentication)  
  13.     throws  AuthenticationException {  
  14.     super .additionalAuthenticationChecks   
  15.     (userDetails, authentication);  
  16.     SignedUsernamePasswordAuthenticationToken signedToken =   
  17.       (SignedUsernamePasswordAuthenticationToken) authentication;  
  18.     if (signedToken.getRequestSignature() ==  null ) {  
  19.       throw   new  BadCredentialsException(messages.getMessage(  
  20.       "SignedUsernamePasswordAuthenticationProvider  
  21.       .missingSignature", " Missing request signature"),  
  22.       isIncludeDetailsObject() ? userDetails : null );  
  23.     }  
  24. // calculate expected signature    
  25.   if (!signedToken.getRequestSignature()   
  26.   .equals(calculateExpectedSignature(signedToken))) {  
  27.   throw   new  BadCredentialsException(messages.getMessage   
  28.   ("SignedUsernamePasswordAuthenticationProvider   
  29.   .badSignature", " Invalid request signature"),  
  30.   isIncludeDetailsObject() ? userDetails : null );  
  31.   }  
  32. }  
  33. private  String calculateExpectedSignature   
  34.   (SignedUsernamePasswordAuthenticationToken signedToken) {  
  35.     return  signedToken.getPrincipal() +  "|+|"  +   
  36.     signedToken.getCredentials();  
  37.   }  
  38. }  

 你可以看到我们再次扩展了框架中的类 DaoAuthenticationProvider ,因为有用的数据访问代码仍然需要进行实际的用户密码校验以及通过 UserDetailsService 加载 UserDetails

         这个类有些复杂,所以我们将分别介绍其中的每个方法。

         Supports 方法,是重写父类的方法,向 AuthenticationManager 指明当前 AuthenticationProvider 要进行校验的期望运行时 Authentication token

         接下来, additionalAuthenticationChecks 方法被父类调用,此方法允许子类对 token 进行特有的校验。这正适合我们的策略,所以添加上我们对 token 新的签名检查。基本上已经完成了我们自定义“简单 SSO ”的实现,仅剩一处配置了。

连接AuthenticationProvider

         一个常见的要求就是将一个或更多的 AuthenticationProvider 接口连接起来,因为用户可能会以几种校验方式中的某一种登录系统。

因为到目前为止,我们还没有了解其它的 AuthenticationProvider ,我们可以假设以下的需求,即使用标准的用户名和密码基于 form 的认证以及前面实现的自定义简单 SSO 认证。当配置了多个 AuthenticationProvider 时,每个 AuthenticationProvider 都会检查过滤器提供给它的 AuthenticationToken ,仅当这个 token 类型它支持时才会处理这个 token 。以这种方式,你的应用同时支持不同的认证方式并不会有什么坏处。

连接多个 AuthenticationProvider 实际上很简单。只需要在我们的 dogstore-security.xml 配置文件中声明另一个 authentication-provider 引用。

 

Xml代码  收藏代码
  1. < authentication-manager   alias = "authenticationManager" >   
  2.   < authentication-provider   ref "signedRequestAuthenticationProvider" />   
  3.   < authentication-provider   user-service-ref = "jdbcUserService" >   
  4.     < password-encoder   ref = "passwordEncoder"   >   
  5.       < salt-source   ref = "saltSource" />   
  6.     </ password-encoder >   
  7.   </ authentication-provider >   
  8. </ authentication-manager >   

          与我们安全配置文件中引用的其它 Spring bean 一样, signedRequestAuthenticationProvider 引用就是我们的 AuthenticationProvider ,它将在 dogstore-base.xml 中与其它的 Spring bean 一起进行配置。

 

Xml代码  收藏代码
  1. < bean   id = "signedRequestAuthenticationProvider"    class ="com.packtpub.springsecurity.security   
  2. .SignedUsernamePasswordAuthenticationProvider">   
  3.   < property   name = "passwordEncoder"   ref = "passwordEncoder" />   
  4.   < property   name = "saltSource"   ref = "saltSource" />   
  5.   < property   name = "userDetailsService"   ref = "jdbcUserService" />   
  6. </ bean >   

 我们自定义的 AuthenticationProvider bean 属性其实都是父类所需要的。这些也都指向了我们在 AuthenticationManager 的中第二个 authentication-provider 声明中的那些 bean

         最终已经完成了支持这个简单单点登录功能的编码和配置,至此可以给自己一个小小的喝彩。但是,还有一个小问题——我们应该怎样操作请求的 http 头以模拟我们的 SSO 认证提供者呢?

使用请求头模拟单点登录

         尽管我们的场景比较牵强,但是有一些商业和开源的单点登录解决方案,它们能够被配置以通过 HTTP 请求头发送凭证信息,最具有代表性的是 CA (以前的 Netegrity SiteMinder

【需要特别注意的是,与 SSO 方案集成的应用是不能通过用户的直接请求访问的。通常情况下, SSO provider 功能作为代理,通过它确定用户的请求流程(是安全的)或 provider 持有关于密码的信息并将这些信息与单个的安全应用隔离。在没有完全了解一个其使用的硬件、网络和安全设施之前,不要部署 SSO 应用。】

         Mozilla Firefox 的浏览器扩展,名为 Modify Headers (可以在以下地址获得: http://modifyheaders.mozdev.org ),是一个很简单的工具能够用来模拟伪造 HTTP 头的请求。以下的截图表明了如何使用这个工具添加我们的 SSO 方案所希望得到的请求头信息:



 将所有的头信息标示为 Enabled ,访问这个 URL http://localhost:8080/JBCPPets/j_spring_security_filter ,会发现我们能够自动登录系统。你可能也会发现我们给予 form 的登录还能继续可用,这是因为保留了这两个 AuthenticationProvider 实现以及过滤器链中对应的过滤器。

(译者注:说实话,作者这个实现自定义 AuthenticationProvider 的例子真的是比较牵强,但是还算完整描述出了其实现方式,想深入了解 AuthenticationProvider 自定义的朋友,可以参照 Spring Security 提供的 CasAuthenticationProvider 等实现。)

实现自定义AuthenticationProviders 时要考虑的事项

         尽管我们刚刚看到的例子并没有阐述你想构建的 AuthenticationProvider ,但是任何自定义 AuthenticationProvider 的步骤是类似的。这个练习的关键在于:

l  基于用户的请求完成一个 Authentication 实现的任务一般情况下会在过滤器链中的某一个中进行。取决于是否校验凭证的数据,这个校验组件可能要进行扩展;

l  基于一个合法的 Authentication 认证用户的任务需要 AuthenticationProvider 的实现来完成。请查看我们在第二章中讨论过的 AuthenticationProvider 所被期望拥有的功能;

l  在一些特殊的场景下,如果未认证的 session 被发现,可能会需要自定义的 AuthenticationEntryPoint 。我们将会在本章接下来的部分更多了解这个接口,也会在第十章介绍中心认证服务( CAS )时,介绍一些 AuthenticationEntryPoint 的实际例子。

如果你能时刻记住它们的角色,当你在开发应用特定的 AuthenticationProvider 时,会在实现和调试过程中少很多的迷惑。

分享到:
评论

相关推荐

    Spring Security3中文文档

    ### 第二章:深入理解Spring Security3 本章深入探讨了Spring Security3的内部机制,包括安全过滤器链、认证管理器(Authentication Manager)的工作原理以及如何定制这些组件以满足特定需求。 ### 第三章:高级安全...

    springsecurity学习笔记

    在"springsecurity学习笔记"中,你可能会涉及以下主题: - Spring Security的基本配置,包括web安全配置和全局安全配置。 - 如何自定义认证和授权流程,比如实现自定义的AuthenticationProvider和...

    spring security 官方文档

    9. **自定义**:Spring Security非常灵活,允许开发者根据需求自定义大部分组件,如访问决策管理器、权限评估器等,以满足特定业务场景。 10. **与其他Spring框架的集成**:Spring Security与Spring Boot、Spring ...

    SpringMVC集成SpringSecurity

    2. **集成过程**:在SpringMVC项目中集成SpringSecurity,首先需要在pom.xml文件中引入相应的依赖,接着配置SpringSecurity的XML配置文件,定义安全规则、用户认证方式等。 3. **默认账户和密码**:“admin,admin...

    spring security 3.x第五章例子

    Spring Security 是一个强大的和高度可定制的身份验证和...总的来说,Spring Security 3.x第五章的实例将帮助开发者深入理解这个框架的使用,通过具体的代码示例来实践安全性配置,从而更好地保护他们的Spring应用。

    SpringBoot + SpringSecurity + JPA 实现用户角色权限登录认证

    在SpringSecurity的配置中,会定义一个自定义的AuthenticationProvider来处理用户的登录认证,以及UserDetailsService来获取用户信息。此外,还需要配置HttpSecurity以拦截特定URL,根据用户的角色和权限决定是否...

    SpringSecurity素材.zip

    6. **CSRF防护(Cross-Site Request Forgery)**:SpringSecurity提供了内置的CSRF防护,通过生成和验证CSRF令牌,防止恶意第三方发起未经授权的操作。 7. **密码加密**:SpringSecurity支持多种密码加密策略,包括...

    spring-security 案例

    - **自定义AuthenticationProvider**:如果需要使用非默认的验证逻辑,可以创建自己的AuthenticationProvider。 - **自定义UserDetailsService**:处理用户详情,从数据库或其他来源加载用户信息。 6. **异常处理...

    springsecurity3离线手册

    本离线手册是SpringSecurity 3版本的详细指南,对于开发者来说,是一份非常宝贵的参考资料。 在SpringSecurity 3中,主要包含了以下几个核心概念: 1. **认证(Authentication)**:这是验证用户身份的过程。...

    spring-security-2.0.5

    用户可以通过自定义AuthenticationProvider实现自己的认证逻辑,确保用户身份的正确性。 - **授权**:Spring Security的访问决策管理器(Access Decision Manager)支持基于角色、基于权限和自定义策略的授权。Role-...

    spring_security.zip

    例如,可以使用OAuth2 实现第三方登录,Spring Security 处理内部认证和授权。 2. **授权服务器**:Spring Security 提供OAuth2 提供者支持,可以搭建自己的授权服务器,实现令牌的颁发和验证。 3. **保护API**:在...

    Spring Security 小例子(1)

    8. **OAuth2集成**:Spring Security还支持OAuth2协议,允许第三方应用获取有限的访问权限到受保护的资源,增强了现代Web应用的安全性和灵活性。 在“SpringSecurityDemo”这个示例项目中,你可能会看到如何配置...

    SpringSecurity3框架

    - Spring Security 3支持OAuth2协议,可以用于实现第三方登录功能,如Google、Facebook登录。 8. **AOP集成** - Spring Security利用Spring的AOP(面向切面编程)实现方法级别的安全控制,可以对方法调用进行访问...

    spring-security3 入门篇

    - **OAuth2集成**: 如何使用Spring Security与OAuth2结合,实现第三方登录功能。 - **CORS支持**: 设置跨域请求的安全策略,允许不同源的Web应用进行交互。 - **国际化的错误处理**: 如何定制Spring Security的...

    SpringSecurity实现的权限管理(含包)

    6. **SpringSecurity的包结构**:在提供的"ss3"压缩包中,可能包含了SpringSecurity的相关库文件,如spring-security-config、spring-security-core、spring-security-web等。这些库提供了实现上述功能所需的所有类...

    spring security 参考手册中文版

    第二部分 架构与实现 73 9.技术概述 73 9.1运行环境 73 9.2核心组件 74 9.2.1 SecurityContextHolder,SecurityContext和认证对象 74 获取有关当前用户的信息 75 9.2.2 UserDetailsService 75 9.2.3授予权力 77 ...

    springsecurity框架

    10. **OAuth2集成**:SpringSecurity可以与OAuth2框架集成,实现第三方登录、API保护等功能。 通过理解以上核心概念,开发者能够更好地利用SpringSecurity为应用程序构建稳固的安全防护。在实践中,根据项目需求...

    spring-security3.1源码

    6. **OAuth支持**:虽然3.1版本的OAuth支持可能相对有限,但Spring Security已经开始提供对OAuth协议的基础支持,允许应用与其他OAuth服务器进行交互,提供资源保护和第三方应用授权。 7. **XML和Java配置**:...

    SpringSecurity_3.0

    《教你使用_SpringSecurity_3.0_52页.pdf》这个文件可能是这本书的一部分,包含了52页的具体内容。虽然篇幅不长,但足以覆盖Spring Security的基本概念和用法。阅读这本书,结合实践操作,将有助于你快速掌握Spring ...

Global site tag (gtag.js) - Google Analytics