/* * ****************************************************************************** * Cloud Foundry * Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved. * * This product is licensed to you under the Apache License, Version 2.0 (the "License"). * You may not use this product except in compliance with the License. * * This product includes a number of subcomponents with * separate copyright notices and license terms. Your use of these * subcomponents is subject to the terms and conditions of the * subcomponent's license, as noted in the LICENSE file. * ****************************************************************************** */ package org.cloudfoundry.identity.uaa.authentication.manager; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.cloudfoundry.identity.uaa.user.UaaAuthority; import org.cloudfoundry.identity.uaa.util.LinkedMaskingMultiValueMap; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.util.MultiValueMap; import org.springframework.web.client.DefaultResponseErrorHandler; import org.springframework.web.client.RestOperations; import org.springframework.web.client.RestTemplate; import java.util.Arrays; import java.util.Map; /** * An authentication manager that can be used to login to a remote UAA service * with username and password credentials, * without the local server needing to know anything about the user accounts. * The request is handled by the UAA's * RemoteAuhenticationEndpoint and success or failure is determined by the * response code. * * @author Dave Syer * @author Luke Taylor * */ public class RestAuthenticationManager implements AuthenticationManager { protected final Log logger = LogFactory.getLog(getClass()); private RestOperations restTemplate = new RestTemplate(); private static String DEFAULT_LOGIN_URL = "http://uaa.cloudfoundry.com/authenticate"; private String remoteUrl = DEFAULT_LOGIN_URL; private boolean nullPassword = false; /** * @param remoteUrl the login url to set */ public void setRemoteUrl(String remoteUrl) { this.remoteUrl = remoteUrl; } public String getRemoteUrl() { return remoteUrl; } /** * @param restTemplate a rest template to use */ public void setRestTemplate(RestOperations restTemplate) { this.restTemplate = restTemplate; } public RestOperations getRestTemplate() { return restTemplate; } public RestAuthenticationManager() { RestTemplate restTemplate = new RestTemplate(); // The default java.net client doesn't allow you to handle 4xx responses restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory()); restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { @Override protected boolean hasError(HttpStatus statusCode) { return statusCode.series() == HttpStatus.Series.SERVER_ERROR; } }); this.restTemplate = restTemplate; } @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = (String) authentication.getCredentials(); HttpHeaders headers = getHeaders(); @SuppressWarnings("rawtypes") ResponseEntity<Map> response = restTemplate.exchange(remoteUrl, HttpMethod.POST, new HttpEntity<Object>(getParameters(username, password), headers), Map.class); if (response.getStatusCode() == HttpStatus.OK || response.getStatusCode() == HttpStatus.CREATED) { if (evaluateResponse(authentication,response)) { logger.info("Successful authentication request for " + authentication.getName()); //TODO - we can return a UAA principal containing the correct origin here. return new UsernamePasswordAuthenticationToken(username, nullPassword?null:"", UaaAuthority.USER_AUTHORITIES); } } else if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) { logger.info("Failed authentication request"); throw new BadCredentialsException("Authentication failed"); } else if (response.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR) { logger.info("Internal error from UAA. Please Check the UAA logs."); } else { logger.error("Unexpected status code " + response.getStatusCode() + " from the UAA." + " Is a compatible version running?"); } throw new RuntimeException("Could not authenticate with remote server"); } protected boolean evaluateResponse(Authentication authentication, ResponseEntity<Map> response) { String userFromUaa = (String) response.getBody().get("username"); if (userFromUaa.equals(authentication.getPrincipal().toString())) { return true; } else { return false; } } protected Object getParameters(String username, String password) { MultiValueMap<String, Object> parameters = new LinkedMaskingMultiValueMap<String, Object>("password"); parameters.set("username", username); parameters.set("password", password); return parameters; } protected HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); return headers; } public boolean isNullPassword() { return nullPassword; } public void setNullPassword(boolean nullPassword) { this.nullPassword = nullPassword; } }