由于谷歌关闭了 NPAPI 插件,我们无法在 Chrome 中加载小程序,我们基于客户端证书的 Web 应用程序身份验证方案不再有效。
在寻找替代方案时,我发现,因为我们在私有(private)内容上使用 SSL,所以有一种方法可以通过在 Web 服务器(在本例中为 Tomcat)上启用 SSL 客户端身份验证来自动请求 SSL 客户端证书.
我的问题是:一旦我启用了 Tomcat 的 SSL 客户端身份验证以从客户端浏览器的个人证书存储中请求此证书:
如何在我的 JSF 网络应用程序上获取证书信息,以便我可以在首次登录时使用该信息注册用户并将其与用户 ID 相关联?
(我假设我不需要担心伪造或过期的证书,因为正确配置的 Tomcat 会拒绝它们,所以我不需要费心进行身份验证/证书验证,而只需检索信息: Tomcat 通过重定向到带有错误代码的源页面来处理拒绝/身份验证错误)
我将描述一个针对西类牙语 DNIe 进行验证的过程,该过程使用使用 JSF 2.2 和 Spring Security 4.0 的客户端证书,尽管可以在不使用 Spring Security 的情况下进行身份验证。
你说你已经启用了Tomcat的SSL客户端认证,所以我想这意味着你已经配置了ROOT证书的keyStore。如果您不这样做,我可以为您提供 Tomcat 7 的有效说明。
因此,一旦 Tomcat 正确配置为需要客户端证书,并且握手完成后,这就是您在应用程序中读取客户端证书所必须执行的操作:
在pom.xml
中配置依赖
将以下依赖项添加到 pom.xml
:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.0.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.0.1.RELEASE</version>
</dependency>
值得一提的是Spring Security 4.0.1绑定(bind)了Spring 4.1.X
配置web.xml
在 Spring Security 中委托(delegate)
告诉servlet容器,安全委托(delegate)给Spring Security
<security-constraint>
<web-resource-collection>
<web-resource-name>Secured</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
它必须要求一个客户端证书:
<login-config>
<auth-method>CLIENT-CERT</auth-method>
<realm-name>certificate</realm-name>
</login-config>
配置Spring Security过滤器
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>*.jsf</url-pattern>
</filter-mapping>
不要忘记将 spring security 文件描述符添加到 contextConfigLocation
中上下文参数。
配置Spring Security
以下是配置 Spring Security 以验证客户端证书的大文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:sec="http://www.springframework.org/schema/security"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<bean id="userDetailsService" class="your.own.UserDetailService">
<property name="dao" ref="userDao" />
</bean>
<bean id="dniPrincipalExtractor" class="your.own.DniePrincipalExtractor">
</bean>
<bean id="x509Filter" class="org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter">
<property name="authenticationManager" ref="authManager" />
<property name="principalExtractor" ref="dniPrincipalExtractor" />
</bean>
<bean id="preauthAuthenticationProvider"
class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<property name="preAuthenticatedUserDetailsService" ref="authenticationUserDetailsService" />
</bean>
<bean id="authenticationUserDetailsService"
class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<property name="userDetailsService" ref="userDetailsService" />
</bean>
<sec:http pattern="/css/**" security="none" />
<sec:http pattern="/error/**" security="none" />
<sec:http pattern="/icons/**" security="none" />
<sec:http pattern="/imgs/**" security="none" />
<sec:http
auto-config="true"
use-expressions="true"
entry-point-ref="forbiddenAuthEP">
<sec:intercept-url pattern="/*" access="permitAll" />
<sec:intercept-url pattern="/xhtml/**" access="hasRole('ROLE_BASIC')" />
<sec:custom-filter ref="x509Filter" position="X509_FILTER"/>
</sec:http>
<sec:authentication-manager alias="authManager">
<sec:authentication-provider ref="preauthAuthenticationProvider" />
</sec:authentication-manager>
</beans>
此文件创建一个安全上下文,需要通过 x509Filter
登录.这个过滤器需要一个
-
dniPrincipalExtractor
您需要查找哪个类并从用户证书中提取 dni。
-
userDetailsService
它知道如何使用 userDao
将用户查找到数据库中.
使用此配置,一旦收到客户端证书,预身份验证服务就会提取 DNI 并从数据库(或其他)加载用户,构建
此代码意味着您必须构建自己的三个类:
-
your.own.UserDao
-
your.own.UserDetailService
它必须实现 org.springframework.security.core.userdetails.UserDetailsService
.在这里你必须检索分配给用户的角色或组来构建 List<GrantedAuthority>
为用户创建一个有效的 org.springframework.security.core.userdetails.User
.
-
your.own.DniePrincipalExtractor
它必须实现 org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor
示例 UserDetailService
package your.own.package;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import your.own.UserDAO;
public class UserDetailService implements UserDetailsService {
private UserDAO dao = null;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String error = null;
UserDetails result = null;
if(StringUtils.isNotEmpty(username)){
if(dao.findById(username) != null){
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList("ROLE_BASIC", "ROLE_ADMIN");
result = new org.springframework.security.core.userdetails.User(username, "", authorities);
}
}else{
error = "No se ha especificado login para el usuario.";
}
if(result == null){
if(StringUtils.isEmpty(error)){
error = String.format("No se encuentra ningún usuario con login %s", username);
}
throw new UsernameNotFoundException(error);
}
return result;
}
}
为简单起见,我手动设置角色,显然您必须更改它。
获取认证用户
现在您可以通过这种方式在 session bean 或其他任何地方获得经过身份验证的用户
SecurityContextHolder.getContext().getAuthentication().getName()
如果您需要,我可以为您提供更多信息,但我不想写一个非常大的答案。此答案中未涵盖的内容:
- DNI principal extractor:如果你搜索谷歌,你会发现几个实现,但我可以为你提供一个。
- OCSP 验证:您必须管理针对 Policia Nacional OCSP 服务器的证书验证。我可以告诉你怎么做。
- JSF 标签库请求用户授权。
- Tomcat 完整配置。
- 实现回退机制以在没有客户端证书的情况下启动表单验证页面。 Spring Security 是可能的
我的第一个实现没有使用 Spring Security,但就我所关心的需要提供回退机制而言,我继续使用 Spring Security,尽管为了简单起见我没有在这里向您展示如何做。
希望对您有所帮助!