为了账号安全,请及时绑定邮箱和手机立即绑定

Spring Boot - 需要 api 密钥和 x509,但不适用于所有端点

Spring Boot - 需要 api 密钥和 x509,但不适用于所有端点

慕斯709654 2023-07-19 10:53:02
Java 11、Spring 启动 2.1.3、Spring 5.1.5我有一个 Spring Boot 项目,其中某些端点由 API 密钥保护。目前使用以下代码效果很好:@Component("securityConfig")@ConfigurationProperties("project.security")@EnableWebSecurity@Order(1)public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {    private static final Logger LOG = LoggerFactory.getLogger(SecurityJavaConfig.class);    private static final String API_KEY_HEADER = "x-api-key";    private String apiKey;    @Override    protected void configure(HttpSecurity httpSecurity) throws Exception {        APIKeyFilter filter = new APIKeyFilter(API_KEY_HEADER);        filter.setAuthenticationManager(authentication -> {            String apiKey = (String) authentication.getPrincipal();            if (this.apiKey != null && !this.apiKey.isEmpty() && this.apiKey.equals(apiKey)) {                authentication.setAuthenticated(true);                return authentication;            } else {                throw new BadCredentialsException("Access Denied.");            }        });        httpSecurity            .antMatcher("/v1/**")            .csrf()            .disable()            .sessionManagement()            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)            .and()            .addFilter(filter)            .authorizeRequests()            .anyRequest()            .authenticated();    }}这成功地需要一个包含 API 密钥的标头,但仅适用于以下端点:/v1/...我有一个新的要求,需要证书进行身份验证。我按照以下指南在我的项目中进行了 X.509 身份验证设置:秃顶迪区以代码为中心但是,我遇到了一些问题:证书始终是必需的,而不仅仅是/v1/*端点API 密钥过滤器不再起作用
查看完整描述

2 回答

?
一只名叫tom的猫

TA贡献1906条经验 获得超3个赞

在您的要求中,由于没有角色(不同的客户端具有不同的访问级别),因此不需要 UserDetailService
APIKeyFilter 足以与 X509 和 API 密钥配合使用。

考虑APIKeyFilter扩展X509AuthenticationFilter,如果存在没有有效证书的请求,则过滤器链将被破坏,并且将发送403/的错误响应。 如果证书有效,则过滤器链继续并进行身份验证。而验证我们所拥有的只有来自身份验证对象的两个方法 - - 。其中主题是 (EMAIL=, CN=, OU=, O=, L=, ST=, C=) (APIKeyFilter 应配置为返回主体和凭证对象) 您可以使用主体(您的 API 密钥)来验证 api 密钥由客户发送。您 可以使用凭据(证书主题)作为增强功能来单独识别每个客户端,如果需要,您可以为不同的客户端授予不同的权限。Forbidden

getPrincipal()header:"x-api-key"
getCredential()certificate subject


回顾您的要求
1. API V1 - 仅当证书和 API 密钥有效时才可访问。
2. 其他API - 无限制


查看完整回答
反对 回复 2023-07-19
?
繁星淼淼

TA贡献1775条经验 获得超11个赞

public class APIKeyFilter extends X509AuthenticationFilter

{

    private String principalRequestHeader;


    public APIKeyFilter(String principalRequestHeader) 

    {

        this.principalRequestHeader = principalRequestHeader;

    }


    @Override

    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request)

    {

        return request.getHeader(principalRequestHeader);

    }


    @Override

    protected Object getPreAuthenticatedCredentials(HttpServletRequest request)

    {

        X509Certificate[] certs = (X509Certificate[]) request

                .getAttribute("javax.servlet.request.X509Certificate");


        if(certs.length > 0)

        {

            return certs[0].getSubjectDN();

        }


        return super.getPreAuthenticatedCredentials(request);

    }

}

@Configuration

@EnableWebSecurity

public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {


    private static final String API_KEY_HEADER = "x-api-key";


    private String apiKey = "SomeKey1234567890";


    @Override

    protected void configure(HttpSecurity http) throws Exception 

    {

        APIKeyFilter filter = new APIKeyFilter(API_KEY_HEADER);

        filter.setAuthenticationManager(authentication -> {

            if(authentication.getPrincipal() == null) // required if you configure http

            {

                throw new BadCredentialsException("Access Denied.");

            }

            String apiKey = (String) authentication.getPrincipal();

            if (authentication.getPrincipal() != null && this.apiKey.equals(apiKey)) 

            {

                authentication.setAuthenticated(true);

                return authentication;

            }

            else

            {

                throw new BadCredentialsException("Access Denied.");

            }

        });


        http.antMatcher("/v1/**")

                .csrf().disable()

                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

            .and()

                .addFilter(filter)

                .authorizeRequests()

                .anyRequest()

                .authenticated();

    }


    @Bean

    public PasswordEncoder passwordEncoder() 

    {

        return new BCryptPasswordEncoder();

    }

}

验证 API 响应

https - 用于数据加密(服务器向客户端发送的 ssl 证书)

X509 - 用于客户端识别(使用服务器 ssl 证书生成的 ssl 证书,但不同客户端不同)

API key - 用于安全检查的共享密钥。


出于验证目的,假设您有 3 个版本,如下所示


@RestController

public class HelloController

{

    @RequestMapping(path = "/v1/hello")

    public String helloV1()

    {

        return "HELLO Version 1";

    }


    @RequestMapping(path = "/v0.9/hello")

    public String helloV0Dot9()

    {

        return "HELLO Version 0.9";

    }


    @RequestMapping(path = "/v0.8/hello")

    public String helloV0Dot8()

    {

        return "HELLO Version 0.8";

    }

}

下面给出不同情况下的响应。

CASE 1.a 版本 1,标头中包含有效的 X509 和 API 密钥


curl -ik --cert pavel.crt --key myPrivateKey.pem -H "x-api-key:SomeKey1234567890" "https://localhost:8443/v1/hello"

回复


HTTP/1.1 200

HELLO Version 1

CASE 1.b 版本 1 仅适用于 X509(无 API 密钥)

curl -ik --cert pavel.crt --key myPrivateKey.pem "https://localhost:8443/v1/hello"

回复


HTTP/1.1 403

{"timestamp":"2019-09-13T11:53:29.269+0000","status":403,"error":"Forbidden","message":"Access Denied","path":"/v1/hello"}

注意:

在您的情况下,有两种类型的证书

i。带有 X509 的客户端证书

ii:如果客户端不包含证书,则将使用服务器中使用的数据交换证书,即不带 X509 的证书

2. 版本 X 不带 X509,标头中不带 API 密钥。


curl "https://localhost:8443/v0.9/hello"

如果服务器证书是自签名证书(没有 CA 即证书颁发机构,证书无效)


curl performs SSL certificate verification by default, using a "bundle"

 of Certificate Authority (CA) public keys (CA certs). If the default

 bundle file isn't adequate, you can specify an alternate file

 using the --cacert option.

If this HTTPS server uses a certificate signed by a CA represented in

 the bundle, the certificate verification probably failed due to a

 problem with the certificate (it might be expired, or the name might

 not match the domain name in the URL).

If you'd like to turn off curl's verification of the certificate, use

 the -k (or --insecure) option.

如果服务器 SSL 证书有效(CA 认证)则

curl "https://localhost:8443/v0.9/hello"

你好版本0.9


curl "https://localhost:8443/v0.8/hello"

你好版本0.8


注意:如果您在开发环境中没有 CA 认证的 SSL 证书,请测试 Hack

使用服务器证书(.crt)和serverPrivateKey(.pem 文件)以及请求,如下所示


curl -ik --cert server.crt --key serverPrivateKey.pem "https://localhost:8443/v0.9/hello"

这也可以在 Mozilla 中进行验证(对于自签名证书),并且可以在 google chrome 中进行相同的验证(如果 CA 认证的 SSL)

给出的屏幕截图,在第一次访问期间

//img1.sycdn.imooc.com//64b7504b00014c1c06530428.jpg

添加服务器发送的证书后。

//img1.sycdn.imooc.com//64b7505600014dcc05190133.jpg


查看完整回答
反对 回复 2023-07-19
  • 2 回答
  • 0 关注
  • 146 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信