当前位置: 首页 > 知识库问答 >
问题:

Spring Webflow Security-基于客户端证书的授权endpoint

岳炎彬
2023-03-14

关于WebFlow的Spring Security性问题。

我有一个带有Spring Security的SpringBoot Webflux web应用程序。同一应用程序还为双向SSL MTL启用了密钥库和信任库的SSL服务器。

现在,如果没有正确的客户端证书,尝试从我的应用程序请求endpoint的客户端已经失败了,这太棒了!在应用层上什么都没做,只是配置密钥库和信任库,太棒了。

问题:是否可以进一步授权谁可以基于客户端证书本身访问特定endpoint?

我的意思是,也许有了Spring Security,一个拥有有效客户端证书的客户端客户端1想要请求 /endpointA如果证书有正确的CN,它将能够访问它。但是如果客户端2有错误的CN,客户端2将被拒绝请求 /endpointA。

反之亦然,具有错误CN的客户端A将无法请求/endpointB,只有具有良好client2 CN的客户端2才可用。

当然,如果client3对 /endpointA和 /endpointB都有不正确的CN,client3将无法请求其中任何一个(但他有有效的客户端证书)。

有没有可能提供Spring Webflow的示例(不是MVC)?最后,如果可能的话?怎么做?(代码片段会很棒)。

非常感谢。

共有1个答案

云昊阳
2023-03-14

是的,这是可能的。您甚至可以通过验证证书的CN字段来进一步保护您的Web应用程序,如果它没有正确的名称,则阻止它。我不确定开箱即用的Spring Security是否可以做到这一点,但我知道使用AOP使用AeyJ是可以做到的。通过这种方式,您可以在成功的ssl握手之后并在请求进入您的控制器之前拦截请求。我绝对建议阅读这篇文章:AeyJ简介,因为它将帮助您理解库的基本概念。

您可以做的是创建一个注释,例如:Additional证书验证,它可以采用允许和不允许的通用名称列表。请参阅下面的实现。通过这种方式,您可以决定每个控制器允许和不允许哪个CN。

java prettyprint-override">@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdditionalCertificateValidations {

    String[] allowedCommonNames()       default {};
    String[] notAllowedCommonNames()    default {};

}

后记您可以使用上述注释注释您的控制器并指定常用名称:

@Controller
public class HelloWorldController {

    @AdditionalCertificateValidations(allowedCommonNames = {"my-common-name-a", "my-common-name-b"}, notAllowedCommonNames = {"my-common-name-c"})
    @GetMapping(value = "/api/hello", produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<String> hello() {
        return ResponseEntity.ok("Hello");
    }

}

现在您需要为注释提供一个实现。实际的类将拦截请求并验证证书内容。

@Aspect
@Configuration
@EnableAspectJAutoProxy
public class AdditionalCertificateValidationsAspect {

    private static final String KEY_CERTIFICATE_ATTRIBUTE = "javax.servlet.request.X509Certificate";
    private static final Pattern COMMON_NAME_PATTERN = Pattern.compile("(?<=CN=)(.*?)(?=,)");

    @Around("@annotation(certificateValidations)")
    public Object validate(ProceedingJoinPoint joinPoint,
                           AdditionalCertificateValidations certificateValidations) throws Throwable {

        List<String> allowedCommonNames = Arrays.asList(certificateValidations.allowedCommonNames());
        List<String> notAllowedCommonNames = Arrays.asList(certificateValidations.notAllowedCommonNames());

        Optional<String> allowedCommonName = getCommonNameFromCertificate()
                .filter(commonName -> allowedCommonNames.isEmpty() || allowedCommonNames.contains(commonName))
                .filter(commonName -> notAllowedCommonNames.isEmpty() || !notAllowedCommonNames.contains(commonName));

        if (allowedCommonName.isPresent()) {
            return joinPoint.proceed();
        } else {
            return ResponseEntity.badRequest().body("This certificate is not a valid one");
        }
    }

    private Optional<String> getCommonNameFromCertificate() {
        return getCertificatesFromRequest()
                .map(Arrays::stream)
                .flatMap(Stream::findFirst)
                .map(X509Certificate::getSubjectX500Principal)
                .map(X500Principal::getName)
                .flatMap(this::getCommonName);
    }

    private Optional<X509Certificate[]> getCertificatesFromRequest() {
        return Optional.ofNullable((X509Certificate[]) ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
                .getRequest()
                .getAttribute(KEY_CERTIFICATE_ATTRIBUTE));
    }

