2020import  java .text .ParseException ;
2121
2222import  javax .servlet .http .HttpServletRequest ;
23+ import  javax .servlet .http .HttpServletResponse ;
24+ import  javax .servlet .http .HttpSession ;
2325
2426import  org .mitre .jwt .assertion .AssertionValidator ;
2527import  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 ;
2631import  org .mitre .openid .connect .service .UserInfoService ;
2732import  org .slf4j .Logger ;
2833import  org .slf4j .LoggerFactory ;
2934import  org .springframework .beans .factory .annotation .Autowired ;
3035import  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 ;
3239import  org .springframework .stereotype .Controller ;
3340import  org .springframework .ui .Model ;
3441import  org .springframework .web .bind .annotation .RequestMapping ;
42+ import  org .springframework .web .bind .annotation .RequestMethod ;
3543import  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
3748import  com .google .common .base .Strings ;
49+ import  com .google .common .collect .Iterables ;
3850import  com .nimbusds .jwt .JWT ;
51+ import  com .nimbusds .jwt .JWTClaimsSet ;
3952import  com .nimbusds .jwt .JWTParser ;
4053
4154/** 
@@ -47,48 +60,137 @@ public class EndSessionEndpoint {
4760
4861public  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+ 
5067private  static  Logger  logger  = LoggerFactory .getLogger (EndSessionEndpoint .class );
5168
52- private  AssertionValidator  validator  = new  SelfAssertionValidator ();
69+ @ Autowired 
70+ private  SelfAssertionValidator  validator ;
5371
5472@ Autowired 
5573private  UserInfoService  userInfoService ;
5674
57- @ RequestMapping (value  = "/"  + URL )
75+ @ Autowired 
76+ private  ClientDetailsEntityService  clientService ;
77+ 
78+ @ RequestMapping (value  = "/"  + URL , method  = RequestMethod .GET )
5879public  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? 
65125if  (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