How to get the certificate into the X509 filter (Spring Security)? - spring

I need to extract more information than just the CN of the certificate. Currently, I only get the standard UserDetails loadUserByUsername(String arg) where arg is the CN of the certificate. I need to get the X509Certificate object. Is it possible?
on spring security xml file :
<x509 subject-principal-regex="CN=(.*?)," user-service-ref="myUserDetailsService" />

No you can't get it that way. You need to grab it from the HttpServletRequest:
X509Certificate[] certs = (X509Certificate[])HttpServletRequest.getAttribute("javax.servlet.request.X509Certificate");

It is also worth noting that once you are authorized by the in-built X509AuthenticationFilter of Spring Security as it has accepted your certificate, then you can access the X509Certificate as
Object object = SecurityContextHolder.getContext().getAuthentication().getCredentials();
if (object instanceof X509Certificate)
{
X509Certificate x509Certificate = (X509Certificate) object;
//convert to bouncycastle if you want
X509CertificateHolder x509CertificateHolder =
new X509CertificateHolder(x509Certificate.getEncoded());
...

Related

Changing a Spring oauth2ResourceServer JWT ex. to use a single secret instead of a Keypair throws an exception “Failed to select a JWK signing key”

I'm trying to implement a Spring Boot Rest backend with JWT-security, based on Springs new authorization server and this example: https://github.com/spring-projects/spring-security-samples/tree/main/servlet/spring-boot/java/jwt/login
It uses Asymmetric keys to sign and verify tokens which seems like an overkill, since both authentication (where the token is generated) and authorization (verified) happens on the same server. So, to simplify deployment (just pass in a single secret via an environment variable), I have been trying to rewrite it to use a single shared secret.
The example code implements two Bean-components, one to create the JwtEncoder (using a private RSA key) and one for the JWTDecoder (using the matching public key).
I have rewritten the Decoder as explained in chapter 15 in the book “Spring Security in Action” so I assume this should work, since the NimbusJwtDecoder offers a withSecretKey method.
//Will eventually come via an environment variable
static byte[] secret = "j8IoV1jF67".getBytes();
#Bean
JwtDecoder jwtDecoder() {
// return NimbusJwtDecoder.withPublicKey(this.key).build();
SecretKey theKey = new SecretKeySpec(secret, 0, secret.length, "AES");
return NimbusJwtDecoder.withSecretKey(theKey).build();
}
I have implemented the Encoder, which is coursing the problem, like so (code commented out, is the original code using the private RSA Key:
#Bean
JwtEncoder jwtEncoder() {
// JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
// JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
// return new NimbusJwtEncoder(jwks);
SecretKey originalKey = new SecretKeySpec(secret, 0, secret.length, "AES");
JWKSource<SecurityContext> immutableSecret = new ImmutableSecret<SecurityContext>(originalKey);
return new NimbusJwtEncoder(immutableSecret);
}
When I login (via the POST /token endpoint) the line that uses the encoder:
return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
Throws this exception
org.springframework.security.oauth2.jwt.JwtEncodingException: An error occurred while attempting to encode the Jwt: Failed to select a JWK signing key
at org.springframework.security.oauth2.jwt.NimbusJwtEncoder.selectJwk(NimbusJwtEncoder.java:134)
at org.springframework.security.oauth2.jwt.NimbusJwtEncoder.encode(NimbusJwtEncoder.java:108)
Any suggestions to how to implement this example with a simple shared secret, instead of asymmetric keys?

Retrieve private key from Azure Key Vault in Spring Boot app

I have a .pfx certificate in Azure Key Vault. I need to retrieve the private key from this to decrypt a string value in my Spring Boot application.
I have used the azure-spring-boot-starter-keyvault-certificates library to load the certificate to java key store, this seems to be working ok.
What I don't understand is how to retrieve the private key part. Any clues to what I am doing wrong?
KeyStore azureKeyVaultKeyStore = KeyStore.getInstance("AzureKeyVault");
KeyVaultLoadStoreParameter parameter = new KeyVaultLoadStoreParameter(
System.getProperty("azure.keyvault.uri"),
System.getProperty("azure.keyvault.tenant-id"),
System.getProperty("azure.keyvault.client-id"),
System.getProperty("azure.keyvault.client-secret"));
azureKeyVaultKeyStore.load(parameter);
// returns null!
PrivateKey privateKey = (PrivateKey) azureKeyVaultKeyStore.getKey(environment.getProperty("azure.keyvault.alias"), "".toCharArray());
// decrypt value
Cipher c = Cipher.getInstance(privateKey.getAlgorithm());
c.init(Cipher.DECRYPT_MODE, privateKey);
c.update(DatatypeConverter.parseBase64Binary(cryptedMsg));
String decryptedMessage = new String(c.doFinal());
Testing with the same certificate on my machine works doing like this:
KeyStore keyStore = KeyStore.getInstance("pkcs12");
keyStore.load(new FileInputStream(filename), password.toCharArray());
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password.toCharArray());
The private key can be retrieved using the GetSecret() method, otherwise you only get the public part.
See this article for details (Even tough its for .NET, I hope you can figure out how to do it in Java) or see the Java samples here

SSL context in mongo:client attribute using spring xml config

How to add SSL key store and trust store file path and password in mongo:client options using spring xml to connect mongo db on TLS . Also need to know how to add ssl invalid host name allowed in mongo:client attribute in xml. I am using spring data mongo db 2.2.3 .
This is not XML solution, but via Bean.
This is what I did for 2.2.5.RELEASE. Note that for 2.3.0, there is no MongoClientOptions
#Value("classpath:truststore/mongoserver-truststore.p12")
private Resource trustStore;
#Value("${ssl.truststore.mongodb.password}")
private String mongoTrustStorePassword;
#Bean
public MongoClientOptions mongoClientOptions() throws Exception {
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(trustStore.getInputStream(), mongoTrustStorePassword.toCharArray());
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
return MongoClientOptions.builder()
.sslEnabled(true)
.sslContext(sslContext)
.sslInvalidHostNameAllowed(true)
.build();
}

Invalid JWToken: kid is a required JOSE Header

I am trying to implement an Oauth2 Authorization Server with SpringBoot using this guide as a reference.
My keystore has a single key. I have successfully managed to create a JWToken (I can check it at jwt.io).
I have also a test Resource Server. When I try to access any endpoint I receive the following message:
{
"error": "invalid_token",
"error_description": "Invalid JWT/JWS: kid is a required JOSE Header"
}
The token really does not have a kid header but I can not figure out how to add it. I can only add data to its payload, using a TokenEnchancer. It also seems that I am not the first one with this issue.
Is there any way to add this header or, at least, ignore it at the resource server?
I've been working on an article that might help you out here:
https://www.baeldung.com/spring-security-oauth2-jws-jwk
So, to configure a Spring Security OAuth Authorization Server to add a JWT kid header, you can follow the steps of section 4.9:
create a new class extending the JwtAccessTokenConverter
In the constructor:
configure the parent class using the same approach you've been using
obtain a Signer object using the signing key you're using
override the encode method. The implementation will be the same as the parent one, with the only difference that you’ll also pass the custom headers when creating the String token
public class JwtCustomHeadersAccessTokenConverter extends JwtAccessTokenConverter {
private JsonParser objectMapper = JsonParserFactory.create();
final RsaSigner signer;
public JwtCustomHeadersAccessTokenConverter(KeyPair keyPair) {
super();
super.setKeyPair(keyPair);
this.signer = new RsaSigner((RSAPrivateKey) keyPair.getPrivate());
}
#Override
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String content;
try {
content = this.objectMapper.formatMap(getAccessTokenConverter().convertAccessToken(accessToken, authentication));
} catch (Exception ex) {
throw new IllegalStateException("Cannot convert access token to JSON", ex);
}
Map<String, String> customHeaders = Collections.singletonMap("kid", "my_kid");
String token = JwtHelper.encode(content, this.signer, this.customHeaders)
.getEncoded();
return token;
}
}
Then, of course, create a bean using this converter:
#Bean
public JwtAccessTokenConverter accessTokenConverter(KeyPair keyPair) {
return new JwtCustomHeadersAccessTokenConverter(keyPair);
}
Here I used a KeyPair instance to obtain the signing key and configure the converter (based on the example of the article), but you might adapt that to your configuration.
In the article I also explain the relevant endpoints provided by the Spring Security OAuth Authentication Server.
Also, regarding #Ortomala Lokni's comment, I wouldn't expect Spring Security OAuth to add any new features at this point. As an alternative, you probably can wait to have a look at Spring Security's Authorization Server features, planned to be released in 5.3.0
I managed to solve it by changing the parameter used to identify the URL where the clients will retrieve the pubkey.
On application.properties, instead of:
security.oauth2.resource.jwk.key-set-uri=http://{auth_server}/.well-known/jwks.json
I used:
security.oauth2.resource.jwt.key-uri=http://{auth_server}/oauth/token_key
If I understood correctly, the key-set-uri config points to an endpoint that presents a set of keys and there is the need for a kid. On the other side key-uri config points to an endpoint with a single key.

