parent
b8c953281e
commit
c84c751991
|
@ -9,13 +9,13 @@ import java.security.KeyPair;
|
|||
import java.security.KeyPairGenerator;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Signature;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.Cookie;
|
||||
|
@ -23,8 +23,6 @@ import javax.servlet.http.HttpServletRequest;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.mitre.openid.connect.model.IdToken;
|
||||
|
@ -33,6 +31,7 @@ import org.springframework.security.authentication.AuthenticationServiceExceptio
|
|||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.HttpClientErrorException;
|
||||
|
@ -93,21 +92,78 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
private final static String NONCE_SIGNATURE_COOKIE_NAME = "nonce";
|
||||
private final static String FILTER_PROCESSES_URL = "/openid_connect_login";
|
||||
|
||||
/**
|
||||
* Builds the redirect_uri that will be sent to the Authorization Endpoint.
|
||||
* By default returns the URL of the current request minus zero or more
|
||||
* fields of the URL's query string.
|
||||
*
|
||||
* @param request
|
||||
* the current request which is being processed by this filter
|
||||
* @param ignoreFields
|
||||
* an array of field names to ignore.
|
||||
* @return a URL built from the messaged parameters.
|
||||
*/
|
||||
public static String buildRedirectURI(HttpServletRequest request,
|
||||
String[] ignoreFields) {
|
||||
|
||||
List<String> ignore = (ignoreFields != null) ? Arrays
|
||||
.asList(ignoreFields) : null;
|
||||
|
||||
boolean isFirst = true;
|
||||
|
||||
StringBuffer sb = request.getRequestURL();
|
||||
|
||||
for (Enumeration<?> e = request.getParameterNames(); e
|
||||
.hasMoreElements();) {
|
||||
|
||||
String name = (String) e.nextElement();
|
||||
|
||||
if ((ignore == null) || (!ignore.contains(name))) {
|
||||
// Assume for simplicity that there is only one value
|
||||
String value = request.getParameter(name);
|
||||
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isFirst) {
|
||||
sb.append("?");
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
sb.append(name).append("=").append(value);
|
||||
|
||||
if (e.hasMoreElements()) {
|
||||
sb.append("&");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the URL w/ GET parameters
|
||||
*
|
||||
* @param baseURI
|
||||
* @param params
|
||||
* @return
|
||||
* A String containing the protocol, server address, path, and
|
||||
* program as per "http://server/path/program"
|
||||
* @param queryStringFields
|
||||
* A map where each key is the field name and the associated
|
||||
* key's value is the field value used to populate the URL's
|
||||
* query string
|
||||
* @return A String representing the URL in form of
|
||||
* http://server/path/program?query_string from the messaged
|
||||
* parameters.
|
||||
*/
|
||||
public static String buildURL(String baseURI,
|
||||
Map<String, String> urlVariables) {
|
||||
Map<String, String> queryStringFields) {
|
||||
|
||||
StringBuilder URLBuilder = new StringBuilder(baseURI);
|
||||
|
||||
char appendChar = '?';
|
||||
|
||||
for (Map.Entry<String, String> param : urlVariables.entrySet()) {
|
||||
for (Map.Entry<String, String> param : queryStringFields.entrySet()) {
|
||||
try {
|
||||
URLBuilder.append(appendChar).append(param.getKey())
|
||||
.append('=')
|
||||
|
@ -124,7 +180,13 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
/**
|
||||
* Returns the signature text for the byte array of data
|
||||
*
|
||||
* @return
|
||||
* @param signer
|
||||
* The algorithm to sign with
|
||||
* @param privateKey
|
||||
* The private key to sign with
|
||||
* @param data
|
||||
* The data to be signed
|
||||
* @return The signature text
|
||||
*/
|
||||
public static String sign(Signature signer, PrivateKey privateKey,
|
||||
byte[] data) {
|
||||
|
@ -151,8 +213,10 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
* Verifies the signature text against the data
|
||||
*
|
||||
* @param data
|
||||
* The data
|
||||
* @param sigText
|
||||
* @return
|
||||
* The signature text
|
||||
* @return True if valid, false if not
|
||||
*/
|
||||
public static boolean verify(Signature signer, PublicKey publicKey,
|
||||
String data, String sigText) {
|
||||
|
@ -197,7 +261,7 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
private Signature signer;
|
||||
|
||||
/**
|
||||
*
|
||||
* OpenIdConnectAuthenticationFilter constructor
|
||||
*/
|
||||
protected OpenIdConnectAuthenticationFilter() {
|
||||
super(FILTER_PROCESSES_URL);
|
||||
|
@ -213,34 +277,21 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
public void afterPropertiesSet() {
|
||||
super.afterPropertiesSet();
|
||||
|
||||
if (errorRedirectURI == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"An Error Redirect URI must be supplied");
|
||||
}
|
||||
Assert.notNull(errorRedirectURI,
|
||||
"An Error Redirect URI must be supplied");
|
||||
|
||||
if (authorizationEndpointURI == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"An Authorization Endpoint URI must be supplied");
|
||||
}
|
||||
Assert.notNull(authorizationEndpointURI,
|
||||
"An Authorization Endpoint URI must be supplied");
|
||||
|
||||
if (tokenEndpointURI == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"A Token ID Endpoint URI must be supplied");
|
||||
}
|
||||
Assert.notNull(tokenEndpointURI,
|
||||
"A Token ID Endpoint URI must be supplied");
|
||||
|
||||
if (checkIDEndpointURI == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"A Check ID Endpoint URI must be supplied");
|
||||
}
|
||||
Assert.notNull(checkIDEndpointURI,
|
||||
"A Check ID Endpoint URI must be supplied");
|
||||
|
||||
if (clientId == null) {
|
||||
throw new IllegalArgumentException("A Client ID must be supplied");
|
||||
}
|
||||
Assert.notNull(clientId, "A Client ID must be supplied");
|
||||
|
||||
if (clientSecret == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"A Client Secret must be supplied");
|
||||
}
|
||||
Assert.notNull(clientSecret, "A Client Secret must be supplied");
|
||||
|
||||
KeyPairGenerator keyPairGenerator;
|
||||
try {
|
||||
|
@ -256,19 +307,11 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
throw new IllegalStateException(generalSecurityException);
|
||||
}
|
||||
|
||||
// prepend the spec necessary scope
|
||||
// prepend the spec necessary SCOPE
|
||||
setScope((scope != null && !scope.isEmpty()) ? SCOPE + " " + scope
|
||||
: SCOPE);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.security.web.authentication.
|
||||
* AbstractAuthenticationProcessingFilter
|
||||
* #attemptAuthentication(javax.servlet.http.HttpServletRequest,
|
||||
* javax.servlet.http.HttpServletResponse)
|
||||
*/
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
|
@ -282,278 +325,22 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
HttpServletResponse response) throws AuthenticationException,
|
||||
IOException, ServletException {
|
||||
|
||||
final boolean debug = logger.isDebugEnabled();
|
||||
|
||||
if (request.getParameter("error") != null) {
|
||||
|
||||
// Handle Authorization Endpoint error
|
||||
|
||||
String error = request.getParameter("error");
|
||||
String errorDescription = request.getParameter("error_description");
|
||||
String errorURI = request.getParameter("error_uri");
|
||||
|
||||
Map<String, String> requestParams = new HashMap<String, String>();
|
||||
|
||||
requestParams.put("error", error);
|
||||
|
||||
if (errorDescription != null) {
|
||||
requestParams.put("error_description", errorDescription);
|
||||
}
|
||||
|
||||
if (errorURI != null) {
|
||||
requestParams.put("error_uri", errorURI);
|
||||
}
|
||||
|
||||
response.sendRedirect(buildURL(errorRedirectURI, requestParams));
|
||||
handleError(request, response);
|
||||
|
||||
} else {
|
||||
|
||||
// Determine if the Authorization Endpoint issued an
|
||||
// authorization grant
|
||||
|
||||
String authorizationGrant = request.getParameter("code");
|
||||
if (request.getParameter("code") != null) {
|
||||
|
||||
if (authorizationGrant != null) {
|
||||
|
||||
// Handle Token Endpoint interaction
|
||||
|
||||
HttpClient httpClient = new DefaultHttpClient();
|
||||
|
||||
httpClient.getParams().setParameter("http.socket.timeout",
|
||||
new Integer(httpSocketTimeout));
|
||||
|
||||
//
|
||||
// TODO: basic auth is untested (it wasn't working last I tested)
|
||||
// UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(
|
||||
// clientId, clientSecret);
|
||||
// ((DefaultHttpClient) httpClient).getCredentialsProvider()
|
||||
// .setCredentials(AuthScope.ANY, credentials);
|
||||
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
|
||||
httpClient);
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate(factory);
|
||||
|
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
|
||||
form.add("grant_type", "authorization_code");
|
||||
form.add("code", authorizationGrant);
|
||||
form.add("redirect_uri",
|
||||
buildRedirectURI(request, new String[] { "code" }));
|
||||
|
||||
// pass clientId and clientSecret in post of request
|
||||
form.add("client_id", clientId);
|
||||
form.add("client_secret", clientSecret);
|
||||
|
||||
|
||||
if (debug) {
|
||||
logger.debug("tokenEndpointURI = " + tokenEndpointURI);
|
||||
logger.debug("form = " + form);
|
||||
}
|
||||
|
||||
String jsonString = null;
|
||||
|
||||
try {
|
||||
jsonString = restTemplate.postForObject(tokenEndpointURI,
|
||||
form, String.class);
|
||||
} catch (HttpClientErrorException httpClientErrorException) {
|
||||
|
||||
// Handle error
|
||||
|
||||
logger.error("Token Endpoint error response: "
|
||||
+ httpClientErrorException.getStatusText() + " : "
|
||||
+ httpClientErrorException.getMessage());
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Unable to obtain Access Token.");
|
||||
}
|
||||
|
||||
JsonElement jsonRoot = new JsonParser().parse(jsonString);
|
||||
|
||||
if (jsonRoot.getAsJsonObject().get("error") != null) {
|
||||
|
||||
// Handle error
|
||||
|
||||
String error = jsonRoot.getAsJsonObject().get("error")
|
||||
.getAsString();
|
||||
|
||||
logger.error("Token Endpoint returned: " + error);
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Unable to obtain Access Token. Token Endpoint returned: "
|
||||
+ error);
|
||||
|
||||
} else {
|
||||
|
||||
// Extract the id_token to insert into the
|
||||
// OpenIdConnectAuthenticationToken
|
||||
|
||||
IdToken idToken = null;
|
||||
|
||||
if (jsonRoot.getAsJsonObject().get("id_token") != null) {
|
||||
|
||||
try {
|
||||
idToken = IdToken.parse(jsonRoot.getAsJsonObject()
|
||||
.get("id_token").getAsString());
|
||||
} catch (Exception e) {
|
||||
|
||||
// I suspect this could happen
|
||||
|
||||
logger.error("Problem parsing id_token: " + e);
|
||||
// e.printStackTrace();
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Problem parsing id_token return from Token endpoint: " + e);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// An error is unlikely, but it good security to check
|
||||
|
||||
logger.error("Token Endpoint did not return a token_id");
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Token Endpoint did not return a token_id");
|
||||
}
|
||||
|
||||
// Handle Check ID Endpoint interaction
|
||||
|
||||
httpClient = new DefaultHttpClient();
|
||||
|
||||
httpClient.getParams().setParameter("http.socket.timeout",
|
||||
new Integer(httpSocketTimeout));
|
||||
|
||||
factory = new HttpComponentsClientHttpRequestFactory(
|
||||
httpClient);
|
||||
restTemplate = new RestTemplate(factory);
|
||||
|
||||
form = new LinkedMultiValueMap<String, String>();
|
||||
|
||||
form.add("access_token",
|
||||
jsonRoot.getAsJsonObject().get("id_token")
|
||||
.getAsString());
|
||||
|
||||
jsonString = null;
|
||||
|
||||
try {
|
||||
jsonString = restTemplate.postForObject(
|
||||
checkIDEndpointURI, form, String.class);
|
||||
} catch (HttpClientErrorException httpClientErrorException) {
|
||||
|
||||
// Handle error
|
||||
|
||||
logger.error("Check ID Endpoint error response: "
|
||||
+ httpClientErrorException.getStatusText()
|
||||
+ " : " + httpClientErrorException.getMessage());
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Unable check token.");
|
||||
}
|
||||
|
||||
jsonRoot = new JsonParser().parse(jsonString);
|
||||
|
||||
// String iss = jsonRoot.getAsJsonObject().get("iss")
|
||||
// .getAsString();
|
||||
String userId = jsonRoot.getAsJsonObject().get("user_id")
|
||||
.getAsString();
|
||||
// String aud = jsonRoot.getAsJsonObject().get("aud")
|
||||
// .getAsString();
|
||||
String nonce = jsonRoot.getAsJsonObject().get("nonce")
|
||||
.getAsString();
|
||||
// String exp = jsonRoot.getAsJsonObject().get("exp")
|
||||
// .getAsString();
|
||||
|
||||
// Compare returned ID Token to signed session cookie
|
||||
// to detect ID Token replay by third parties.
|
||||
|
||||
Cookie nonceSignatureCookie = WebUtils.getCookie(request,
|
||||
NONCE_SIGNATURE_COOKIE_NAME);
|
||||
|
||||
if (nonceSignatureCookie != null) {
|
||||
|
||||
String sigText = nonceSignatureCookie.getValue();
|
||||
|
||||
if (sigText != null && !sigText.isEmpty()) {
|
||||
|
||||
if (!verify(signer, publicKey, nonce, sigText)) {
|
||||
logger.error("Possible replay attack detected! "
|
||||
+ "The comparison of the nonce in the returned "
|
||||
+ "ID Token to the signed session "
|
||||
+ NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " failed.");
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Possible replay attack detected! "
|
||||
+ "The comparison of the nonce in the returned "
|
||||
+ "ID Token to the signed session "
|
||||
+ NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " failed.");
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.error(NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " was found, but was null or empty.");
|
||||
|
||||
throw new AuthenticationServiceException(NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " was found, but was null or empty.");
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
logger.error(NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " cookie was not found.");
|
||||
|
||||
throw new AuthenticationServiceException(NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " cookie was not found.");
|
||||
}
|
||||
|
||||
// Create an Authentication object for the token, and
|
||||
// return.
|
||||
|
||||
OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(
|
||||
userId, idToken);
|
||||
|
||||
Authentication authentication = this
|
||||
.getAuthenticationManager().authenticate(token);
|
||||
|
||||
return authentication;
|
||||
|
||||
}
|
||||
return handleAuthorizationGrantResponse(request);
|
||||
|
||||
} else {
|
||||
|
||||
// Initiate an Authorization request
|
||||
|
||||
Map<String, String> urlVariables = new HashMap<String, String>();
|
||||
|
||||
// Required parameters:
|
||||
|
||||
urlVariables.put("response_type", "code");
|
||||
urlVariables.put("client_id", clientId);
|
||||
urlVariables.put("scope", scope);
|
||||
urlVariables.put("redirect_uri",
|
||||
buildRedirectURI(request, null));
|
||||
|
||||
// Create a string value used to associate a user agent session
|
||||
// with an ID Token to mitigate replay attacks. The value is
|
||||
// passed through unmodified to the ID Token. One method is to
|
||||
// store a random value as a signed session cookie, and pass the
|
||||
// value in the nonce parameter.
|
||||
|
||||
String nonce = new BigInteger(50, new Random()).toString(16);
|
||||
|
||||
Cookie nonceCookie = new Cookie(NONCE_SIGNATURE_COOKIE_NAME,
|
||||
sign(signer, privateKey, nonce.getBytes()));
|
||||
|
||||
response.addCookie(nonceCookie);
|
||||
|
||||
urlVariables.put("nonce", nonce);
|
||||
|
||||
// Optional parameters:
|
||||
|
||||
// TODO: display, prompt, request, request_uri
|
||||
|
||||
response.sendRedirect(buildURL(authorizationEndpointURI,
|
||||
urlVariables));
|
||||
handleAuthorizationRequest(request, response);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -561,52 +348,305 @@ public class OpenIdConnectAuthenticationFilter extends
|
|||
}
|
||||
|
||||
/**
|
||||
* Builds the redirect_uri that will be sent to the Authorization Endpoint.
|
||||
* By default returns the URL of the current request.
|
||||
* Handles the authorization grant response
|
||||
*
|
||||
* @param request
|
||||
* the current request which is being processed by this filter
|
||||
* @param ingoreParameters
|
||||
* an array of parameter names to ignore.
|
||||
* @return
|
||||
* The request from which to extract parameters and perform the
|
||||
* authentication
|
||||
* @return The authenticated user token, or null if authentication is
|
||||
* incomplete.
|
||||
*/
|
||||
private String buildRedirectURI(HttpServletRequest request,
|
||||
String[] ingoreParameters) {
|
||||
private Authentication handleAuthorizationGrantResponse(
|
||||
HttpServletRequest request) {
|
||||
|
||||
List<String> ignore = (ingoreParameters != null) ? Arrays
|
||||
.asList(ingoreParameters) : null;
|
||||
final boolean debug = logger.isDebugEnabled();
|
||||
|
||||
boolean isFirst = true;
|
||||
String authorizationGrant = request.getParameter("code");
|
||||
|
||||
StringBuffer sb = request.getRequestURL();
|
||||
// Handle Token Endpoint interaction
|
||||
HttpClient httpClient = new DefaultHttpClient();
|
||||
|
||||
for (Enumeration<?> e = request.getParameterNames(); e
|
||||
.hasMoreElements();) {
|
||||
httpClient.getParams().setParameter("http.socket.timeout",
|
||||
new Integer(httpSocketTimeout));
|
||||
|
||||
String name = (String) e.nextElement();
|
||||
//
|
||||
// TODO: basic auth is untested (it wasn't working last I
|
||||
// tested)
|
||||
// UsernamePasswordCredentials credentials = new
|
||||
// UsernamePasswordCredentials(
|
||||
// clientId, clientSecret);
|
||||
// ((DefaultHttpClient) httpClient).getCredentialsProvider()
|
||||
// .setCredentials(AuthScope.ANY, credentials);
|
||||
//
|
||||
|
||||
if ((ignore == null) || (!ignore.contains(name))) {
|
||||
// Assume for simplicity that there is only one value
|
||||
String value = request.getParameter(name);
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(
|
||||
httpClient);
|
||||
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
RestTemplate restTemplate = new RestTemplate(factory);
|
||||
|
||||
if (isFirst) {
|
||||
sb.append("?");
|
||||
isFirst = false;
|
||||
}
|
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
|
||||
form.add("grant_type", "authorization_code");
|
||||
form.add("code", authorizationGrant);
|
||||
form.add("redirect_uri", OpenIdConnectAuthenticationFilter
|
||||
.buildRedirectURI(request, new String[] { "code" }));
|
||||
|
||||
sb.append(name).append("=").append(value);
|
||||
// pass clientId and clientSecret in post of request
|
||||
form.add("client_id", clientId);
|
||||
form.add("client_secret", clientSecret);
|
||||
|
||||
if (e.hasMoreElements()) {
|
||||
sb.append("&");
|
||||
}
|
||||
}
|
||||
if (debug) {
|
||||
logger.debug("tokenEndpointURI = " + tokenEndpointURI);
|
||||
logger.debug("form = " + form);
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
String jsonString = null;
|
||||
|
||||
try {
|
||||
jsonString = restTemplate.postForObject(tokenEndpointURI, form,
|
||||
String.class);
|
||||
} catch (HttpClientErrorException httpClientErrorException) {
|
||||
|
||||
// Handle error
|
||||
|
||||
logger.error("Token Endpoint error response: "
|
||||
+ httpClientErrorException.getStatusText() + " : "
|
||||
+ httpClientErrorException.getMessage());
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Unable to obtain Access Token.");
|
||||
}
|
||||
|
||||
JsonElement jsonRoot = new JsonParser().parse(jsonString);
|
||||
|
||||
if (jsonRoot.getAsJsonObject().get("error") != null) {
|
||||
|
||||
// Handle error
|
||||
|
||||
String error = jsonRoot.getAsJsonObject().get("error")
|
||||
.getAsString();
|
||||
|
||||
logger.error("Token Endpoint returned: " + error);
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Unable to obtain Access Token. Token Endpoint returned: "
|
||||
+ error);
|
||||
|
||||
} else {
|
||||
|
||||
// Extract the id_token to insert into the
|
||||
// OpenIdConnectAuthenticationToken
|
||||
|
||||
IdToken idToken = null;
|
||||
|
||||
if (jsonRoot.getAsJsonObject().get("id_token") != null) {
|
||||
|
||||
try {
|
||||
idToken = IdToken.parse(jsonRoot.getAsJsonObject()
|
||||
.get("id_token").getAsString());
|
||||
} catch (Exception e) {
|
||||
|
||||
// I suspect this could happen
|
||||
|
||||
logger.error("Problem parsing id_token: " + e);
|
||||
// e.printStackTrace();
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Problem parsing id_token return from Token endpoint: "
|
||||
+ e);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// An error is unlikely, but it good security to check
|
||||
|
||||
logger.error("Token Endpoint did not return a token_id");
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Token Endpoint did not return a token_id");
|
||||
}
|
||||
|
||||
// Handle Check ID Endpoint interaction
|
||||
|
||||
httpClient = new DefaultHttpClient();
|
||||
|
||||
httpClient.getParams().setParameter("http.socket.timeout",
|
||||
new Integer(httpSocketTimeout));
|
||||
|
||||
factory = new HttpComponentsClientHttpRequestFactory(httpClient);
|
||||
restTemplate = new RestTemplate(factory);
|
||||
|
||||
form = new LinkedMultiValueMap<String, String>();
|
||||
|
||||
form.add("access_token", jsonRoot.getAsJsonObject().get("id_token")
|
||||
.getAsString());
|
||||
|
||||
jsonString = null;
|
||||
|
||||
try {
|
||||
jsonString = restTemplate.postForObject(checkIDEndpointURI,
|
||||
form, String.class);
|
||||
} catch (HttpClientErrorException httpClientErrorException) {
|
||||
|
||||
// Handle error
|
||||
|
||||
logger.error("Check ID Endpoint error response: "
|
||||
+ httpClientErrorException.getStatusText() + " : "
|
||||
+ httpClientErrorException.getMessage());
|
||||
|
||||
throw new AuthenticationServiceException("Unable check token.");
|
||||
}
|
||||
|
||||
jsonRoot = new JsonParser().parse(jsonString);
|
||||
|
||||
// String iss = jsonRoot.getAsJsonObject().get("iss")
|
||||
// .getAsString();
|
||||
String userId = jsonRoot.getAsJsonObject().get("user_id")
|
||||
.getAsString();
|
||||
// String aud = jsonRoot.getAsJsonObject().get("aud")
|
||||
// .getAsString();
|
||||
String nonce = jsonRoot.getAsJsonObject().get("nonce")
|
||||
.getAsString();
|
||||
// String exp = jsonRoot.getAsJsonObject().get("exp")
|
||||
// .getAsString();
|
||||
|
||||
// Compare returned ID Token to signed session cookie
|
||||
// to detect ID Token replay by third parties.
|
||||
|
||||
Cookie nonceSignatureCookie = WebUtils.getCookie(request,
|
||||
NONCE_SIGNATURE_COOKIE_NAME);
|
||||
|
||||
if (nonceSignatureCookie != null) {
|
||||
|
||||
String sigText = nonceSignatureCookie.getValue();
|
||||
|
||||
if (sigText != null && !sigText.isEmpty()) {
|
||||
|
||||
if (!verify(signer, publicKey, nonce, sigText)) {
|
||||
logger.error("Possible replay attack detected! "
|
||||
+ "The comparison of the nonce in the returned "
|
||||
+ "ID Token to the signed session "
|
||||
+ NONCE_SIGNATURE_COOKIE_NAME + " failed.");
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
"Possible replay attack detected! "
|
||||
+ "The comparison of the nonce in the returned "
|
||||
+ "ID Token to the signed session "
|
||||
+ NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " failed.");
|
||||
}
|
||||
|
||||
} else {
|
||||
logger.error(NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " was found, but was null or empty.");
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " was found, but was null or empty.");
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
logger.error(NONCE_SIGNATURE_COOKIE_NAME
|
||||
+ " cookie was not found.");
|
||||
|
||||
throw new AuthenticationServiceException(
|
||||
NONCE_SIGNATURE_COOKIE_NAME + " cookie was not found.");
|
||||
}
|
||||
|
||||
// Create an Authentication object for the token, and
|
||||
// return.
|
||||
|
||||
OpenIdConnectAuthenticationToken token = new OpenIdConnectAuthenticationToken(
|
||||
userId, idToken);
|
||||
|
||||
Authentication authentication = this.getAuthenticationManager()
|
||||
.authenticate(token);
|
||||
|
||||
return authentication;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate an Authorization request
|
||||
*
|
||||
* @param request
|
||||
* The request from which to extract parameters and perform the
|
||||
* authentication
|
||||
* @param response
|
||||
* The response, needed to set a cookie and do a redirect as part
|
||||
* of a multi-stage authentication process
|
||||
* @throws IOException
|
||||
* If an input or output exception occurs
|
||||
*/
|
||||
private void handleAuthorizationRequest(HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
|
||||
Map<String, String> urlVariables = new HashMap<String, String>();
|
||||
|
||||
// Required parameters:
|
||||
|
||||
urlVariables.put("response_type", "code");
|
||||
urlVariables.put("client_id", clientId);
|
||||
urlVariables.put("scope", scope);
|
||||
urlVariables.put("redirect_uri", OpenIdConnectAuthenticationFilter
|
||||
.buildRedirectURI(request, null));
|
||||
|
||||
// Create a string value used to associate a user agent session
|
||||
// with an ID Token to mitigate replay attacks. The value is
|
||||
// passed through unmodified to the ID Token. One method is to
|
||||
// store a random value as a signed session cookie, and pass the
|
||||
// value in the nonce parameter.
|
||||
|
||||
String nonce = new BigInteger(50, new SecureRandom()).toString(16);
|
||||
|
||||
Cookie nonceCookie = new Cookie(NONCE_SIGNATURE_COOKIE_NAME, sign(
|
||||
signer, privateKey, nonce.getBytes()));
|
||||
|
||||
response.addCookie(nonceCookie);
|
||||
|
||||
urlVariables.put("nonce", nonce);
|
||||
|
||||
// Optional parameters:
|
||||
|
||||
// TODO: display, prompt, request, request_uri
|
||||
|
||||
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL(
|
||||
authorizationEndpointURI, urlVariables));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Authorization Endpoint error
|
||||
*
|
||||
* @param request
|
||||
* The request from which to extract parameters and handle the
|
||||
* error
|
||||
* @param response
|
||||
* The response, needed to do a redirect to display the error
|
||||
* @throws IOException
|
||||
* If an input or output exception occurs
|
||||
*/
|
||||
private void handleError(HttpServletRequest request,
|
||||
HttpServletResponse response) throws IOException {
|
||||
|
||||
String error = request.getParameter("error");
|
||||
String errorDescription = request.getParameter("error_description");
|
||||
String errorURI = request.getParameter("error_uri");
|
||||
|
||||
Map<String, String> requestParams = new HashMap<String, String>();
|
||||
|
||||
requestParams.put("error", error);
|
||||
|
||||
if (errorDescription != null) {
|
||||
requestParams.put("error_description", errorDescription);
|
||||
}
|
||||
|
||||
if (errorURI != null) {
|
||||
requestParams.put("error_uri", errorURI);
|
||||
}
|
||||
|
||||
response.sendRedirect(OpenIdConnectAuthenticationFilter.buildURL(
|
||||
errorRedirectURI, requestParams));
|
||||
}
|
||||
|
||||
public void setAuthorizationEndpointURI(String authorizationEndpointURI) {
|
||||
|
|
Loading…
Reference in New Issue