diff --git a/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java
index cd169c509..1ddf77bbe 100644
--- a/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java
+++ b/openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java
@@ -25,6 +25,7 @@ import org.mitre.openid.connect.config.ConfigurationPropertiesBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
import com.google.common.base.Strings;
import com.nimbusds.jwt.JWT;
@@ -37,6 +38,7 @@ import com.nimbusds.jwt.SignedJWT;
* @author jricher
*
*/
+@Component("selfAssertionValidator")
public class SelfAssertionValidator implements AssertionValidator {
private static Logger logger = LoggerFactory.getLogger(SelfAssertionValidator.class);
@@ -62,16 +64,19 @@ public class SelfAssertionValidator implements AssertionValidator {
return false;
}
+ // make sure the issuer exists
if (Strings.isNullOrEmpty(claims.getIssuer())) {
logger.debug("No issuer for assertion, rejecting");
return false;
}
- if (claims.getIssuer().equals(config.getIssuer())) {
+ // make sure the issuer is us
+ if (!claims.getIssuer().equals(config.getIssuer())) {
logger.debug("Issuer is not the same as this server, rejecting");
return false;
}
+ // validate the signature based on our public key
if (jwtService.validateSignature((SignedJWT) assertion)) {
return true;
} else {
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp
new file mode 100644
index 000000000..a53199e2f
--- /dev/null
+++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/logoutConfirmation.jsp
@@ -0,0 +1,55 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
+<%@ page import="org.springframework.security.core.AuthenticationException"%>
+<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%>
+<%@ page import="org.springframework.security.web.WebAttributes"%>
+<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
+
+
+
+
+
+
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp
new file mode 100644
index 000000000..aafd0a0fb
--- /dev/null
+++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/postLogout.jsp
@@ -0,0 +1,26 @@
+<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
+<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%>
+<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
+<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
+<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
+
+
+
+
+
+
diff --git a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp
index 69a6ed301..9551acbff 100644
--- a/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp
+++ b/openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp
@@ -16,7 +16,7 @@
-
+
diff --git a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java
index 2856b6513..abaf20a7a 100644
--- a/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java
+++ b/openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java
@@ -20,22 +20,35 @@ package org.mitre.openid.connect.web;
import java.text.ParseException;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
import org.mitre.jwt.assertion.AssertionValidator;
import org.mitre.jwt.assertion.impl.SelfAssertionValidator;
+import org.mitre.oauth2.model.ClientDetailsEntity;
+import org.mitre.oauth2.service.ClientDetailsEntityService;
+import org.mitre.openid.connect.model.UserInfo;
import org.mitre.openid.connect.service.UserInfoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
-import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
+import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.util.UriComponents;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.web.util.UriUtils;
import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.JWTParser;
/**
@@ -47,48 +60,137 @@ public class EndSessionEndpoint {
public static final String URL = "endsession";
+ private static final String CLIENT_KEY = "client";
+ private static final String STATE_KEY = "state";
+ private static final String REDIRECT_URI_KEY = "redirectUri";
+
private static Logger logger = LoggerFactory.getLogger(EndSessionEndpoint.class);
- private AssertionValidator validator = new SelfAssertionValidator();
+ @Autowired
+ private SelfAssertionValidator validator;
@Autowired
private UserInfoService userInfoService;
- @RequestMapping(value = "/" + URL)
+ @Autowired
+ private ClientDetailsEntityService clientService;
+
+ @RequestMapping(value = "/" + URL, method = RequestMethod.GET)
public String endSession(@RequestParam (value = "id_token_hint", required = false) String idTokenHint,
@RequestParam (value = "post_logout_redirect_uri", required = false) String postLogoutRedirectUri,
- @RequestParam (value = "state", required = false) String state,
+ @RequestParam (value = STATE_KEY, required = false) String state,
HttpServletRequest request,
+ HttpServletResponse response,
+ HttpSession session,
Authentication auth, Model m) {
+ // conditionally filled variables
+ JWTClaimsSet idTokenClaims = null; // pulled from the parsed and validated ID token
+ ClientDetailsEntity client = null; // pulled from ID token's audience field
+
+ if (!Strings.isNullOrEmpty(postLogoutRedirectUri)) {
+ session.setAttribute(REDIRECT_URI_KEY, postLogoutRedirectUri);
+ }
+ if (!Strings.isNullOrEmpty(state)) {
+ session.setAttribute(STATE_KEY, state);
+ }
+
+ // parse the ID token hint to see if it's valid
+ if (!Strings.isNullOrEmpty(idTokenHint)) {
+ try {
+ JWT idToken = JWTParser.parse(idTokenHint);
+
+ if (validator.isValid(idToken)) {
+ // we issued this ID token, figure out who it's for
+ idTokenClaims = idToken.getJWTClaimsSet();
+
+ String clientId = Iterables.getOnlyElement(idTokenClaims.getAudience());
+
+ client = clientService.loadClientByClientId(clientId);
+
+ // save a reference in the session for us to pick up later
+ //session.setAttribute("endSession_idTokenHint_claims", idTokenClaims);
+ session.setAttribute(CLIENT_KEY, client);
+ }
+ } catch (ParseException e) {
+ // it's not a valid ID token, ignore it
+ logger.debug("Invalid id token hint", e);
+ } catch (InvalidClientException e) {
+ // couldn't find the client, ignore it
+ logger.debug("Invalid client", e);
+ }
+ }
+
// are we logged in or not?
if (auth == null || !request.isUserInRole("ROLE_USER")) {
- // we're not logged in, process the logout
- return null;
+ // we're not logged in anyway, process the final redirect bits if needed
+ return processLogout(null, request, response, session, auth, m);
} else {
// we are logged in, need to prompt the user before we log out
- // parse the ID token hint to see if it's valid
- if (!Strings.isNullOrEmpty(idTokenHint)) {
- try {
- JWT idToken = JWTParser.parse(idTokenHint);
-
- if (validator.isValid(idToken)) {
- // we issued this ID token, figure out who it's for
- String subject = idToken.getJWTClaimsSet().getSubject();
-
- userInfoService.getByUsername(subject);
-
- }
- } catch (ParseException e) {
-
- }
-
+ // see who the current user is
+ UserInfo ui = userInfoService.getByUsername(auth.getName());
+
+ if (idTokenClaims != null) {
+ String subject = idTokenClaims.getSubject();
+ // see if the current user is the same as the one in the ID token
+ // TODO: should we do anything different in these cases?
+ if (!Strings.isNullOrEmpty(subject) && subject.equals(ui.getSub())) {
+ // it's the same user
+ } else {
+ // it's not the same user
+ }
}
+
+ m.addAttribute("client", client);
+ m.addAttribute("idToken", idTokenClaims);
- // display the end session page
- return "endSession";
+ // display the log out confirmation page
+ return "logoutConfirmation";
}
}
+ @RequestMapping(value = "/" + URL, method = RequestMethod.POST)
+ public String processLogout(@RequestParam(value = "approve", required = false) String approved,
+ HttpServletRequest request,
+ HttpServletResponse response,
+ HttpSession session,
+ Authentication auth, Model m) {
+
+ String redirectUri = (String) session.getAttribute(REDIRECT_URI_KEY);
+ String state = (String) session.getAttribute(STATE_KEY);
+ ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(CLIENT_KEY);
+
+ if (!Strings.isNullOrEmpty(approved)) {
+ // use approved, perform the logout
+ if (auth != null){
+ new SecurityContextLogoutHandler().logout(request, response, auth);
+ }
+ SecurityContextHolder.getContext().setAuthentication(null);
+ // TODO: hook into other logout post-processing
+ }
+
+ // if the user didn't approve, don't log out but hit the landing page anyway for redirect as needed
+
+
+
+ // if we have a client AND the client has post-logout redirect URIs
+ // registered AND the URI given is in that list, then...
+ if (!Strings.isNullOrEmpty(redirectUri) &&
+ client != null && client.getPostLogoutRedirectUris() != null) {
+
+ if (client.getPostLogoutRedirectUris().contains(redirectUri)) {
+ // TODO: future, add the redirect URI to the model for the display page for an interstitial
+ // m.addAttribute("redirectUri", postLogoutRedirectUri);
+
+ UriComponents uri = UriComponentsBuilder.fromHttpUrl(redirectUri).queryParam("state", state).build();
+
+ return "redirect:" + uri;
+ }
+ }
+
+ // otherwise, return to a nice post-logout landing page
+ return "postLogout";
+ }
+
}