首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SpringSecurity5.3资源服务器多个密钥

SpringSecurity5.3资源服务器多个密钥
EN

Stack Overflow用户
提问于 2020-07-23 16:04:26
回答 2查看 4.1K关注 0票数 1

我们有以下情况:

  1. 多台“遗留”Oauth2 Auth服务器(2.3.4) --每个服务器配置了一个不同的RSA密钥,用于创建JWT令牌.
  2. Single更新(SS 5.3.3,SB 2.3.1)资源服务器,我们希望从( auth服务器)接受令牌。

问题是资源服务器只配置了一个键(当前),所以它只能接受来自一个服务器的令牌。

我们的资源服务器中是否有任何可以支持多个密钥的方法来解码来自不同auth服务器的JWT?

我们基本上想要这样做,但需要使用多个键:https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver-jwt-decoder-public-key

SpringSecurity5.3指出,这在“多租户”https://docs.spring.io/spring-security/site/docs/current/reference/html5/#webflux-oauth2resourceserver-multitenancy中是可能的。

这是一个基本的配置

代码语言:javascript
复制
    @Value("${security.oauth2.resourceserver.jwt.key-value}")
    RSAPublicKey key;


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http

                // using new Spring Security SpE"{{LOCATOR_BASE_URL}}"L
                //https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#webflux-oauth2resourceserver-jwt-authorization
                .authorizeRequests(authorizeRequests ->
                                authorizeRequests

                                        .antMatchers("/shipments/**").hasAuthority("SCOPE_DOMPick")
                                                                               .anyRequest().authenticated()
                )

                .csrf().disable()

                // ****** this is the new DSL way in Spring Security 5.2 instead of Spring Security Oauth @EnableResourceServer ******
                .oauth2ResourceServer(oauth2ResourceServer ->
                        oauth2ResourceServer
                                .jwt(jwt ->
                                        jwt.decoder(jwtDecoder())
                                )
                );

    }

    // static key
    @Bean
    JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withPublicKey(this.key).build();
EN

回答 2

Stack Overflow用户

发布于 2020-07-23 16:10:27

是的,SpringSecurity5.3允许您使用多个jwk键。请在这里阅读我的答案:

https://stackoverflow.com/a/61615389/12053054

如果您不能使用这个版本的SS,则可以手动配置spring安全性,以使用多个jwk键(请按照我提供的链接查看如何使用)。

Security的这一部分指定如何使用SpringSecurity5.3:https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver-multitenancy

代码语言:javascript
复制
JwtIssuerAuthenticationManagerResolver authenticationManagerResolver = new JwtIssuerAuthenticationManagerResolver
    ("https://idp.example.org/issuerOne", "https://idp.example.org/issuerTwo");

http
    .authorizeRequests(authorize -> authorize
        .anyRequest().authenticated()
    )
    .oauth2ResourceServer(oauth2 -> oauth2
        .authenticationManagerResolver(authenticationManagerResolver)
    );

请注意,发行人url是从传入令牌解析的(JWT oauth2令牌始终包含颁发者url,其中jwk的uri用于验证JWT令牌)。通过手动配置(我已经发布了答案),您可以添加自定义行为,例如:不需要从JWT直接查找应该使用哪个ulr来验证令牌,您可以检查报头的信息,以解析应该在此请求中使用哪个发出者URL (您在spring应用程序中指定了它们)来验证JWT令牌。

票数 2
EN

Stack Overflow用户

发布于 2022-11-17 23:09:53

我知道现在有点晚了,但这正是我们公司所需要的。没有发行者的网址是为服务器。

此外,也不需要auth服务器,因为客户端请求资源服务器上的受保护资源,只需生成带有私钥的签名JWT,并将其作为授权比勒令牌发送到http头中。在资源服务器上,只有在信任库中导入其公钥(证书)的客户端才允许访问资源。

