Spring Boot Authentication with FreeIPA
April 24 2018 by Chris
FreeIPA is a free LDAP authentication system. Today we will integrate it with Spring Boot.
This guide assumes you have a knowledge of and working instance of a secured Spring Boot application. You can find a guide here. Click here for how to secure a Spring Boot application. We will assume you have a secured Spring Boot application for this point forward.
In your SpringWebSecurityConfig file (or whatever you called your class that extended WebSecurityConfigurerAdapter), add the following override:
...
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(freeIpaAuthenticationManager);
}
...
Add the following to your application.yaml:
freeipa.base.url: https://ipa.demo1.freeipa.org
Finally, we implement the freeIpaAuthenticationManager method:
@Component
public class FreeIpaAuthenticationManager implements AuthenticationProvider {
private Logger logger = Logger.getLogger(FreeIpaAuthenticationManager.class.getName());
private final String LOGIN_SERVICE = "/ipa/session/login_password";
private final String LOOKUP_SERVICE = "/ipa/session/json";
private final String LOOKUP_REFERER = "/ipa/ui/";
private Environment environment;
private String baseUrl = "";
@Autowired
public FreeIpaAuthenticationManager(Environment environment) {
this.environment = environment;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
this.baseUrl = this.environment.getProperty("freeipa.base.url");
String username = authentication.getName();
String password = authentication.getCredentials().toString();
Map<String, Object> lookup = this.auth(username, password);
Boolean authenticated = (Boolean) lookup.get("authenticated");
Map<String, Object> principal = (Map<String, Object>) lookup.get("principal");
List<GrantedAuthority> roles = (List<GrantedAuthority>) lookup.get("authorities");
if (authenticated) {
return new UsernamePasswordAuthenticationToken(principal, password, roles);
} else {
throw new BadCredentialsException("FreeIpaAuthenticationManager - Authentication failed for: " + username);
}
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
private Map<String, Object> auth(String username, String password) {
Map<String, Object> lookup = new HashMap<>();
lookup.put("authenticated", false);
lookup.put("principal", new HashMap<String, Object>());
lookup.put("authorities", new ArrayList<GrantedAuthority>());
try {
CloseableHttpClient client = HttpClients.custom().
setHostnameVerifier(new AllowAllHostnameVerifier()).
setSslcontext(new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
return true;
}
}).build()).build();
this.logger.info("FreeIpaAuthenticationManager - auth - URL: " + this.baseUrl + LOGIN_SERVICE);
HttpPost httpPost = new HttpPost(this.baseUrl + LOGIN_SERVICE);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("user", username));
params.add(new BasicNameValuePair("password", password));
httpPost.setEntity(new UrlEncodedFormEntity(params));
CloseableHttpResponse response = client.execute(httpPost);
Integer statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
String cookie = "";
for (Header header : response.getAllHeaders()) {
if (header.getName().equals("Set-Cookie")) {
cookie = header.getValue();
}
}
if (StringUtils.isNotEmpty(cookie)) {
lookup = this.lookup(client, cookie, username, lookup);
} else {
this.logger.info("FreeIpaAuthenticationManager - auth - error - cookie is empty");
}
} else if(statusCode == 401) {
this.logger.info("FreeIpaAuthenticationManager - auth - Unauthorized");
} else {
this.logger.info("FreeIpaAuthenticationManager - auth - error - statusCode: " + statusCode);
}
client.close();
} catch (Exception e) {
e.printStackTrace();
}
return lookup;
}
private Map<String, Object> lookup(CloseableHttpClient client, String cookie, String username, Map<String, Object> lookup) throws IOException {
HttpPost httpPost = new HttpPost(this.baseUrl + LOOKUP_SERVICE);
httpPost.setHeader("Cookie", cookie);
httpPost.setHeader("Referer",this.baseUrl + LOOKUP_REFERER);
httpPost.setHeader("Content-Type", "application/json");
HttpEntity entity = new NStringEntity("{\"method\":\"user_show/1\",\"params\":[[\"" + username + "\"],{\"all\":true,\"version\":\"2.229\"}]}", ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);
CloseableHttpResponse response = client.execute(httpPost);
Integer statusCode = response.getStatusLine().getStatusCode();
if (statusCode == 200) {
String responseBody = EntityUtils.toString(response.getEntity());
Map<String, Object> map = new ObjectMapper().readValue(responseBody, Map.class);
if (map != null) {
Map<String, Object> principal = (Map<String, Object>) map.get("result");
if (principal != null) {
Map<String, Object> principalDetails = (Map<String, Object>) principal.get("result");
if (principalDetails != null) {
List<String> groups = (List<String>) principalDetails.get("memberof_group");
if (groups != null) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (String group : groups) {
GrantedAuthority authority = new SimpleGrantedAuthority(group);
authorities.add(authority);
}
lookup.put("authenticated", true);
lookup.put("principal", principal);
lookup.put("authorities", authorities);
this.logger.info("FreeIpaAuthenticationManager - authenticated: " + username + ", authorities: " + authorities);
} else {
this.logger.info("FreeIpaAuthenticationManager - lookup - error - Unexpected response (groups)");
}
} else {
this.logger.info("FreeIpaAuthenticationManager - lookup - error - Unexpected response (principalDetails)");
}
} else {
this.logger.info("FreeIpaAuthenticationManager - lookup - error - Unexpected response (principal)");
}
} else {
this.logger.info("FreeIpaAuthenticationManager - lookup - error - Unexpected response (map)");
}
} else if (statusCode == 401) {
this.logger.info("FreeIpaAuthenticationManager - lookup - Unauthorized");
} else {
this.logger.info("FreeIpaAuthenticationManager - lookup - error - statusCode: " + statusCode);
}
return lookup;
}
}