I have a springboot server running with https (for websocket connections), with following config:
application.properties
server.port: 9080
server.ssl.key-store: classpath:keystore.p12
server.ssl.key-store-password: XXXXXX
server.ssl.keyStoreType: PKCS12
server.ssl.keyAlias: XXXXXX
and web config
#Slf4j
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting").setHandshakeHandler(new CustomHandshakeHandler())
.setAllowedOrigins("https://192.168.0.101:9000", "chrome-extension://ggnhohnkfcpcanfekomdkjffnfcjnjam").withSockJS();
}
}
The UI is a vue dev server running on port 9000, with following config:
vue.config.js
var fs = require('fs');
module.exports = {
"devServer": {
"port": 9000,
"https": {
"key": fs.readFileSync('./certs/privkey.pem'),
"cert": fs.readFileSync('./certs/cert.pem')
},
"proxy": {
"/": {
"target": "https://192.168.0.101:9080",
"ws": true,
"secure": false,
"changeOrigin": true
}
},
"overlay": {
"warnings": true,
"errors": true
}
},
"transpileDependencies": [
"vuetify"
],
publicPath: "./"
}
certs folder is at same level as src folder, and the key and certificates were generated using following steps:
Generate keystore:
keytool -genkey -alias tomcat -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650
Generate certificate in pem format from keystore
openssl pkcs12 -in keystore.p12 -nokeys -out cert.pem
Generate private key in pem format from keystore:
openssl pkcs12 -in keystore.p12 -nodes -nocerts -out privkey.pem
and in my vue file I am creating new connection using:
import SockJS from "sockjs-client";
import Stomp from "webstomp-client";
...
var socket = new SockJS("/greeting");
this.ws = Stomp.over(socket);
...
this.ws.send("/app/message", JSON.stringify({"id":12, "name":"dev"}), {});
My problem was even after adding configuration for https in both frontend and backend, I was getting net::ERR_CONNECTION_REFUSED on the UI side
The mistake I did was, in my web-config in setAllowedOrigins() I was still using http instead of https for the url of the ui dev server.
I know this isn't a question per say, but hopefully all these steps helps somebody
Related
We have the following scenario:
Multiple 'legacy' Spring Security Oauth2 Auth Servers (2.3.4) - each with a different RSA key configured for creation of the JWT tokens.
Single newer (SS 5.3.3, SB 2.3.1) Resource Server which we want to accept tokens from either auth server.
Problem is the resource server is only configured with 1 key (currently)- so it can only accept tokens from 1 auth-server.
Is there any conceivable way to support multiple keys in our resource server to decode JWTs coming from different auth-servers?
We basically want to do this but with multiple keys:
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2resourceserver-jwt-decoder-public-key
Spring Security 5.3 indicates this may be possible with 'multi-tenancy' https://docs.spring.io/spring-security/site/docs/current/reference/html5/#webflux-oauth2resourceserver-multitenancy
It's a basic configuration
#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();
Yes Spring Security 5.3 allow's you to use multiple jwk-uri key's. Please read my answer here:
https://stackoverflow.com/a/61615389/12053054
If you cannot use this version of SS it is possible to manually configure spring security to use multiple jwk-uri key's. (Follow link i have provided to see how).
This part of Spring Security doc's specify how to do it with Spring Security 5.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)
);
Note that issuer url's are resolved from incoming token (JWT oauth2 token always contains issuer url where uri for jwk to verify JWT token can be found). By manual configuration (answer i have posted) you can add custom behavior for example: instead of finding which ulr should be used to verify token directly from JWT you can check header's for information that resolves which issuer URL (you have specified them in your spring app) should be used with this request to verify JWT token.
I know it's a bit late, but it was exactly what we needed in our company. No issuer url's for auth-servers.
Further more, no need for auth-servers, too, as clients requesting the protected resource on the resource server, just have to generate signed JWT with the private key and send it in the http header as Authorization Bearer token. On the resource server only clients having their public key (certificates) imported in the truststore will be allowed to access the resources.
So thanks to the tips given by #Norbert Dopjera, I implemented a custom AuthenticationManagerResolver that will look in the JWT header for kid (key id) transporting the alias for a certificate (public key) stored in a truststore.jks file, will retrieve this public key and create a JWTDecoder that will check if the incoming JWT as Authorization Bearer from the http header, was signed with the corresponding private key.
Here is the whole code using Spring Boot 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();
}
}
}
And now the security configuration:
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);
}
}
Dependencies:
<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>
Properties in the application.properties:
jwt.keystore.location=/absolute_path_to/truststore.jks
jwt.keystore.password=your_trustsrore_passwd
jwt.algorithm=RS512
Further requirements:
Creation of a keypair (a self signed certificate) in a keystore with Java keytool (stays on the authentication/authorization server):
keytool -genkey -keyalg RSA -alias my_alias -keystore my_keystore_file.jks -storepass my_keystore_pass -validity 360 -keysize 2048 -storetype JKS
Extract the public key (the certificate) from the keystore:
keytool -exportcert -alias my_alias -keystore my_keystore.jks -storepass my_keystore_pass -rfc -file my_cert_file.pem
Import this certificate in a new keystore (truststore) holding the public keys (stays on the resource server):
keytool -importcert -alias my_alias -file my_cert_file.pem -keystore my_truststore_file.jks -storepass my_store_pass
For multi-tenancy, add more keypairs with different aliases to the keystore then extract the certificate (public key) and add it to the truststore. The my_truststore_file.jks will be used in the configured property jwt.keystore.location of the resource server.
Code for generating signed JWT with the private key stored in the keystore (this should be implemented on the Security Oauth2 Auth Servers). I put this code in a JUnit test class:
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;
}
}
}
My Spring Boot App was working fine with KeyCloak using a public certificate. But since my Keycloak has changed to a private certificate I get the following error:
"An I/O error occurred while reading from the JWK Set source: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target"
I've already got the private certificate but haven't figure out a successful way to set this up; If I make a curl to the Keycloack endpoint passing the certificate as parameter works fine.
curl --cacert mycertificate.crt -X GET \ 130 ↵
https://keycloak.address.bla/auth/realms/my-app/protocol/openid-connect/certs
I've tried to tweak my ResourceServerConfiguration class to use the certificate by generating a keystore.jks using keytool from my private certificate but I had no success; When I try the code bellow I get following error: "Cannot load keys from store: class path resource [keystore.jks]"
#Bean
#Primary
public JwtAccessTokenConverter createJwtAccessTokenConverter() {
var jwtAccessTokenConverter = new JwtAccessTokenConverter();
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("/keystore.jks"), "changeit".toCharArray());
jwtAccessTokenConverter.setKeyPair(keyStoreKeyFactory.getKeyPair("myAlias"));
jwtAccessTokenConverter.setAccessTokenConverter(keycloakAccessTokenConverter);
return jwtAccessTokenConverter;
}
My application.properties
security.oauth2.resource.id=account
security.oauth2.resource.jwk.key-set-uri=${app.keycloak.api}/protocol/openid-connect/certs
The problem was solved by just importing the private certificate to my JVM. After importing the ".crt" file all my request were successful;
The command to import .crt file to JVM is as follow: keytool -importcert -file my_certificate.crt -noprompt -alias certificate_alias -storepass changeit -keystore $JAVA_HOME/lib/security/cacerts
Do you try setting the classloader?
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("keystore.jks",this.getClass().getClassLoader()), "changeit".toCharArray());
Or you can use #Resource
#Value("classpath:keystore.jks")
Resource keystore;
...
KeyStoreKeyFactory key = new KeyStoreKeyFactory(this.keystore, passphrase);
I have an Eureka Client implemented with Spring Boot with two active ports:
9005 for https and 9010 for http.
Https is implemented natively by Spring Boot. Http is just an additional simple TomcatServletWebServerFactory added in the code.
Now the problem is that this Eureka Client registers itself with the https port specified in the application properties as 9005:
server:
port: 9005
ssl:
enabled: true
...
http:
port: 9010
address: 127.0.0.1
but I would like that this client registers itself with the http port 9010.
Of course the Eureka Server itself is running on the localhost and all other registered services are on the localhost too, talking http only.
Https is for external clients that are not WEB browsers and custom certificates are in use there.
I've tried nonSecurePort on the Client side, but that seems be to a server-side only configuration parameter. My Eureka Client's configuration (in addition to the port's configuration defined above):
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
healthcheck:
enabled: true
instance:
preferIpAddress: true
leaseRenewalIntervalInSeconds: 1
leaseExpirationDurationInSeconds: 2
nonSecurePortEnabled: true
nonSecurePort: 9010
Additional HttpServer implementation:
public ServletWebServerFactory servletContainer(#Value("${server.http.port}") int httpPort, #Value("${server.http.address}") String httpHost) throws UnknownHostException {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setPort(httpPort);
connector.setAttribute("address", InetAddress.getByName(httpHost).getHostAddress());
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(connector);
return tomcat;
}
One way to get around this is to add the SSL connector as an additional connector and keep the non secure connector as the default one.
Adding the SSL connector as an additional connector:
#Configuration
public class HttpsConfiguration {
#Value("${sslConfig.key-alias}")
private String keyAlias;
#Value("${sslConfig.key-store}")
private String keyStoreFile;
#Value("${sslConfig.key-password}")
private String keyPassword;
#Value("${sslConfig.key-store-type}")
private String keyStoreType;
#Value("${sslConfig.port}")
private int sslPort;
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addAdditionalTomcatConnectors(createSslConnector());
return tomcat;
}
private Connector createSslConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
connector.setScheme("https");
connector.setSecure(true);
protocol.setSSLEnabled(true);
connector.setPort(sslPort);
protocol.setKeystoreFile(keyStoreFile);
protocol.setKeystorePass(keyPassword);
protocol.setKeyAlias(keyAlias);
protocol.setKeystoreType(keyStoreType);
return connector;
}
}
Corresponding configuration for the connector:
sslConfig:
key-alias: ode-https
key-store-type: JKS
key-password: password
key-store: ode-https.jks
port: 443
If you are not sure how to generate a keypair, you could use the below command as a starting point:
keytool -genkey -alias ode-https -storetype JKS -keyalg RSA -keys ize 2048 -validity 365 -keystore ode-https.jks
I pull the answer out of a comment above, to make it more visible.
I find no direct solution to the problem, so I've implemented my own https ServletContainer instead of http.
Meaning http became native, while https was implemented with the help of TomcatServletWebServerFactory and org.springframework.boot.web.server.Ssl.
For initializing TomcatServletWebServerFactory, see here:
https://www.baeldung.com/embeddedservletcontainercustomizer-configurableembeddedservletcontainer-spring-boot
#Component
public class CustomContainer implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.setContextPath("");
factory.setPort(443);
}
}
To enable SSL:
Ssl ssl = new Ssl();
ssl.setEnabled(true);
// set your keystore here if required
...
// assign ssl to the new Tomcat instance
factory.setSsl(ssl);
I hope this helps. I do not post an exact code snippet here, as unfortunately in some big companies (that are crazy about their IP rights and possible conflicts with OSS licenses) it creates more troubles than help. Please google for https and TomcatServletWebServerFactory examples.
I've got the same issue when deploying a Spring Boot app on Cloud Foundry after enabling Spring Security. The funny part being I haven't enabled HTTPS on the app (only the 8080 port) but the URL registered with Eureka Server points to the port 443.
I've managed to fix the problem adding the following lines on the app's application.properties (the 1st line is for using the container-to-container network and the last two did the trick that this question is about).
eureka.instance.preferIpAddress=true
eureka.instance.securePortEnabled=false
eureka.instance.nonSecurePort=8080
I'm using Spring Boot 2.2.8.RELEASE and Spring Cloud Hoxton.SR6
I'm using spring-boot version 1.5.6.RELEASE. I configured SSL on port 9443 declaratively in application.yml. This is working. I am also using Undertow for this Spring-boot app.
server:
session:
cookie:
http-only: true
contextPath: /webapp
port: 9443
ssl:
key-store: /etc/pki/mycert.jks
key-store-password: ${SSL_KEYSTORE_PWD}
keyStoreType: JKS
keyAlias: alias
I have configured an additional SSL port programmatically. Here is a snippet:
#Configuration
public class UndertowAdditionalSSLConfig
{
#Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory()
{
UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory();
factory.addBuilderCustomizers(new UndertowBuilderCustomizer()
{
#Override
public void customize(Undertow.Builder builder)
{
try
{
builder.addHttpsListener(9444, "0.0.0.0", getSSLContext());
}
catch (Exception e)
{
log.error(e,"Could not add additional listener for https");
}
}
});
return factory;
}
}
The secondary ssl port is used for x509 client authentication for REST calls between servers. I have been unable to figure out how to do the following programmatically for the secondary ssl port:
client-auth=need
The problem I'm having is the client cert does not seem to be sent or it is not being accepted by the server. My thinking is that I'm missing this piece.
Thanks for any help.
UPDATE
After some digging into Spring boot source. I found this:
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUIRED);
I applied the change to my code:
#Override
public void customize(Undertow.Builder builder)
{
try
{
builder.addHttpsListener(8444, "0.0.0.0", getSSLContext());
builder.setSocketOption(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUIRED);
}
catch (Exception e)
{
log.error(e,"Could not add additional listener for https");
}
}
I thought I had the solution I was looking for, however the change bled through to SSL on port 9443 as well and the app became non-responsive to browser access.
Really, a better question for me to ask is:
How can I setup SSL on 2 separate ports and have 1 accept a client cert so that client based authentication can occur.
thanks
Instead of setting the getSslContext in addHttpsListener method of the builder, which customizes the entire sslContext used by all your connectors, you need to set the ssl on particular connector
public Ssl ssl() {
Ssl ssl = new Ssl();
ssl.setProtocol("TLS");
ssl.setClientAuth(Ssl.ClientAuth.valueOf("need".toUpperCase()));
// Other SSL stuff
return ssl;
}
// Not sure where this function is for 1.5.6 spring boot, but for 1.5.2 it is a method of the container factory which you need to override
protected void customizeConnector(Connector aConnector) {
final Ssl theSsl = ssl();
// .. Other stuff to enable disable based on condition
// turn on SSL for our connector
theSsl.setEnabled(true);
this.setSsl(theSsl);
this.setPort(myConnector.getPort()); //otherwise customizeConnector will override port
}
You should set client-auth:want in application.properties file like below:
server:
session:
cookie:
http-only: true
contextPath: /webapp
port: 9443
ssl:
key-store: /etc/pki/mycert.jks
key-store-password: ${SSL_KEYSTORE_PWD}
keyStoreType: JKS
keyAlias: alias
client-auth: want
and then open another port programmatically like below:
#Configuration
public class UndertowAdditionalSSLConfig
{
#Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory()
{
UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory();
factory.addBuilderCustomizers(new UndertowBuilderCustomizer()
{
#Override
public void customize(Undertow.Builder builder)
{
try
{
builder.addListener(new Undertow.ListenerBuilder().setPort(8444)
.setType(Undertow.ListenerType.HTTPS)
.setSslContext(getSSLContext())
.setHost("0.0.0.0")
.setOverrideSocketOptions(OptionMap.create(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUIRED)));
}
catch (Exception e)
{
log.error(e,"Could not add additional listener for https");
}
}
});
return factory;
}
}
and if you want to use Java lambda expressions:
#Configuration
public class UndertowAdditionalSSLConfig {
#Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() {
UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory();
factory.addBuilderCustomizers((UndertowBuilderCustomizer) builder -> {
try {
builder.addListener(new Undertow.ListenerBuilder().setPort(8444)
.setType(Undertow.ListenerType.HTTPS)
.setSslContext(getSSLContext())
.setHost("0.0.0.0")
.setOverrideSocketOptions(OptionMap.create(Options.SSL_CLIENT_AUTH_MODE, SslClientAuthMode.REQUIRED)));
} catch (Exception e) {
log.error(e, "Could not add additional listener for https");
}
});
return factory;
}
}
I have created keys using below command
keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048
After the i have exposed HTTPS connetion using camel end points
public class HTTPSCamelEndPoint {
public Endpoint httpsConfig(CamelContext context) throws Exception
{
KeyStoreParameters ksp = new KeyStoreParameters();
ksp.setResource("C:\\Users\\sithamparamd\\keystore.jks");
ksp.setPassword("123456");
KeyManagersParameters kmp = new KeyManagersParameters();
kmp.setKeyStore(ksp);
kmp.setKeyPassword("password");
SSLContextParameters scp = new SSLContextParameters();
scp.setKeyManagers(kmp);
JettyHttpComponent jettyComponent =context.getComponent("jetty", JettyHttpComponent.class);
jettyComponent.setSslContextParameters(scp);
//jettyComponent.createEndpoint("jetty:https://192.168.16.98:4443/myservice");
return jettyComponent.createEndpoint("jetty:https://192.168.16.98:4443/myservice");
}
public static void main(String[] args) throws Exception {
HTTPSCamelEndPoint httpsCamelEndPoint= new HTTPSCamelEndPoint();
CamelContext camelContext=httpsCamelEndPoint.getContext();
final Endpoint endpoint=httpsCamelEndPoint.httpsConfig(camelContext);
System.out.println(endpoint);
camelContext.addRoutes(new RouteBuilder() {
#Override
public void configure() throws Exception {
// TODO Auto-generated method stub
from(endpoint).process(new Processor() {
public void process(Exchange arg0) throws Exception {
// TODO Auto-generated method stub
System.out.println("GOT THE MSG !!!!");
}
});
}
});
camelContext.start();
}
public CamelContext getContext()
{
CamelContext camelContext=new DefaultCamelContext();
JettyHttpComponent httpComponent=new JettyHttpComponent();
camelContext.addComponent("jetty", httpComponent);
return camelContext;
}
}
but when i access through the URL its showing as invalided certificate. Please tel me the reason for this and give the solution for over come this.
It's a warning, since you are using self-signed certificate that you generated is not trusted by the browser.
The warning will not occur when you use CA Certificate What are CA Certificates
You can suppress the warning by adding the certificate to the trusted root CA store Example
A self-signed certificate would not be recognised by the browser. Only CA signed certificate could be recognised.
You can set up a free trusted certificate with the Let's Encrypt project, this is the how-to tutorial.
And this is a wiki of CA.