    private Optional<String> getCommonName(String subjectDistinguishedName) {
        Matcher matcher = COMMON_NAME_PATTERN.matcher(subjectDistinguishedName);

        if (matcher.find()) {
            return Optional.of(matcher.group());
        } else {
            return Optional.empty();
        }
    }

}

使用上述配置,具有允许的公用名的客户端将获得带有hello消息的200状态代码,其他客户端将获得带有消息的400状态代码:此证书无效。您可以将上述选项与以下附加库一起使用:

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
</dependency>

示例项目可以在这里找到:GitHub-Java教程

示例代码段可在此处找到:

  • 附加证书验证
  • 附加证书验证方面
  • HelloWorld控制器

==========更新1#

我发现,只有Spring Security性才能验证CN名称。请参阅此处示例的详细说明:https://www.baeldung.com/x-509-authentication-in-spring-security#2-Spring Security配置

首先,您需要告诉spring拦截每个请求,通过使用自己的逻辑重写configure方法来授权和验证,请参见下面的示例。它将提取公共名称字段并将其视为“用户名”,如果用户已知,它将与UserDetailsService进行检查。您的控制器还需要用预授权(“hasAuthority('ROLE\u USER')注释

@SpringBootApplication
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class X509AuthenticationServer extends WebSecurityConfigurerAdapter {
    ...
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated()
          .and()
          .x509()
            .subjectPrincipalRegex("CN=(.*?)(?:,|$)")
            .userDetailsService(userDetailsService());
    }
 
    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsService() {
            @Override
            public UserDetails loadUserByUsername(String username) {
                if (username.equals("Bob")) {
                    return new User(username, "", 
                      AuthorityUtils
                        .commaSeparatedStringToAuthorityList("ROLE_USER"));
                }
                throw new UsernameNotFoundException("User not found!");
            }
        };
    }
}

==========更新2#

我不知怎么错过了这一点,它应该是在一个非阻塞的方式。反应流有点类似于上面第一次更新中提供的示例。以下配置将为您提供帮助:

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
    return http
            .x509(Customizer.withDefaults())
            .authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
            .build();
}

@Bean
public MapReactiveUserDetailsService mapReactiveUserDetailsService() {
    UserDetails bob = User.withUsername("Bob")
            .authorities(new SimpleGrantedAuthority("ROLE_USER"))
            .password("")
            .build();

    return new MapReactiveUserDetailsService(bob);
}

我基于上述输入创建了一个工作示例实现,详细信息请参见此处:GitHub-Spring security with common name validation

 类似资料:
  • 本文向大家介绍oauth 客户证书授予,包括了oauth 客户证书授予的使用技巧和注意事项,需要的朋友参考一下 示例 资源

  • 我正在编写一个类,用于创建对BigQuery和Google云存储的授权。 在过去,我曾使用,但已被弃用。我试图使用,但我发现它只允许我使用,而我需要。 我知道可以从转换为,但我不知道如何将它们转换成相反的方向(转换为)。例如,我像这样创建连接: 有人能给我指明如何实现这一目标的方向吗? 谢谢!

  • Importing a delegation certificate As a repository owner: Add the delegation key to the repository using the targets/releases path, as this is what Docker searches for when signing an image (first tar

  • OAuth2 JWT 配置文件引入了将 JWT 用作授权授予和客户端身份验证的可能性。 JWT客户端身份验证功能独立于特定的授权类型,并且可以与任何授权类型一起使用,也可以与客户端凭据授权一起使用。 但是,使用 JWT 授权类型似乎与将客户端凭据授予与 JWT 客户端身份验证结合使用完全相同,只是语法略有不同。 在这两种情况下,客户端都会联系令牌终结点以获取访问令牌: vs

  • 我是oAuth2安全系统的新手。关于访问REST资源的基于用户角色的授权,我有一个问题。我的互联网冲浪提供了关于oauth2的身份验证部分的输入。 让我提供给你困扰我的情况。

  • 问题内容: 我还很陌生,对于使用证书进行身份验证时客户端应该显示的内容有些困惑。 我正在编写一个Java客户端,该客户端需要对POST特定对象进行简单的数据处理URL。那部分工作正常,唯一的问题是应该完成。该部分相当容易处理(无论是使用Java的内置HTTPS支持还是使用Java的内置支持),但是我一直坚持使用客户端证书进行身份验证。我注意到这里已经存在一个非常类似的问题,我还没有尝试使用我的代码