因此,由于@Norbert提供的提示,我实现了一个自定义AuthenticationManagerResolver,它将在JWT头中查找存储在truststore.jks文件中的证书(公钥)的别名的JWT头(密钥id),检索这个公钥并创建一个JWTDecoder,该JWTDecoder将检查来自http头的传入JWT作为授权承载器是否与相应的私钥签名。

下面是使用Spring 2.7.1的整个代码:

代码语言:javascript
复制
import java.io.FileInputStream;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.servlet.http.HttpServletRequest;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.util.StringUtils;

import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jwt.JWTParser;

public class TenantAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {

  private static final Logger log = LoggerFactory.getLogger(TenantAuthenticationManagerResolver.class);

  private final Map<String, AuthenticationManager> authenticationManagers = new ConcurrentHashMap<>();

  private final BearerTokenResolver resolver = new DefaultBearerTokenResolver();

  private String trustetoreFile;
  private char[] storePasswd;

  public TenantAuthenticationManagerResolver(String truststoreFile, char[] storePasswd) {
    super();
    this.trustetoreFile = truststoreFile;
    this.storePasswd = storePasswd;
  }

  @Override
  public AuthenticationManager resolve(HttpServletRequest request) {
    try {
        return this.authenticationManagers.computeIfAbsent(toTenant(request), this::fromTenant);
    }
    catch (Exception e) {
        throw new InvalidBearerTokenException(e.getMessage());
    }
  }

  private String toTenant(HttpServletRequest request) throws ParseException {
    String jwt = this.resolver.resolve(request);
    String keyId = ((JWSHeader) JWTParser.parse(jwt).getHeader()).getKeyID();
    if (!StringUtils.hasText(keyId)) {
        throw new IllegalArgumentException("KeyID missing");
    }
    return keyId;
  }

  private AuthenticationManager fromTenant(String tenant) {
    return new JwtAuthenticationProvider(jwtDecoder(tenant))::authenticate;
  }

  private JwtDecoder jwtDecoder(String kid) {
    log.info("Building JwtDecoder for {}", kid);
    try {
        NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(getPublicKeyFromTruststore(kid)).signatureAlgorithm(SignatureAlgorithm.from("RS512")).build();
        OAuth2TokenValidator<Jwt> withDefault = JwtValidators.createDefault();
        OAuth2TokenValidator<Jwt> withDelegating = new DelegatingOAuth2TokenValidator<>(withDefault);
        jwtDecoder.setJwtValidator(withDelegating);
        return jwtDecoder;
    }
    catch (Exception e) {
        throw new IllegalStateException(e.getMessage());
    }
  }

  private RSAPublicKey getPublicKeyFromTruststore(String certificateAlias) throws IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException {
    try (FileInputStream myKeys = new FileInputStream(trustetoreFile)) {
        log.info("Opening truststore");

        KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        myTrustStore.load(myKeys, storePasswd);

        Certificate certificate = myTrustStore.getCertificate(certificateAlias);
        if (certificate == null) {
            throw new IllegalArgumentException("No entry found for alias " + certificateAlias);
        }

        return (RSAPublicKey) certificate.getPublicKey();
    }
  }

}

现在是安全配置:

代码语言:javascript
复制
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

import your_package.TenantAuthenticationManagerResolver;

@EnableWebSecurity
public class SecurityConfig {

  @Value("${jwt.keystore.location}")
  private String keyStore;

  @Value("${jwt.keystore.password}")
  private char[] storePasswd;

  @Value("${jwt.algorithm}")
  private String algorithm;

  @Bean
  public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationManagerResolver<HttpServletRequest> tenantAuthManagerResolver) throws Exception {

    //@formatter:off
        http
            .authorizeRequests()
            .mvcMatchers("/").permitAll()
            .mvcMatchers("/protectedservice/**").authenticated()
            .and().cors()
            .and().oauth2ResourceServer(oauth2 -> oauth2
                        .authenticationManagerResolver(tenantAuthManagerResolver)
                      );
    //@formatter:on

    return http.build();
  }


  @Bean
  public AuthenticationManagerResolver<HttpServletRequest> tenantAuthManagerResolver() {
    return new TenantAuthenticationManagerResolver(keyStore, storePasswd);
  }

}

