This is my client code:
#GetMapping("/")
public String home() throws NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, KeyStoreException, KeyManagementException, UnrecoverableKeyException, RestClientException, URISyntaxException {
String url = "https://localhost:8483/secure-server/hola";
//
// KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
//
// keyStore.load(new FileInputStream(new File("client-keystore.jks")), "secret".toCharArray());
//
// System.out.println(url);
// SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
// new SSLContextBuilder()
// .loadTrustMaterial(null, new TrustSelfSignedStrategy())
// .loadKeyMaterial(keyStore, "secret".toCharArray())
// .build(),
// NoopHostnameVerifier.INSTANCE);
//
// HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
//
// ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
// RestTemplate restTemplate = new RestTemplate(requestFactory);
// String record = restTemplate.getForObject(url, String.class);
////
//
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<>("", headers);
return restTemplate.exchange(url, HttpMethod.POST, request , String.class ).getBody();
//
// ResponseEntity<String> resp = restTemplate.exchange(
// new URI(url), HttpMethod.GET,
// httpEntity, String.class);
//return model.getBody();
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) throws Exception {
char[] password = "secret".toCharArray();
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(keyStore("client-keystore.jks", password), password)
.loadTrustMaterial(new File("client-truststore.jks"),"secret".toCharArray()).build();
HttpClient client = HttpClients.custom().setSSLContext(sslContext).build();
return builder
.requestFactory(new HttpComponentsClientHttpRequestFactory(client))
.build();
}
private KeyStore keyStore(String file, char[] password) throws Exception {
KeyStore keyStore = KeyStore.getInstance("jks");
File key = ResourceUtils.getFile(file);
try (InputStream in = new FileInputStream(key)) {
keyStore.load(in, password);
}
return keyStore;
}
This is my server code with the two methods post and get, the get is working but post is not working:
#RestController
public class HomeRestController {
#PostMapping("/hola")
public String home(Principal principal) {
return String.format("Hello %s!", principal.getName());
}
#GetMapping("/holaa")
public String homee(Principal principal) {
return String.format("Hello %s!", principal.getName());
}
}
I have this is my YML with the mutual authentication configuration:
server:
context-path: /${spring.application.name}
port: 8483
ssl:
key-store: server-keystore.keystore
key-store-password: pass123
key-alias: default
trust-store: server-truststore.jks
trust-store-password: secret
enabled: true
client-auth: need
Calling the getMaping it works, but calling the postMaping it returns to me 403.
The keystore and trustore are configured and are OK.
And in my security configuration I have:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().x509()
.subjectPrincipalRegex("CN=(.*?)(?:,|$)").userDetailsService(userDetailsService());
}
#Override
#Bean
public UserDetailsService userDetailsService() {
return (username -> {
return new User(username, "",
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
});
}
}
Why my post calling does not work?
Related
I use spring boot with spring cloud gateway
I have another app with spring boot and thymeleaf
Spring gateway return a token to my thymeleaf app.
#EnableWebFluxSecurity
#Configuration
public class WebFluxSecurityConfig {
#Autowired
private WebFluxAuthManager authManager;
#Bean
protected SecurityWebFilterChain securityFilterChange(ServerHttpSecurity http) throws Exception {
http.authorizeExchange()
// URL that starts with / or /login/
.pathMatchers("/", "/login", "/js/**", "/images/**", "/css/**", "/h2-console/**").permitAll()
.anyExchange().authenticated().and().formLogin()
.authenticationManager(authManager)
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccesHandler("/findAllCustomers"));
return http.build();
}
}
WebFluxAuthManager class
#Component
public class WebFluxAuthManager implements ReactiveAuthenticationManager {
#Value("${gateway.url}")
private String gatewayUrl;
#Override
public Mono<Authentication> authenticate(Authentication authentication) {
// return is already authenticated
if (authentication.isAuthenticated()) {
return Mono.just(authentication);
}
String username = authentication.getName();
String password = authentication.getCredentials().toString();
LoginRequest loginRequest = new LoginRequest(username, password);
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
//todo modify to use webclient
HttpPost httpPost = new HttpPost(this.gatewayUrl + "/authenticate");
httpPost.setHeader("Content-type", "application/json");
String jsonReq = converObjectToJson(loginRequest);
StringEntity requestEntity = new StringEntity(jsonReq);
httpPost.setEntity(requestEntity);
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.OK.value()) {
HttpEntity entity = httpResponse.getEntity();
Header encodingHeader = entity.getContentEncoding();
Charset encoding = encodingHeader == null ? StandardCharsets.UTF_8
: Charsets.toCharset(encodingHeader.getValue());
// use org.apache.http.util.EntityUtils to read json as string
String jsonRes = EntityUtils.toString(entity, encoding);
LoginResponse loginResponse = converJsonToResponse(jsonRes);
Collection<? extends GrantedAuthority> authorities = loginResponse.getRoles().stream()
.map(item -> new SimpleGrantedAuthority(item)).collect(Collectors.toList());
return Mono.just(new UsernamePasswordAuthenticationToken(username, password, authorities));
} else {
throw new BadCredentialsException("Authentication Failed!!!");
}
} catch (RestClientException | ParseException | IOException e) {
throw new BadCredentialsException("Authentication Failed!!!", e);
} finally {
try {
if (httpClient != null)
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
In WebFluxAuthManager, I have access to the token, now I search a way to transfert it to a fragment.
I'm writing a Flutter web project with a Spring boot backend and am really battling with getting the authentication stuff to work.
In flutter web I have a "sign_in" method which receives an email and password and passes it to a repository method which sends a post request to the server. See code below. Currently it appears as if the post never returns as the "done with post" line never prints.
Future<String> signIn(String email, String password) async {
authenticationRepository.setStatus(AuthenticationStatus.unknown());
print('signIn user: email: $email pw: $password');
User user = User('null', email, password: password);
//print('user: $user');
var url;
if (ServerRepository.SERVER_USE_HTTPS) {
url = new Uri.https(ServerRepository.SERVER_ADDRESS,
ServerRepository.SERVER_AUTH_LOGIN_ENDPOINT);
} else {
url = new Uri.http(ServerRepository.SERVER_ADDRESS,
ServerRepository.SERVER_AUTH_LOGIN_ENDPOINT);
}
// print('url: $url');
var json = user.toUserRegisterEntity().toJson();
print('Sending request: $json');
// var response = await http.post(url, body: json);
var response = await ServerRepository.performPostRequest(url, jsonBody: json, printOutput: true, omitHeaders: true );
print('Response status: ${response.statusCode}');
print('Response body b4 decoding: ${response.body}');
Map<String, dynamic> responseBody = jsonDecode(response.body);
print('Response body parsed: $responseBody');
if (response.statusCode != 201) {
authenticationRepository
.setStatus(AuthenticationStatus.unauthenticated());
throw FailedRequestError('${responseBody['message']}');
}
User user2 = User(
responseBody['data']['_id'], responseBody['data']['email'],
accessToken: responseBody['accessToken'],
refreshToken: responseBody['refreshToken']);
print('user2 $user2');
authenticationRepository
.setStatus(AuthenticationStatus.authenticated(user2));
return responseBody['data']['_id']; // return the id of the response
}
static Future<Response> performPostRequest(Uri url, {String? accessToken, var jsonBody, bool printOutput = false, bool omitHeaders=false} ) async {
var body = json.encode(jsonBody ?? '');
if(printOutput){
print('Posting to url: $url');
print('Request Body: $body');
}
Map<String, String> userHeader = {
HttpHeaders.authorizationHeader: 'Bearer ${accessToken ?? 'accessToken'}',
"Content-type": "application/json",
};
if(omitHeaders){
userHeader = { };
}
print('performing post: ');
var response = await http.post(
url,
body: body,
headers: userHeader,
);
print('done with post?!');
if(printOutput){
print('Response status: ${response.statusCode}');
print('Response body: ${response.body}');
Map<String, dynamic> responseBody = jsonDecode(response.body);
print('Response body parsed: $responseBody');
}
return response;
}
My console output is as follows when attempting the request:
signIn user: email: XXXXXX#gmail.com pw: XXxxXXx500!
Sending request: {email: XXXXXX#gmail.com, password: XXxxXXx500!}
Posting to url: http://localhost:8080/auth/login
Request Body: {"email":"XXXXXX#gmail.com","password":"XXxxXXx500!"}
performing post:
So it seems like the response is never sent by the server.
On my server, using Spring boot security the setup is as follows (I based it from this tutorial). Securityconfig:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final JWTUtils jwtTokenUtil;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CustomAuthenticationFilter customAuthenticationFilter = new CustomAuthenticationFilter(jwtTokenUtil, authenticationManagerBean());
customAuthenticationFilter.setFilterProcessesUrl("/auth/login");
http.csrf().disable();
//http.cors(); //tried but still no repsonse
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers( "/auth/**").permitAll(); // no restrictions on this end point
http.authorizeRequests().antMatchers(POST, "/users").permitAll();
http.authorizeRequests().antMatchers(GET, "/users/**").hasAnyAuthority("ROLE_USER");
http.authorizeRequests().antMatchers(POST, "/users/role/**").hasAnyAuthority("ROLE_ADMIN");
http.authorizeRequests().anyRequest().authenticated();
http.addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
And the filter handling the "/auth/login" end point:
#Slf4j
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JWTUtils jwtTokenUtil;
private final AuthenticationManager authenticationManager;
#Autowired
public CustomAuthenticationFilter(JWTUtils jwtTokenUtil, AuthenticationManager authenticationManager) {
this.jwtTokenUtil = jwtTokenUtil;
this.authenticationManager = authenticationManager;
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
log.info("attemptAuthentication");
log.info("type "+request.getHeader("Content-Type"));
try {
//Wrap the request
MutableHttpServletRequest wrapper = new MutableHttpServletRequest(request);
//Get the input stream from the wrapper and convert it into byte array
byte[] body;
body = StreamUtils.copyToByteArray(wrapper.getInputStream());
Map<String, String> jsonRequest = new ObjectMapper().readValue(body, Map.class);
log.info("jsonRequest "+jsonRequest);
String email = jsonRequest.get("email");
String password = jsonRequest.get("password");
log.info("jsonRequest username is "+email);
log.info("jsonRequest password is "+password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
} catch (IOException e) {
e.printStackTrace();
}
//if data is not passed as json, but rather form Data - then this should allow it to work as well
String email = request.getParameter("email");
String password = request.getParameter("password");
log.info("old username is "+email);
log.info("old password is "+password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
return authenticationManager.authenticate(authenticationToken);
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
log.info("successfulAuthentication");
User user = (User) authResult.getPrincipal();
String[] tokens = jwtTokenUtil.generateJWTTokens(user.getUsername()
,user.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())
, request.getRequestURL().toString() );
String access_token = tokens[0];
String refresh_token = tokens[1];
log.info("tokens generated");
Map<String, String> tokensMap = new HashMap<>();
tokensMap.put("access_token", access_token);
tokensMap.put("refresh_token", refresh_token);
response.setContentType(APPLICATION_JSON_VALUE);
log.info("writing result");
response.setStatus(HttpServletResponse.SC_OK);
new ObjectMapper().writeValue(response.getWriter(), tokensMap);
}
}
When I try the "auth/login" endpoint using postman, I get the correct response with the jwt tokens. See below:
I'm really stuck and have no idea how to fix it. I've tried setting cors on, changing the content-type (which helped making the server see the POST request instead of an OPTIONS request). Any help/explanation would be greatly appreciated.
After lots of trial and error I stumbled across this answer on a JavaScript/ajax question.
It boils down to edge/chrome not liking the use of localhost in a domain. so, if you're using a Spring Boot server, add the following bean to your application class (remember to update the port number):
#Bean
public CorsFilter corsFilter() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:56222"));
corsConfiguration.setAllowedHeaders(Arrays.asList("Origin","Access-Control-Allow-Origin",
"Content-Type","Accept","Authorization","Origin,Accept","X-Requested-With",
"Access-Control-Request-Method","Access-Control-Request-Headers"));
corsConfiguration.setExposedHeaders(Arrays.asList("Origin","Content-Type","Accept","Authorization",
"Access-Control-Allow-Origin","Access-Control-Allow-Origin","Access-Control-Allow-Credentials"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET","PUT","POST","DELETE","OPTIONS"));
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(urlBasedCorsConfigurationSource);
}
I tried to call the rest endpoint from browser and is working fine, but with rest template, i am not getting any response.
URL: https://www1.nseindia.com/marketinfo/sym_map/symbolCount.jsp?symbol=INFY
public class Test {
public static void main(String[] args) throws Exception {
try {
String jksPath = "C:\\ssl_server.jks";
String pass = "123456";
SSLContext ssl = SSLContextBuilder.create()
.loadTrustMaterial(ResourceUtils.getFile(jksPath), pass.toCharArray()).build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(ssl, new LHVerifier());
CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
requestFactory.setConnectionRequestTimeout(15000);
requestFactory.setReadTimeout(15000);
System.out.println("Start: " + new Date());
HttpHeaders headers = new HttpHeaders();
headers.set(org.apache.http.HttpHeaders.ACCEPT, "*/*");
HttpEntity<?> httpEntity = new HttpEntity<>(headers);
ResponseEntity<Object> response = new RestTemplate(requestFactory).exchange(
"https://www1.nseindia.com/marketinfo/sym_map/symbolCount.jsp?symbol=INFY", HttpMethod.GET,
httpEntity, Object.class);
} catch (Exception e) {
System.out.println("Exception: " + new Date());
e.printStackTrace();
}
System.out.println("done");
}
}
class LHVerifier implements HostnameVerifier {
#Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
I am trying to secure a swagger interface withj OpenId Connect.
I can login using OIDC and swagger shows me as authorised:
but when I do 'try it out' the authentication is ignored and a login box pops up:
In my class which extends SpringBootServletInitializer I have:
#Bean
#ConditionalOnProperty("security.oauth2.client.clientId")
public SecurityScheme securityScheme(Environment environment, OAuth2ClientProperties clientProperties) {
String authorizationUri = environment.getRequiredProperty("security.oauth2.client.user-authorization-uri");
String accessTokenUri = environment.getRequiredProperty("security.oauth2.client.access-token-uri");
LoginEndpoint loginEndpoint = new LoginEndpoint(authorizationUri);
TokenRequestEndpoint tokenRequestEndpoint =
new TokenRequestEndpoint(authorizationUri, clientProperties.getClientId(), clientProperties.getClientSecret());
TokenEndpoint tokenEndpoint = new TokenEndpoint(accessTokenUri, "auth_code");
GrantType grantType = new AuthorizationCodeGrant(tokenRequestEndpoint, tokenEndpoint);
AuthorizationScope authorizationScope = new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobal);
return new OAuthBuilder()
.name(securitySchemaOAuth2)
.grantTypes(Arrays.asList(grantType))
.scopes(Arrays.asList(authorizationScope))
.build();
}
private List<SecurityReference> defaultAuth() {
AuthorizationScope authorizationScope = new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobalDesc);
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
authorizationScopes[0] = authorizationScope;
return Arrays.asList(new SecurityReference(securitySchemaOAuth2, authorizationScopes));
}
#Bean
SecurityConfiguration security(OAuth2ClientProperties clientProperties) {
return new SecurityConfiguration(
clientProperties.getClientId(),
clientProperties.getClientSecret(),
securitySchemaOAuth2,
"test-app",
"apiKey",
ApiKeyVehicle.HEADER,
"api_key",
" " /*scope separator*/);
}
#Bean
public SecurityContext securityContext() {
return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.regex("/v1/.*")).build();
}
and a class with:
#ApiModel(value = "Template", description = "Template of REST APIs")
#RestController
#RequestMapping("/v1")
public class TemplateServiceImplementation {
...
#ApiOperation(httpMethod = "GET", value = "Call Get method",
notes = "See Get method")
#RequestMapping(method = RequestMethod.GET, value = "/calltemplate/{param}/", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Structure> callGet(#PathVariable("param") String param, HttpServletRequest hreq) {
MultiValueMap<String, String> mapParams = new LinkedMultiValueMap<String, String>();
mapParams.add("param", param);
Structure structure = restTemplate.getForObject(callGetEndpoint, Structure.class, mapParams);
ResponseEntity<Structure> thisresponse = new ResponseEntity<Structure>(structure, HttpStatus.OK);
return thisresponse;
}
Sorry for all the code. So how do I make the GET use my OIDC authentication?
When I cancel the login, curl is:
curl -X GET --header 'Accept: application/json' --header 'Authorization: Bearer eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMy lots more encrypted text' 'http://localhost:8080/v1/calltemplate/%7B%20%20%20%22id%22%3A%20%22string%22%2C%20%20%20%22name%22%3A%20%22string%22%2C%20%20%20%22path%22%3A%20%22string%22%2C%20%20%20%22version%22%3A%20%22string%22%20%7D/'
request url:
http://localhost:8080/v1/calltemplate/%7B%20%20%20%22id%22%3A%20%22string%22%2C%20%20%20%22name%22%3A%20%22string%22%2C%20%20%20%22path%22%3A%20%22string%22%2C%20%20%20%22version%22%3A%20%22string%22%20%7D/
and other response variables:
EDIT Forgot to metion my WebSecurityConfig:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/ui",
"/swagger-resources/**", "/configuration/**", "/swagger-ui.html"
, "/webjars/**", "/csrf", "/");
}
UPDATE
Output of network:
Can anybody provide me with a code sample to access the rest service URL secured with HTTPS using the Spring Rest template?
I have the certificate, username and password. Basic Authentication is used on the server-side and I want to create a client that can connect to that server using a provided certificate, username and password (if needed).
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(new FileInputStream(new File(keyStoreFile)),
keyStorePassword.toCharArray());
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
new SSLContextBuilder()
.loadTrustMaterial(null, new TrustSelfSignedStrategy())
.loadKeyMaterial(keyStore, keyStorePassword.toCharArray())
.build(),
NoopHostnameVerifier.INSTANCE);
HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(
socketFactory).build();
ClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
MyRecord record = restTemplate.getForObject(uri, MyRecord.class);
LOG.debug(record.toString());
Here is some code that will give you the general idea.
You need to create a custom ClientHttpRequestFactory in order to trust the certificate.
It looks like this:
final ClientHttpRequestFactory clientHttpRequestFactory =
new MyCustomClientHttpRequestFactory(org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER, serverInfo);
restTemplate.setRequestFactory(clientHttpRequestFactory);
This is the implementation for MyCustomClientHttpRequestFactory:
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
private final HostnameVerifier hostNameVerifier;
private final ServerInfo serverInfo;
public MyCustomClientHttpRequestFactory (final HostnameVerifier hostNameVerifier,
final ServerInfo serverInfo) {
this.hostNameVerifier = hostNameVerifier;
this.serverInfo = serverInfo;
}
#Override
protected void prepareConnection(final HttpURLConnection connection, final String httpMethod)
throws IOException {
if (connection instanceof HttpsURLConnection) {
((HttpsURLConnection) connection).setHostnameVerifier(hostNameVerifier);
((HttpsURLConnection) connection).setSSLSocketFactory(initSSLContext()
.getSocketFactory());
}
super.prepareConnection(connection, httpMethod);
}
private SSLContext initSSLContext() {
try {
System.setProperty("https.protocols", "TLSv1");
// Set ssl trust manager. Verify against our server thumbprint
final SSLContext ctx = SSLContext.getInstance("TLSv1");
final SslThumbprintVerifier verifier = new SslThumbprintVerifier(serverInfo);
final ThumbprintTrustManager thumbPrintTrustManager =
new ThumbprintTrustManager(null, verifier);
ctx.init(null, new TrustManager[] { thumbPrintTrustManager }, null);
return ctx;
} catch (final Exception ex) {
LOGGER.error(
"An exception was thrown while trying to initialize HTTP security manager.", ex);
return null;
}
}
In this case my serverInfo object contains the thumbprint of the server.
You need to implement the TrustManager interface to get
the SslThumbprintVerifier or any other method you want to verify your certificate (you can also decide to also always return true).
The value org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER allows all host names.
If you need to verify the host name,
you will need to implement it differently.
I'm not sure about the user and password and how you implemented it.
Often,
you need to add a header to the restTemplate named Authorization
with a value that looks like this: Base: <encoded user+password>.
The user+password must be Base64 encoded.
This is a solution with no deprecated class or method :
(Java 8 approved)
CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new NoopHostnameVerifier()).build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);
Important information : Using NoopHostnameVerifier is a security risk
One point from me. I used a mutual cert authentication with spring-boot microservices. The following is working for me, key points here are
keyManagerFactory.init(...) and sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom()) lines of code without them, at least for me, things did not work. Certificates are packaged by PKCS12.
#Value("${server.ssl.key-store-password}")
private String keyStorePassword;
#Value("${server.ssl.key-store-type}")
private String keyStoreType;
#Value("${server.ssl.key-store}")
private Resource resource;
private RestTemplate getRestTemplate() throws Exception {
return new RestTemplate(clientHttpRequestFactory());
}
private ClientHttpRequestFactory clientHttpRequestFactory() throws Exception {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
private HttpClient httpClient() throws Exception {
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore trustStore = KeyStore.getInstance(keyStoreType);
if (resource.exists()) {
InputStream inputStream = resource.getInputStream();
try {
if (inputStream != null) {
trustStore.load(inputStream, keyStorePassword.toCharArray());
keyManagerFactory.init(trustStore, keyStorePassword.toCharArray());
}
} finally {
if (inputStream != null) {
inputStream.close();
}
}
} else {
throw new RuntimeException("Cannot find resource: " + resource.getFilename());
}
SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(trustStore, new TrustSelfSignedStrategy()).build();
sslcontext.init(keyManagerFactory.getKeyManagers(), null, new SecureRandom());
SSLConnectionSocketFactory sslConnectionSocketFactory =
new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1.2"}, null, getDefaultHostnameVerifier());
return HttpClients.custom().setSSLSocketFactory(sslConnectionSocketFactory).build();
}
Here is what I ended up with for the similar problem. The idea is the same as in #Avi's answer, but I also wanted to avoid the static "System.setProperty("https.protocols", "TLSv1");", so that any adjustments won't affect the system. Inspired by an answer from here http://www.coderanch.com/t/637177/Security/Disabling-handshake-message-Java
public class MyCustomClientHttpRequestFactory extends SimpleClientHttpRequestFactory {
#Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier((hostname, session) -> true);
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
throw Throwables.propagate(e);
}
}
/**
* We need to invoke sslSocket.setEnabledProtocols(new String[] {"SSLv3"});
* see http://www.oracle.com/technetwork/java/javase/documentation/cve-2014-3566-2342133.html (Java 8 section)
*/
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
#Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
#Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
#Override
public Socket createSocket(final Socket socket, final String host, final int port, final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
#Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress, final int localPort) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[] {"SSLv3"});
return socket;
}
}
}
You need to configure a raw HttpClient with SSL support, something like this:
#Test
public void givenAcceptingAllCertificatesUsing4_4_whenUsingRestTemplate_thenCorrect()
throws ClientProtocolException, IOException {
CloseableHttpClient httpClient
= HttpClients.custom()
.setSSLHostnameVerifier(new NoopHostnameVerifier())
.build();
HttpComponentsClientHttpRequestFactory requestFactory
= new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
ResponseEntity<String> response
= new RestTemplate(requestFactory).exchange(
urlOverHttps, HttpMethod.GET, null, String.class);
assertThat(response.getStatusCode().value(), equalTo(200));
}
from: Baeldung