如何在没有applet或外部应用程序的情况下执行基于浏览器内Web应用程序客户端证书的身份验证?

时间:2015-09-18 15:41:06

标签: java jsf tomcat authentication ssl

由于谷歌杀死了NPAPI插件,我们无法在Chrome中加载小程序,因此基于客户端证书的网络应用程序身份验证方案不再有效。

在寻找替代方案时我发现,因为我们在私人内容上使用SSL,因此可以通过在Web服务器上启用SSL客户端身份验证(在本例中为Tomcat)自动请求SSL客户端证书

我的问题是:一旦我启用了Tomcat的SSL客户端身份验证,就可以从客户端浏览器的个人证书存储区请求此证书:

如何在我的JSF Web应用程序上获取证书信息,以便我可以在首次登录时注册具有该信息的用户并将其与用户ID相关联?

(我假设我不需要担心伪造或过期的证书,因为正确配置的Tomcat会拒绝它们,所以我不需要打扰身份验证/证书验证,但只需检索信息: Tomcat通过使用错误代码重定向到源页面来处理拒绝/身份验证错误

1 个答案:

答案 0 :(得分:3)

我将描述一个过程来验证使用JSF 2.2和Spring Security 4.0使用客户端证书的西班牙语DNIe,尽管可以在不使用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绑定到Spring 4.1.X

在Spring Security

中配置web.xml以进行委派

告诉servlet容器,安全性被委托给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安全文件描述符添加到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;
    }
}

为简单起见,我手动放置角色,显然你必须改变它。

获取经过身份验证的用户

现在,您可以通过会话bean或其他任何方式获得经过身份验证的用户

SecurityContextHolder.getContext().getAuthentication().getName()

如果您需要,我可以为您提供更多信息,但我不想写一个非常大的答案。这个答案中没有涉及的事情:

  • DNI主要提取器:如果你搜索谷歌,你会发现几个实现,但我可以提供给你一个。
  • OCSP验证:您必须针对Policia Nacional OCSP服务器管理证书验证。我可以告诉你该怎么做。
  • JSF Tag Libs要求用户授予。
  • Tomcat完整配置。
  • 如果没有客户端证书,则实施回退机制以启动表单验证页面。可以使用Spring Security

我的第一个实现并没有使用Spring Security,但到目前为止我担心需要提供回退机制,我继续推进Spring Security,尽管我没有告诉你如何在这里做,为简单起见。

希望它有所帮助!