我们有以下情况:
问题是资源服务器只配置了一个键(当前),所以它只能接受来自一个服务器的令牌。
我们的资源服务器中是否有任何可以支持多个密钥的方法来解码来自不同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中是可能的。
这是一个基本的配置
@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();发布于 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
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令牌。
发布于 2022-11-17 23:09:53
我知道现在有点晚了,但这正是我们公司所需要的。没有发行者的网址是为服务器。
此外,也不需要auth服务器,因为客户端请求资源服务器上的受保护资源,只需生成带有私钥的签名JWT,并将其作为授权比勒令牌发送到http头中。在资源服务器上,只有在信任库中导入其公钥(证书)的客户端才允许访问资源。
因此,由于@Norbert提供的提示,我实现了一个自定义AuthenticationManagerResolver,它将在JWT头中查找存储在truststore.jks文件中的证书(公钥)的别名的JWT头(密钥id),检索这个公钥并创建一个JWTDecoder,该JWTDecoder将检查来自http头的传入JWT作为授权承载器是否与相应的私钥签名。
下面是使用Spring 2.7.1的整个代码:
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();
}
}
}现在是安全配置:
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);
}
}依赖关系:
<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中的属性:
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测试类中:
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;
}
}
}https://stackoverflow.com/questions/63058504
复制相似问题