Skip to content

Commit b4ed1b8

Browse files
committed
SAML (AD) ensure back button does not cause errors
1 parent 527fb66 commit b4ed1b8

File tree

3 files changed

+102
-26
lines changed

3 files changed

+102
-26
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* ContainerProxy
3+
*
4+
* Copyright (C) 2016-2020 Open Analytics
5+
*
6+
* ===========================================================================
7+
*
8+
* This program is free software: you can redistribute it and/or modify
9+
* it under the terms of the Apache License as published by
10+
* The Apache Software Foundation, either version 2 of the License, or
11+
* (at your option) any later version.
12+
*
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* Apache License for more details.
17+
*
18+
* You should have received a copy of the Apache License
19+
* along with this program. If not, see <http://www.apache.org/licenses/>
20+
*/
21+
package eu.openanalytics.containerproxy.auth.impl.saml;
22+
23+
24+
import org.springframework.security.authentication.AnonymousAuthenticationToken;
25+
import org.springframework.security.core.Authentication;
26+
import org.springframework.security.core.context.SecurityContextHolder;
27+
import org.springframework.web.filter.GenericFilterBean;
28+
29+
import javax.servlet.*;
30+
import java.io.IOException;
31+
32+
/**
33+
* A filter that sets a request attribute when the user is already logged in.
34+
* This is used to know whether a user was already logged in when performing a SAML SSO request.
35+
* If so, the user probably has clicked the back button and should therefore be redirected to the main page.
36+
*
37+
* Note: we don't redirect to the main page here, because that seems to be to intrusive. There may be good reasons
38+
* to land on the SSO page again.
39+
*/
40+
public class AlreadyLoggedInFilter extends GenericFilterBean {
41+
42+
public static final String REQ_PROP_AUTH_BEFORE_SSO = "SP_REQ_PROP_AUTH_BEFORE_SSO";
43+
44+
@Override
45+
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
46+
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
47+
if (auth != null && !(auth instanceof AnonymousAuthenticationToken)) {
48+
request.setAttribute(REQ_PROP_AUTH_BEFORE_SSO, "true");
49+
}
50+
chain.doFilter(request, response);
51+
}
52+
53+
}
54+

src/main/java/eu/openanalytics/containerproxy/auth/impl/saml/AuthenticationFailureHandler.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,69 @@
2020
*/
2121
package eu.openanalytics.containerproxy.auth.impl.saml;
2222

23+
import org.opensaml.common.SAMLException;
24+
import org.springframework.security.core.Authentication;
2325
import org.springframework.security.core.AuthenticationException;
26+
import org.springframework.security.core.context.SecurityContext;
27+
import org.springframework.security.core.context.SecurityContextHolder;
2428
import org.springframework.security.saml.SAMLStatusException;
2529
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
2630
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
2731

2832
import javax.servlet.ServletException;
2933
import javax.servlet.http.HttpServletRequest;
3034
import javax.servlet.http.HttpServletResponse;
35+
import javax.servlet.http.HttpSession;
3136
import java.io.IOException;
3237

33-
public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
38+
import static eu.openanalytics.containerproxy.auth.impl.saml.AlreadyLoggedInFilter.REQ_PROP_AUTH_BEFORE_SSO;
39+
40+
public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
3441

3542
public void onAuthenticationFailure(HttpServletRequest request,
3643
HttpServletResponse response, AuthenticationException exception)
3744
throws IOException, ServletException {
3845

39-
4046
if (exception.getCause() instanceof SAMLStatusException) {
4147
SAMLStatusException samlException = (SAMLStatusException) exception.getCause();
4248

4349
if (samlException.getStatusCode().equals("urn:oasis:names:tc:SAML:2.0:status:RequestDenied")) {
4450
response.sendRedirect(ServletUriComponentsBuilder.fromCurrentContextPath().path("/app-access-denied").build().toUriString());
4551
return;
4652
}
53+
54+
} else if (exception.getCause() instanceof SAMLException) {
55+
SAMLException samlException = (SAMLException) exception.getCause();
56+
57+
if (isOrWasAuthenticated(request) && (
58+
samlException.getMessage().startsWith("Response issue time is either too old or with date in the future")
59+
|| samlException.getMessage().startsWith("InResponseToField of the Response doesn't correspond to sent message"))
60+
|| samlException.getMessage().equals("Unsupported request")) {
61+
response.sendRedirect(ServletUriComponentsBuilder.fromCurrentContextPath().path("/").build().toUriString());
62+
}
4763
}
4864

4965
super.onAuthenticationFailure(request, response, exception);
5066
}
5167