依赖关系:

代码语言:javascript
复制
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    </dependency>

Application.properties中的属性:

代码语言:javascript
复制
jwt.keystore.location=/absolute_path_to/truststore.jks
jwt.keystore.password=your_trustsrore_passwd
jwt.algorithm=RS512

所需进一步经费:

使用

  • 工具在密钥库中创建密钥区(自签名证书)(停留在身份验证/授权服务器上):

密钥工具-genkey -keyalg RSA -alias my_alias -keystore my_keystore_file.jks -storepass my_keystore_pass -validity 360 -keysize 2048 -storetype JKS

  • 从密钥库提取公钥(证书):

关键工具-exportcert -alias my_alias -keystore my_keystore.jks -storepass my_keystore_pass -rfc -file -file

  • 将此证书导入到一个新的密钥存储库(信任存储库)中,其中包含公钥(停留在资源服务器上):

键盘工具my_store_pass -importcert -alias my_alias -file my_cert_file.pem -keystore my_truststore_file.jks -storepass

对于多租户,向密钥库中添加更多具有不同别名的密钥,然后提取证书(公钥)并将其添加到信任库中。my_truststore_file.jks将在资源服务器的配置属性jwt.keystore.location中使用。

使用存储在密钥存储库中的私钥生成签名JWT的代码(这应该在安全Oauth2 Auth服务器上实现)。我将这些代码放在一个JUnit测试类中:

代码语言:javascript
复制
import static org.junit.jupiter.api.Assertions.assertNotNull;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.time.Instant;
import java.time.temporal.ChronoField;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

import org.junit.jupiter.api.Test;

import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;

class TestJWTGeneration {

  @Test
  void testCreateJWTNimusKS() throws Exception {
    PrivateKey privateKey = getPrivateKeyFromKeystore("/absolute_path_to/keystore.jks", "my_alias");

    // Create RSA-signer with the private key
    JWSSigner signer = new RSASSASigner(privateKey);

    //@formatter:off
    // Prepare JWT with claims set

    // 1 day JWT validity
    Date expirationDate = Date.from(Instant.now().plus(1L, ChronoField.DAY_OF_MONTH.getBaseUnit()));
    JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
            .subject("my_subject")
            .issuer("https://my_oauth2_server.com/")
            .audience("my_audience")
            .issueTime(new Date())
            .claim("nonce", Base64.getEncoder().encodeToString(UUID.randomUUID().toString().getBytes()))
            .expirationTime(expirationDate)
            .build();
    //@formatter:on

    // put the certificate alias in the JWT header as "kid" field (Key ID)
    final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS512).type(JOSEObjectType.JWT).keyID("my_alias").build();

    final SignedJWT signedJWT = new SignedJWT(header, claimsSet);

    signedJWT.sign(signer);

    String jwtSigned = signedJWT.serialize();

    assertNotNull(jwtSigned);

    System.out.println("##Nimbus JWT=" + jwtSigned);
  }

  public static PrivateKey getPrivateKeyFromKeystore(String pubKeyFile, String keyAlias) throws FileNotFoundException, IOException, KeyStoreException, NoSuchAlgorithmException, CertificateException, UnrecoverableKeyException {
    try (FileInputStream myKeys = new FileInputStream(pubKeyFile)) {

        KeyStore myTrustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        myTrustStore.load(myKeys, "my_keystore_pass".toCharArray());

        Key key = myTrustStore.getKey(keyAlias, "my_keystore_pass".toCharArray());
        return (PrivateKey) key;
    }
  }

}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/63058504

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档