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

标签 java jsf tomcat authentication ssl

<分区>

由于谷歌关闭了 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,尽管为了简单起见我没有在这里向您展示如何做。

希望对您有所帮助!

关于java - 如何在没有小程序或外部应用程序的情况下执行基于浏览器内 Web 应用程序客户端证书的身份验证?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32656016/

相关文章:

java - Tomcat无法读取本地读取的文件

java - 无法从 Spring Boot 应用程序生成 WAR 文件

java.lang.IllegalArgumentException : Can not set java. lang.Long 字段 test.models.Aktion.id 到 test.models.Aktion

java - Spring boot 启动时的调试日志跟踪

jsf - 如何将 selectManyCheckbox 中选择的数据存储到不同的对象中?

java - 比较 JSF 实现

eclipse - 在 eclipse 中运行 tomcat 服务器时,ldap 身份验证失败。没有权限

Java FlyServer 飞对象空间

java - 从整数构建十六进制值

java - 动态网页内置 jsp 到任何高级 java 框架