68+
private boolean isOrWasAuthenticated(HttpServletRequest request) {
69+
if (request.getAttribute(REQ_PROP_AUTH_BEFORE_SSO).equals("true")) {
70+
// Before doing a SSO request we check whether the user is authenticated, if so we set the SP_REQ_PROP_AUTH_BEFORE_SSO
71+
// property. If the auth failed, the Spring SecurityContext is cleared and thus we cannot use that to
72+
// check whether the user is authenticated.
73+
return true;
74+
}
75+
76+
HttpSession session = request.getSession();
77+
Object obj = session.getAttribute("SPRING_SECURITY_CONTEXT");
78+
if (obj instanceof SecurityContext) {
79+
SecurityContext ctx = (SecurityContext) obj;
80+
Authentication auth = ctx.getAuthentication();
81+
// in some cases the session may still contain the security context so we fallback to that
82+
return auth != null && auth.getPrincipal() != null;
83+
}
84+
85+
return false;
86+
}
87+
5288
}

src/main/java/eu/openanalytics/containerproxy/auth/impl/saml/SAMLConfiguration.java

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,17 @@
2121
package eu.openanalytics.containerproxy.auth.impl.saml;
2222

2323
import eu.openanalytics.containerproxy.auth.UserLogoutHandler;
24-
import java.util.ArrayList;
25-
import java.util.Arrays;
26-
import java.util.Collection;
27-
import java.util.HashMap;
28-
import java.util.List;
29-
import java.util.Map;
30-
import java.util.Timer;
31-
32-
import javax.inject.Inject;
33-
3424
import eu.openanalytics.containerproxy.auth.impl.SAMLAuthenticationBackend;
3525
import org.apache.commons.httpclient.HttpClient;
3626
import org.apache.logging.log4j.LogManager;
3727
import org.apache.logging.log4j.Logger;
3828
import org.apache.velocity.app.VelocityEngine;
39-
import org.opensaml.saml2.core.Attribute;
4029
import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
4130
import org.opensaml.saml2.metadata.provider.MetadataProvider;
4231
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
4332
import org.opensaml.util.resource.ResourceException;
44-
import org.opensaml.xml.XMLObject;
4533
import org.opensaml.xml.parse.StaticBasicParserPool;
4634
import org.opensaml.xml.parse.XMLParserException;
47-
import org.opensaml.xml.schema.XSAny;
48-
import org.opensaml.xml.schema.XSString;
4935
import org.springframework.beans.factory.annotation.Qualifier;
5036
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
5137
import org.springframework.context.annotation.Bean;
@@ -60,7 +46,6 @@
6046
import org.springframework.security.core.userdetails.User;
6147
import org.springframework.security.core.userdetails.UsernameNotFoundException;
6248
import org.springframework.security.saml.*;
63-
import org.springframework.security.saml.context.SAMLContextProvider;
6449
import org.springframework.security.saml.context.SAMLContextProviderImpl;
6550
import org.springframework.security.saml.key.EmptyKeyManager;
6651
import org.springframework.security.saml.key.JKSKeyManager;
@@ -74,14 +59,7 @@
7459
import org.springframework.security.saml.processor.SAMLProcessorImpl;
7560
import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
7661
import org.springframework.security.saml.util.VelocityFactory;
77-
import org.springframework.security.saml.websso.SingleLogoutProfile;
78-
import org.springframework.security.saml.websso.SingleLogoutProfileImpl;
79-
import org.springframework.security.saml.websso.WebSSOProfile;
80-
import org.springframework.security.saml.websso.WebSSOProfileConsumer;
81-
import org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl;
82-
import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl;
83-
import org.springframework.security.saml.websso.WebSSOProfileImpl;
84-
import org.springframework.security.saml.websso.WebSSOProfileOptions;
62+
import org.springframework.security.saml.websso.*;
8563
import org.springframework.security.web.DefaultSecurityFilterChain;
8664
import org.springframework.security.web.FilterChainProxy;
8765
import org.springframework.security.web.SecurityFilterChain;
@@ -92,6 +70,9 @@
9270
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
9371
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
9472

73+
import javax.inject.Inject;
74+
import java.util.*;
75+
9576
@Configuration
9677
@ConditionalOnProperty(name="proxy.authentication", havingValue="saml")
9778
public class SAMLConfiguration {
@@ -326,6 +307,11 @@ public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
326307
return samlWebSSOProcessingFilter;
327308
}
328309

310+
@Bean
311+
public AlreadyLoggedInFilter alreadyLoggedInFilter() {
312+
return new AlreadyLoggedInFilter();
313+
}
314+
329315
@Bean
330316
public WebSSOProfileConsumer webSSOprofileConsumer() {
331317
return new WebSSOProfileConsumerImpl();
@@ -342,7 +328,7 @@ public SAMLFilterSet samlFilter() throws Exception {
342328
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint()));
343329
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter()));
344330
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"), samlLogoutProcessingFilter()));
345-
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), samlWebSSOProcessingFilter()));
331+
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), alreadyLoggedInFilter(), samlWebSSOProcessingFilter()));
346332
return new SAMLFilterSet(chains);
347333
}
348334

0 commit comments

Comments
 (0)