Skip to content

Commit 2aa12fc

Browse files
committed
end session endpoint
1 parent 0c46e7c commit 2aa12fc

File tree

5 files changed

+214
-26
lines changed

5 files changed

+214
-26
lines changed

openid-connect-common/src/main/java/org/mitre/jwt/assertion/impl/SelfAssertionValidator.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.slf4j.Logger;
2626
import org.slf4j.LoggerFactory;
2727
import org.springframework.beans.factory.annotation.Autowired;
28+
import org.springframework.stereotype.Component;
2829

2930
import com.google.common.base.Strings;
3031
import com.nimbusds.jwt.JWT;
@@ -37,6 +38,7 @@
3738
* @author jricher
3839
*
3940
*/
41+
@Component("selfAssertionValidator")
4042
public class SelfAssertionValidator implements AssertionValidator {
4143

4244
private static Logger logger = LoggerFactory.getLogger(SelfAssertionValidator.class);
@@ -62,16 +64,19 @@ public boolean isValid(JWT assertion) {
6264
return false;
6365
}
6466

67+
// make sure the issuer exists
6568
if (Strings.isNullOrEmpty(claims.getIssuer())) {
6669
logger.debug("No issuer for assertion, rejecting");
6770
return false;
6871
}
6972

70-
if (claims.getIssuer().equals(config.getIssuer())) {
73+
// make sure the issuer is us
74+
if (!claims.getIssuer().equals(config.getIssuer())) {
7175
logger.debug("Issuer is not the same as this server, rejecting");
7276
return false;
7377
}
7478

79+
// validate the signature based on our public key
7580
if (jwtService.validateSignature((SignedJWT) assertion)) {
7681
return true;
7782
} else {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
2+
<%@ page import="org.springframework.security.core.AuthenticationException"%>
3+
<%@ page import="org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException"%>
4+
<%@ page import="org.springframework.security.web.WebAttributes"%>
5+
<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%>
6+
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
7+
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
8+
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
9+
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
10+
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
11+
12+
<spring:message code="logout.confirmation.title" var="title"/>
13+
<o:header title="${title}"/>
14+
<o:topbar />
15+
<div class="container main">
16+
17+
<div class="well" style="text-align: center">
18+
19+
<h1><spring:message code="logout.confirmation.header"/></h1>
20+
21+
<form action="${ config.issuer }${ config.issuer.endsWith('/') ? '' : '/' }endsession" method="POST">
22+
23+
<div class="row-fluid">
24+
<div class="span12">
25+
<spring:message code="logout.confirmation.submit" var="authorize_label"/>
26+
<spring:message code="logout.confirmation.deny" var="deny_label"/>
27+
<div>
28+
<c:if test="${ not empty client }">
29+
<!-- display some client information -->
30+
<spring:message code="logout.confirmation.requested"/>&nbsp;
31+
<c:choose>
32+
<c:when test="${empty client.clientName}">
33+
<em><c:out value="${client.clientId}" /></em>
34+
</c:when>
35+
<c:otherwise>
36+
<em><c:out value="${client.clientName}" /></em>
37+
</c:otherwise>
38+
</c:choose>
39+
</c:if>
40+
</div>
41+
<div>
42+
<spring:message code="logout.confirmation.explanation" />
43+
</div>
44+
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
45+
<input name="approve" value="${authorize_label}" type="submit" class="btn btn-info btn-large" />
46+
&nbsp;
47+
<input name="deny" value="${deny_label}" type="submit" class="btn btn-large" />
48+
</div>
49+
</div>
50+
51+
</form>
52+
53+
</div>
54+
</div>
55+
<o:footer/>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
2+
<%@ taglib prefix="authz" uri="http://www.springframework.org/security/tags"%>
3+
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
4+
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%>
5+
<%@ taglib prefix="o" tagdir="/WEB-INF/tags"%>
6+
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
7+
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
8+
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags"%>
9+
10+
<spring:message code="logout.post.title" var="title"/>
11+
<o:header title="${title}"/>
12+
<o:topbar />
13+
<div class="container main">
14+
15+
<div class="well" style="text-align: center">
16+
<h1><spring:message code="logout.post.title" /></h1>
17+
18+
<security:authorize access="hasRole('ROLE_USER')">
19+
<div class=""><spring:message code="logout.post.notLoggedOut" /></div>
20+
</security:authorize>
21+
<security:authorize access="!hasRole('ROLE_USER')">
22+
<div class=""><spring:message code="logout.post.loggedOut" /></div>
23+
</security:authorize>
24+
</div>
25+
</div>
26+
<o:footer/>

openid-connect-server-webapp/src/main/webapp/WEB-INF/views/requestUserCode.jsp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
<div class="well" style="text-align: center">
1818

19-
<h1><spring:message code="device.request_code.header"/>&nbsp;</h1>
19+
<h1><spring:message code="device.request_code.header"/></h1>
2020

2121
<c:if test="${ error != null }">
2222
<c:choose>

openid-connect-server/src/main/java/org/mitre/openid/connect/web/EndSessionEndpoint.java

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,35 @@
2020
import java.text.ParseException;
2121

2222
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.HttpServletResponse;
24+
import javax.servlet.http.HttpSession;
2325

2426
import org.mitre.jwt.assertion.AssertionValidator;
2527
import org.mitre.jwt.assertion.impl.SelfAssertionValidator;
28+
import org.mitre.oauth2.model.ClientDetailsEntity;
29+
import org.mitre.oauth2.service.ClientDetailsEntityService;
30+
import org.mitre.openid.connect.model.UserInfo;
2631
import org.mitre.openid.connect.service.UserInfoService;
2732
import org.slf4j.Logger;
2833
import org.slf4j.LoggerFactory;
2934
import org.springframework.beans.factory.annotation.Autowired;
3035
import org.springframework.security.core.Authentication;
31-
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
36+
import org.springframework.security.core.context.SecurityContextHolder;
37+
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
38+
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
3239
import org.springframework.stereotype.Controller;
3340
import org.springframework.ui.Model;
3441
import org.springframework.web.bind.annotation.RequestMapping;
42+
import org.springframework.web.bind.annotation.RequestMethod;
3543
import org.springframework.web.bind.annotation.RequestParam;
44+
import org.springframework.web.util.UriComponents;
45+
import org.springframework.web.util.UriComponentsBuilder;
46+
import org.springframework.web.util.UriUtils;
3647

3748
import com.google.common.base.Strings;
49+
import com.google.common.collect.Iterables;
3850
import com.nimbusds.jwt.JWT;
51+
import com.nimbusds.jwt.JWTClaimsSet;
3952
import com.nimbusds.jwt.JWTParser;
4053

4154
/**
@@ -47,48 +60,137 @@ public class EndSessionEndpoint {
4760

4861
public static final String URL = "endsession";
4962

63+
private static final String CLIENT_KEY = "client";
64+
private static final String STATE_KEY = "state";
65+
private static final String REDIRECT_URI_KEY = "redirectUri";
66+
5067
private static Logger logger = LoggerFactory.getLogger(EndSessionEndpoint.class);
5168

52-
private AssertionValidator validator = new SelfAssertionValidator();
69+
@Autowired
70+
private SelfAssertionValidator validator;
5371

5472
@Autowired
5573
private UserInfoService userInfoService;
5674

57-
@RequestMapping(value = "/" + URL)
75+
@Autowired
76+
private ClientDetailsEntityService clientService;
77+
78+
@RequestMapping(value = "/" + URL, method = RequestMethod.GET)
5879
public String endSession(@RequestParam (value = "id_token_hint", required = false) String idTokenHint,
5980
@RequestParam (value = "post_logout_redirect_uri", required = false) String postLogoutRedirectUri,
60-
@RequestParam (value = "state", required = false) String state,
81+
@RequestParam (value = STATE_KEY, required = false) String state,
6182
HttpServletRequest request,
83+
HttpServletResponse response,
84+
HttpSession session,
6285
Authentication auth, Model m) {
6386

87+
// conditionally filled variables
88+
JWTClaimsSet idTokenClaims = null; // pulled from the parsed and validated ID token
89+
ClientDetailsEntity client = null; // pulled from ID token's audience field
90+
91+
if (!Strings.isNullOrEmpty(postLogoutRedirectUri)) {
92+
session.setAttribute(REDIRECT_URI_KEY, postLogoutRedirectUri);
93+
}
94+
if (!Strings.isNullOrEmpty(state)) {
95+
session.setAttribute(STATE_KEY, state);
96+
}
97+
98+
// parse the ID token hint to see if it's valid
99+
if (!Strings.isNullOrEmpty(idTokenHint)) {
100+
try {
101+
JWT idToken = JWTParser.parse(idTokenHint);
102+
103+
if (validator.isValid(idToken)) {
104+
// we issued this ID token, figure out who it's for
105+
idTokenClaims = idToken.getJWTClaimsSet();
106+
107+
String clientId = Iterables.getOnlyElement(idTokenClaims.getAudience());
108+
109+
client = clientService.loadClientByClientId(clientId);
110+
111+
// save a reference in the session for us to pick up later
112+
//session.setAttribute("endSession_idTokenHint_claims", idTokenClaims);
113+
session.setAttribute(CLIENT_KEY, client);
114+
}
115+
} catch (ParseException e) {
116+
// it's not a valid ID token, ignore it
117+
logger.debug("Invalid id token hint", e);
118+
} catch (InvalidClientException e) {
119+
// couldn't find the client, ignore it
120+
logger.debug("Invalid client", e);
121+
}
122+
}
123+
64124
// are we logged in or not?
65125
if (auth == null || !request.isUserInRole("ROLE_USER")) {
66-
// we're not logged in, process the logout
67-
return null;
126+
// we're not logged in anyway, process the final redirect bits if needed
127+
return processLogout(null, request, response, session, auth, m);
68128
} else {
69129
// we are logged in, need to prompt the user before we log out
70130

71-
// parse the ID token hint to see if it's valid
72-
if (!Strings.isNullOrEmpty(idTokenHint)) {
73-
try {
74-
JWT idToken = JWTParser.parse(idTokenHint);
75-
76-
if (validator.isValid(idToken)) {
77-
// we issued this ID token, figure out who it's for
78-
String subject = idToken.getJWTClaimsSet().getSubject();
79-
80-
userInfoService.getByUsername(subject);
81-
82-
}
83-
} catch (ParseException e) {
84-
85-
}
86-
131+
// see who the current user is
132+
UserInfo ui = userInfoService.getByUsername(auth.getName());
133+
134+
if (idTokenClaims != null) {
135+
String subject = idTokenClaims.getSubject();
136+
// see if the current user is the same as the one in the ID token
137+
// TODO: should we do anything different in these cases?
138+
if (!Strings.isNullOrEmpty(subject) && subject.equals(ui.getSub())) {
139+
// it's the same user
140+
} else {
141+
// it's not the same user
142+
}
87143
}
144+
145+
m.addAttribute("client", client);
146+
m.addAttribute("idToken", idTokenClaims);
88147

89-
// display the end session page
90-
return "endSession";
148+
// display the log out confirmation page
149+
return "logoutConfirmation";
91150
}
92151
}
93152

153+
@RequestMapping(value = "/" + URL, method = RequestMethod.POST)
154+
public String processLogout(@RequestParam(value = "approve", required = false) String approved,
155+
HttpServletRequest request,
156+
HttpServletResponse response,
157+
HttpSession session,
158+
Authentication auth, Model m) {
159+
160+
String redirectUri = (String) session.getAttribute(REDIRECT_URI_KEY);
161+
String state = (String) session.getAttribute(STATE_KEY);
162+
ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(CLIENT_KEY);
163+
164+
if (!Strings.isNullOrEmpty(approved)) {
165+
// use approved, perform the logout
166+
if (auth != null){
167+
new SecurityContextLogoutHandler().logout(request, response, auth);
168+
}
169+
SecurityContextHolder.getContext().setAuthentication(null);
170+
// TODO: hook into other logout post-processing
171+
}
172+
173+
// if the user didn't approve, don't log out but hit the landing page anyway for redirect as needed
174+
175+
176+
177+
// if we have a client AND the client has post-logout redirect URIs
178+
// registered AND the URI given is in that list, then...
179+
if (!Strings.isNullOrEmpty(redirectUri) &&
180+
client != null && client.getPostLogoutRedirectUris() != null) {
181+
182+
if (client.getPostLogoutRedirectUris().contains(redirectUri)) {
183+
// TODO: future, add the redirect URI to the model for the display page for an interstitial
184+
// m.addAttribute("redirectUri", postLogoutRedirectUri);
185+
186+
UriComponents uri = UriComponentsBuilder.fromHttpUrl(redirectUri).queryParam("state", state).build();
187+
188+
return "redirect:" + uri;
189+
}
190+
}
191+
192+
// otherwise, return to a nice post-logout landing page
193+
return "postLogout";
194+
}
195+
94196
}

0 commit comments

Comments
 (0)