Spring Framework - Where to parse JWT for custom claim?

I have created a Spring JWT authorization application. JWT contains some custom claims. On a resource server side, I wonder, where should I parse the JWT token to collect and check these claims? Should I do this in a controller or in some filter? Whats the best practice? Maybe you have some example?
You can use a combination of a Jackson Object Mapper and Spring Security classes, namely Jwt, JwtHelper and Authentication. You can get the authentication by using Spring Security's static context object and then parse the token you receive using the JwtHelper.
ObjectMapper objectMapper = new ObjectMapper();
Authentication authentication =
SecurityContextHolder.getContext().getAuthentication();
Map<String, Object> map =
objectMapper.convertValue(authentication.getDetails(), Map.class);
// create a token object to represent the token that is in use.
Jwt jwt = JwtHelper.decode((String) map.get("tokenValue"));
// jwt.getClaims() will return a JSON object of all the claims in your token
// Convert claims JSON object into a Map so we can get the value of a field
Map<String, Object> claims = objectMapper.readValue(jwt.getClaims(), Map.class);
String customField = (String) claims.get("you_custom_field_name");
I would suggest debugging and putting a breakpoint on the third line in the code above. At that point, expose the authentication object. I might have some useful details you'll need later.
This can all be done on the controller. I'm not sure how to use the filter to do so.
you can also use springframework.boot.json.JsonParser:
JsonParser parser = JsonParserFactory.getJsonParser();
Map<String, ?> tokenData = parser.parseMap(JwtHelper.decode(token).getClaims());
> tokenData.get("VALID_KEY");
I'm using this:
private Claim getClaim(String claimKey) {
Authentication token = SecurityContextHolder.getContext().getAuthentication();
try {
DecodedJWT jwt = JWT.decode(token.getCredentials().toString());
return jwt.getClaim(claimKey);
} catch (JWTVerificationException ex) {
throw new RuntimeException(ex);
}
}

Resources