《Spring Security3》第十一章(客户端证书认证)第二部分翻译
在Spring Security中配置客户端证书认证
不同于我们到目前为止所使用的认证机制,使用客户端证书认证会使得用户的请求已经被服务器预先认证(pre-authenticated)了。因为服务器(Tomcat)已经确定用户提供了合法且可信的证书,所以Spring Security只需信任这个assertion的合法性。
安全登录过程的另一个组件还缺失,也就是对认证过的用户进行授权。这就是我们要配置Spring Security的地方——我们必须添加一个组件到Spring Security中,它能够辨认出用户HTTP session(Tomcat填充进去的)中的证书认证信息,并将提供的凭证信息与Spring Security的UserDetailsService进行校验。与所有的UserDetailsService一样,这会确定对于证书中声明的用户,Spring Security是否了解(译者注:即Spring Security对应的存储中是否有该用户的信息),然后像其它登录的用户那样分配GrantedAuthority。
使用security命名空间配置客户端证书认证
由于LDAP和OpenID的配置的复杂性,配置客户端证书认证会稍好一些。如果你使用的是security命名空间风格的配置,添加客户端证书认证只需一行配置变化,添加到<http>声明中:
<http auto-config="true" ...> <!-- Other content omitted --> <x509 user-service-ref="jdbcUserServiceCustom"/> <!-- Other content omitted --> </http>
如果你重启应用,你会再次被提示要求客户端证书,但是这次你能够访问需要授权的内容了。你能够从日志中看到(如果你启用的话)你已经以admin用户登录了!干得漂亮!
Spring Security怎样使用证书信息
正如前面讨论的那样,Spring Security在证书交换中的相关内容是从提供的证书中提取信息并将用户的凭证与用户服务进行匹配。我们在使用<x509>声明中没有看到的是使得这一切发生的魔力所在。回忆我们建立客户端证书时,一个类似于LDAP DN的唯一标识名(distinguished name ,DN)会与这个证书关联:
Owner: CN=admin, OU=JBCP Pets, O=JBCP Pets, L=Anywhere, ST=NH, C=US |
Spring Security使用DN中的信息来确定安全实体的实际用户名并在UserDetailsService中进行查找。特别指出的是,它允许指明一个正则表达式,它会用来匹配证书建立时的DN并用DN的这一部分作为安全实体的用户名。<x509>默认配置的明确写法如下:
<x509 subject-principal-regex="CN=(.*?)," user-service-ref="jdbcUserService"/>
我们可以看到,这个正则表达式将会匹配admin作为用户名。这个正则表达式必须包含一个匹配值,但是它能够被配置成支持用户名和DN以满足应用的需要——如,如果你们组织的证书包含email或userid域,正则表达式可以修改成使用这些值作为已认证安全实体的名字。
Spring Securit证书认证怎样实现
让我们看一下检查和评估客户端证书以及将其转换成Spring Security认证session的参与者,如下图:
我们可以看到o.s.s.web.authentication.preauth.x509. X509AuthenticationFilter负责处理要求未认证用户提供客户端证书的请求。如果这个请求包含了合法的证书,它将会使用o.s.s.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor抽取出安全实体,如前文所描述,这里会使用匹配证书自己DN的正则表达式。
【注意,尽管在图中描述了对未认证用户的证书检查,但是还有一个检查会进行即使用证书认证的用户是否为已经认证过的用户。这会使用新提供的凭证进行一个新的认证。这个的目的很明确——任何时间一个用户提供了新的凭证,应用必须能够意识到,并作出适当的响应以确保用户能够正确访问。】
一旦证书被接受(或拒绝/忽略),就像其它的认证机制那样,会构建一个Authentication token并传递给AuthenticationManager进行认证。我们现在可以简单看一下o.s.s.web.authentication.preauth.PreAuthenticatedAuthenticationProvider对认证token的处理:
如果你读过我们在第十章:使用CAS进行单点登录讲到的CAS认证,你会意识到客户端证书认证请求和CAS请求的相似之处——这是有意的设计,事实上相似的设计影响到Spring Security支持的其它的预先认证机制(较少使用),包括Java EE角色匹配和Site Minder风格的认证。如果你理解了客户端证书认证的流程,理解其它的认证流程就容易多了。
其它未解决的问题
我们要解决的一个问题就是处理对认证的拒绝。在第六章:高级配置和扩展中,我们曾经学习过的功能AuthenticationEntryPoint(我们在CAS章节曾重温过这个话题)。在典型的form登录场景下,如果用户试图访问保护的资源却没有登录时,LoginUrlAuthenticationEntryPoint用来将用户重定向到登录页面。
与之相反,在典型的客户端证书认证环境下,并不支持替代的授权方法(要记住的是,Tomcat校验证书在Spring Security的form登录前面)。所以,并不会再存在默认重定向到form登录页的行为。相反,我们将会修改这个entry point,使用o.s.s.web.authentication.Http403ForbiddenEntryPoint简单返回一个HTTP 403禁止访问的信息。我们将会在dogstore-base.xml文件中配置这个bean,其它的Spring bean也位于此处,如下:
<bean id="forbiddenAuthEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
接下来,通过在<http>声明上设置一个简单的属性就会使得新entry point马上生效:
<http ... entry-point-ref="forbiddenAuthEntryPoint">
如果一个用户试图访问受保护的资源,而不能提供合法证书的话,他们将会看到如下的页面
不像我们在第六章看到的配置AccessDeniedHandler那样,Http403ForbiddenEntryPoint不能将用户重定向到一个友好页面或Spring管理的URL。作为替代,这样的配置应该在web应用部署描述符的<error-page>声明中配置,这是Java EE servlet规范声明的,或者实现Http403ForbiddenEntryPoint的子类以改写这个行为。
在常用的客户端证书认证中,其它的配置或应用流程要进行的调整如下:
l 同时移除基于form的登录页面;
l 移除所有的“Log out”链接(没有理由退出了,因为浏览器会始终提供用户的证书);
l 移除用户重命名和修改密码功能;
l 移除用户注册功能(除非你能将其与发放新证书关联)。
支持双模认证(Dual-Mode authentication)
在一些环境下,可能会同时支持基于证书和基于form的认证。如果你的环境就是如此,也可以(很简单的)通过Spring Security3来进行支持。我们需要使用默认的AuthenticationEntryPoint(重定向到form登录页)与用户交互并允许用户在没有提供客户端证书的情况下使用标准的form进行登录。
如果你选择这种方式配置你的应用,你需要调整Tomcat的SSL设置(对于你的应用服务器也要适当调整)。只需将clientAuth属性的值设为want而不是true:
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" sslProtocol="TLS" keystoreFile="conf/tomcat.keystore" keystorePass="password" truststoreFile="conf/tomcat.truststore" truststorePass="password" clientAuth="want" />
我们也需要移除前面练习中添加的entry-point-ref,这样如果浏览器在初始查询时不能提供一个合法的证书将会启用标准的基于form的认证流程。
尽管这很便利,但还是要记住几件关于双模认证(基于form和基于证书)的事情。
【一旦证书校验失败,大多数的浏览器不会提示用户再次要求证书,所以确保你的用户注意的是他们可能需要重新进入浏览器以再次提供证书。】
要注意的是使用证书认证用户的时候并不需要密码;但是,如果你还继续使用JDBC UserDetailsService来支持基于form的认证,可能会需要你使用UserDetailsService提供一些关于用户的信息给PreAuthenticatedAuthenticationProvider。这会有潜在的安全风险,因为你只想让其进行证书认证的用户可能会有使用form登录认证的潜在问题。有几个方法来解决这个问题,描述如下:
l 确保证书认证的用户有一个合适的强密码在数据库中;
l 考虑自定义你的JDBC用户存储以明确可以使用form登录的用户。这可以通过在保存用户信息的表上添加一个新的域,并细微调整JdbcDaoImpl使用的SQL查询;
l 为证书认证的用户配置一个单独的用户存储,与允许基于form登录的用户完全隔离。
双模认证可以是对你站点的一个很大增强功能,并能够有效部署和保证安全,这当然要以你记住用户能够允许访问的条件。
使用Spring bean配置客户端证书认证
在前面,我们曾经讲过客户端证书认证相关类的流程。所以,对于只使用基于bean的配置就会更容易了,它们都会在dogstore-explicit-base.xml文件中。我们将会添加如下的bean定义,这就对应我们迄今所讨论的所有bean:
<bean id="x509Filter" class="org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter> <property name="authenticationManager" ref="customAuthenticationManager"/> </bean> <bean id="preauthAuthenticationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> <property name="preAuthenticatedUserDetailsService" ref="authenticationUserDetailsService"/> </bean> <bean id="forbiddenAuthEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/> <bean id="authenticationUserDetailsService" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" ref="jdbcUserService"/> </bean>
我们还需要添加这个过滤器到我们的过滤器链中(比移除登录和退出相关的过滤器):
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy"> <security:filter-chain-map path-type="ant"> <security:filter-chain pattern="/**" filters=" securityContextPersistenceFilter, x509Filter, anonymousProcessingFilter, exceptionTranslationFilter, filterSecurityInterceptor" /> </security:filter-chain-map> </bean>
最后,我们需要添加AuthenticationProvider的实现到ProviderManager中,并移除原来存在的所有其它类:
<bean id="customAuthenticationManager" class="org.springframework.security.authentication.ProviderManager"> <property name="providers"> <list> <ref local="preauthAuthenticationProvider"/> </list> </property> </bean>
到此为止,我们基于bean的配置就准备好了。如果你想实验它,记住要切换web.xml中的(配置文件)引用来使用只基于bean的配置机制。
基于bean配置的其它功能
Spring基于bean的配置通过暴露bean属性为我们提供了一些其它的功能,而这些通过security命名空间风格的配置是没有暴露的。
X509AuthenticationFilter的其它属性如下:
属性 |
描述 |
默认值 |
continueFilterChainOnUnsuccessfulAuthentication |
如果为false,一个失败的认证将会抛出异常而不是允许请求继续。这通常会在需要合法证书才能访问的安全站点中设置。如果为true,即使认证失败,过滤器链将会继续。 |
true |
checkForPrincipalChanges |
如果为true,过滤器将会检查当前认证的用户名与客户端证书提供的用户名是否有所不同。如果这样的话,对于新证书的将会进行认证并且HTTP session将会失效(可选的,参照下一属性)。如果为false的话,一旦用户认证过,将会一直处于合法状态即便他修改了凭证。 |
false |
invalidateSessionOn PrincipalChange |
如果true并且请求中的安全实体发生变化,用户的HTTP session在重新认证前将会失效。如果为false,session将会保持——注意这可能会引入安全风险。 |
true |
PreAuthenticatedAuthenticationProvider为我们提供了两个有意思的属性,如下:
属性 |
描述 |
默认值 |
preAuthenticated UserDetailsService |
用从证书中获取的用户名构建完整的UserDetails对象 |
None |
throwExceptionWhenTokenRejected |
如果为true,当token不能正确构建时(证书中不包含用户名或没有证书)会抛出一个BadCredentialsException错误。在证书要明确要使用的环境中,一般设置为true。 |
false |
除了这些属性,还有很多的机会来实现接口或扩展证书认证相关的类来对你的应用进行更进一步的自定义。
实现客户端证书认证要考虑的事情
客户端证书认证,尽管非常安全,但是并不是适合所有人,也并不适合于所有的环境。
益处:
l 证书对双方(客户端和服务器)都建立起了相互信任并确保各自就是其所宣称的(参与者)(译者注:即能够确定提供证书的是谁);
l 基于证书的认证,如果能够很好地实现,相对于其它的form认证很难被模仿或窜改;
l 如果使用良好支持的浏览器并且正确设置,通过透明登录到所有证书安全的应用,客户端证书认证能够有效地作为单点登录的手段。
弊端:
这种方式(使用证书)一般会要求所有的用户都要拥有证书。这会导致用户培训负担以及管理负担。对于大量使用证书认证的组织必须要有足够的服务和支持,以进行证书管理、过期跟踪以及处理用户求助;
使用证书一般是一个全有或全无的事情,意味着一般不会对没有认证的用户提供混合模式的认证,这主要是因为web服务器的复杂配置以及应用的较差支持;
你的所有用户(包含移到设备)并不一定很好的支持证书;
正确配置证书认证的所有设施可能会需要高级的IT知识。
正如我们所看到的,客户端证书认证有利有弊。当它正确实现时,它对于所有的用户会是一种很便利的访问模式,并具有很吸引人的安全性和不可抵赖属性(non-repudiation)。你需要根据特定的情况来决定这种类型的认证是否适合。
小结
本章中,我们了解了基于客户端证书认证的架构、流程以及Spring Security提供的支持。我们学到了如下的内容:
l 了解客户端证书(相互的)认证的概念和整体流程;
l 学习配置Apache Tomcat支持自签名SSL和客户端证书的重要步骤;
l 配置Spring Security能够理解客户端提供的证书认证凭证;
l 理解Spring Security与证书认证相关的类;
l 学习怎样配置Spring bean风格的客户端证书环境;
l 权衡这种方式认证的利弊。
对于不熟悉客户端证书的开发者来说,可能会被这种环境的复杂性所困扰。我们希望本章的内容能够使得这个复杂的话题更容易理解和实现。