20
20
import java .text .ParseException ;
21
21
22
22
import javax .servlet .http .HttpServletRequest ;
23
+ import javax .servlet .http .HttpServletResponse ;
24
+ import javax .servlet .http .HttpSession ;
23
25
24
26
import org .mitre .jwt .assertion .AssertionValidator ;
25
27
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 ;
26
31
import org .mitre .openid .connect .service .UserInfoService ;
27
32
import org .slf4j .Logger ;
28
33
import org .slf4j .LoggerFactory ;
29
34
import org .springframework .beans .factory .annotation .Autowired ;
30
35
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 ;
32
39
import org .springframework .stereotype .Controller ;
33
40
import org .springframework .ui .Model ;
34
41
import org .springframework .web .bind .annotation .RequestMapping ;
42
+ import org .springframework .web .bind .annotation .RequestMethod ;
35
43
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 ;
36
47
37
48
import com .google .common .base .Strings ;
49
+ import com .google .common .collect .Iterables ;
38
50
import com .nimbusds .jwt .JWT ;
51
+ import com .nimbusds .jwt .JWTClaimsSet ;
39
52
import com .nimbusds .jwt .JWTParser ;
40
53
41
54
/**
@@ -47,48 +60,137 @@ public class EndSessionEndpoint {
47
60
48
61
public static final String URL = "endsession" ;
49
62
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
+
50
67
private static Logger logger = LoggerFactory .getLogger (EndSessionEndpoint .class );
51
68
52
- private AssertionValidator validator = new SelfAssertionValidator ();
69
+ @ Autowired
70
+ private SelfAssertionValidator validator ;
53
71
54
72
@ Autowired
55
73
private UserInfoService userInfoService ;
56
74
57
- @ RequestMapping (value = "/" + URL )
75
+ @ Autowired
76
+ private ClientDetailsEntityService clientService ;
77
+
78
+ @ RequestMapping (value = "/" + URL , method = RequestMethod .GET )
58
79
public String endSession (@ RequestParam (value = "id_token_hint" , required = false ) String idTokenHint ,
59
80
@ 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 ,
61
82
HttpServletRequest request ,
83
+ HttpServletResponse response ,
84
+ HttpSession session ,
62
85
Authentication auth , Model m ) {
63
86
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
+
64
124
// are we logged in or not?
65
125
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 ) ;
68
128
} else {
69
129
// we are logged in, need to prompt the user before we log out
70
130
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
+ }
87
143
}
144
+
145
+ m .addAttribute ("client" , client );
146
+ m .addAttribute ("idToken" , idTokenClaims );
88
147
89
- // display the end session page
90
- return "endSession " ;
148
+ // display the log out confirmation page
149
+ return "logoutConfirmation " ;
91
150
}
92
151
}
93
152
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
+
94
196
}
0 commit comments