20
20
*/
21
21
package eu .openanalytics .containerproxy .auth .impl .saml ;
22
22
23
+ import eu .openanalytics .containerproxy .auth .UserLogoutHandler ;
23
24
import java .util .ArrayList ;
24
25
import java .util .Arrays ;
25
26
import java .util .Collection ;
31
32
import javax .inject .Inject ;
32
33
33
34
import org .apache .commons .httpclient .HttpClient ;
35
+ import org .apache .logging .log4j .LogManager ;
36
+ import org .apache .logging .log4j .Logger ;
34
37
import org .apache .velocity .app .VelocityEngine ;
38
+ import org .opensaml .saml2 .core .Attribute ;
35
39
import org .opensaml .saml2 .metadata .provider .HTTPMetadataProvider ;
36
40
import org .opensaml .saml2 .metadata .provider .MetadataProvider ;
37
41
import org .opensaml .saml2 .metadata .provider .MetadataProviderException ;
38
42
import org .opensaml .util .resource .ResourceException ;
43
+ import org .opensaml .xml .XMLObject ;
39
44
import org .opensaml .xml .parse .StaticBasicParserPool ;
40
45
import org .opensaml .xml .parse .XMLParserException ;
46
+ import org .opensaml .xml .schema .XSAny ;
47
+ import org .opensaml .xml .schema .XSString ;
41
48
import org .springframework .beans .factory .annotation .Qualifier ;
42
49
import org .springframework .boot .autoconfigure .condition .ConditionalOnProperty ;
43
50
import org .springframework .context .annotation .Bean ;
51
58
import org .springframework .security .core .authority .SimpleGrantedAuthority ;
52
59
import org .springframework .security .core .userdetails .User ;
53
60
import org .springframework .security .core .userdetails .UsernameNotFoundException ;
54
- import org .springframework .security .saml .SAMLAuthenticationProvider ;
55
- import org .springframework .security .saml .SAMLBootstrap ;
56
- import org .springframework .security .saml .SAMLCredential ;
57
- import org .springframework .security .saml .SAMLEntryPoint ;
58
- import org .springframework .security .saml .SAMLProcessingFilter ;
61
+ import org .springframework .security .saml .*;
59
62
import org .springframework .security .saml .context .SAMLContextProvider ;
60
63
import org .springframework .security .saml .context .SAMLContextProviderImpl ;
61
64
import org .springframework .security .saml .key .EmptyKeyManager ;
70
73
import org .springframework .security .saml .processor .SAMLProcessorImpl ;
71
74
import org .springframework .security .saml .userdetails .SAMLUserDetailsService ;
72
75
import org .springframework .security .saml .util .VelocityFactory ;
76
+ import org .springframework .security .saml .websso .SingleLogoutProfile ;
77
+ import org .springframework .security .saml .websso .SingleLogoutProfileImpl ;
73
78
import org .springframework .security .saml .websso .WebSSOProfile ;
74
79
import org .springframework .security .saml .websso .WebSSOProfileConsumer ;
75
80
import org .springframework .security .saml .websso .WebSSOProfileConsumerHoKImpl ;
81
86
import org .springframework .security .web .SecurityFilterChain ;
82
87
import org .springframework .security .web .authentication .SavedRequestAwareAuthenticationSuccessHandler ;
83
88
import org .springframework .security .web .authentication .SimpleUrlAuthenticationFailureHandler ;
89
+ import org .springframework .security .web .authentication .logout .LogoutHandler ;
90
+ import org .springframework .security .web .authentication .logout .SecurityContextLogoutHandler ;
91
+ import org .springframework .security .web .authentication .logout .SimpleUrlLogoutSuccessHandler ;
84
92
import org .springframework .security .web .util .matcher .AntPathRequestMatcher ;
85
93
86
94
@ Configuration
87
95
@ ConditionalOnProperty (name ="proxy.authentication" , havingValue ="saml" )
88
96
public class SAMLConfiguration {
89
97
90
98
private static final String DEFAULT_NAME_ATTRIBUTE = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" ;
91
-
99
+
100
+ private static final String PROP_LOG_ATTRIBUTES = "proxy.saml.log-attributes" ;
101
+ private static final String PROP_FORCE_AUTHN = "proxy.saml.force-authn" ;
102
+ private static final String PROP_KEYSTORE = "proxy.saml.keystore" ;
103
+ private static final String PROP_ENCRYPTION_CERT_NAME = "proxy.saml.encryption-cert-name" ;
104
+ private static final String PROP_ENCRYPTION_CERT_PASSWORD = "proxy.saml.encryption-cert-password" ;
105
+ private static final String PROP_ENCRYPTION_KEYSTORE_PASSWORD = "proxy.saml.keystore-password" ;
106
+ private static final String PROP_APP_ENTITY_ID = "proxy.saml.app-entity-id" ;
107
+ private static final String PROP_BASE_URL = "proxy.saml.app-base-url" ;
108
+ private static final String PROP_METADATA_URL = "proxy.saml.idp-metadata-url" ;
109
+
92
110
@ Inject
93
111
private Environment environment ;
94
112
95
113
@ Inject
96
114
@ Lazy
97
115
private AuthenticationManager authenticationManager ;
116
+
117
+ @ Inject
118
+ private UserLogoutHandler userLogoutHandler ;
98
119
99
120
@ Bean
100
121
public SAMLEntryPoint samlEntryPoint () {
101
122
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint ();
102
123
samlEntryPoint .setDefaultProfileOptions (defaultWebSSOProfileOptions ());
103
124
return samlEntryPoint ;
104
125
}
126
+
127
+ @ Bean
128
+ public SingleLogoutProfile logoutProfile () {
129
+ return new SingleLogoutProfileImpl ();
130
+ }
131
+
132
+ @ Bean
133
+ public SAMLLogoutFilter samlLogoutFilter () {
134
+ return new SAMLLogoutFilter (successLogoutHandler (),
135
+ new LogoutHandler []{userLogoutHandler , securityContextLogoutHandler ()},
136
+ new LogoutHandler []{userLogoutHandler , securityContextLogoutHandler ()});
137
+ }
138
+
139
+ /**
140
+ * Filter responsible for the `/saml/SingleLogout` endpoint. This makes it possible for users to logout in the IDP
141
+ * or any other application and get automatically logged out in ShinyProxy as well.
142
+ */
143
+ @ Bean
144
+ public SAMLLogoutProcessingFilter samlLogoutProcessingFilter () {
145
+ return new SAMLLogoutProcessingFilter (successLogoutHandler (),
146
+ securityContextLogoutHandler ());
147
+ }
148
+
149
+ @ Bean
150
+ public SecurityContextLogoutHandler securityContextLogoutHandler () {
151
+ SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler ();
152
+ logoutHandler .setInvalidateHttpSession (true );
153
+ logoutHandler .setClearAuthentication (true );
154
+ return logoutHandler ;
155
+ }
156
+
157
+ @ Bean
158
+ public SimpleUrlLogoutSuccessHandler successLogoutHandler () {
159
+ SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler ();
160
+ successLogoutHandler .setDefaultTargetUrl ("/" );
161
+ return successLogoutHandler ;
162
+ }
105
163
106
164
@ Bean
107
165
public WebSSOProfileOptions defaultWebSSOProfileOptions () {
108
166
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions ();
109
167
webSSOProfileOptions .setIncludeScoping (false );
110
- webSSOProfileOptions .setForceAuthN (Boolean .valueOf (environment .getProperty ("proxy.saml.force-authn" , "false" )));
168
+ webSSOProfileOptions .setForceAuthN (Boolean .valueOf (environment .getProperty (PROP_FORCE_AUTHN , "false" )));
111
169
return webSSOProfileOptions ;
112
170
}
113
171
@@ -123,13 +181,13 @@ public WebSSOProfile webSSOprofile() {
123
181
124
182
@ Bean
125
183
public KeyManager keyManager () {
126
- String keystore = environment .getProperty ("proxy.saml.keystore" );
184
+ String keystore = environment .getProperty (PROP_KEYSTORE );
127
185
if (keystore == null || keystore .isEmpty ()) {
128
186
return new EmptyKeyManager ();
129
187
} else {
130
- String certName = environment .getProperty ("proxy.saml.encryption-cert-name" );
131
- String certPW = environment .getProperty ("proxy.saml.encryption-cert-password" );
132
- String keystorePW = environment .getProperty ("proxy.saml.keystore-password" , certPW );
188
+ String certName = environment .getProperty (PROP_ENCRYPTION_CERT_NAME );
189
+ String certPW = environment .getProperty (PROP_ENCRYPTION_CERT_PASSWORD );
190
+ String keystorePW = environment .getProperty (PROP_ENCRYPTION_KEYSTORE_PASSWORD , certPW );
133
191
134
192
Resource keystoreFile = new FileSystemResource (keystore );
135
193
Map <String , String > passwords = new HashMap <>();
@@ -193,8 +251,8 @@ public MetadataDisplayFilter metadataDisplayFilter() throws MetadataProviderExce
193
251
194
252
@ Bean
195
253
public MetadataGenerator metadataGenerator () {
196
- String appEntityId = environment .getProperty ("proxy.saml.app-entity-id" );
197
- String appBaseURL = environment .getProperty ("proxy.saml.app-base-url" );
254
+ String appEntityId = environment .getProperty (PROP_APP_ENTITY_ID );
255
+ String appBaseURL = environment .getProperty (PROP_BASE_URL );
198
256
199
257
MetadataGenerator metadataGenerator = new MetadataGenerator ();
200
258
metadataGenerator .setEntityId (appEntityId );
@@ -215,7 +273,7 @@ public ExtendedMetadata extendedMetadata() {
215
273
216
274
@ Bean
217
275
public ExtendedMetadataDelegate idpMetadata () throws MetadataProviderException , ResourceException {
218
- String metadataURL = environment .getProperty ("proxy.saml.idp-metadata-url" );
276
+ String metadataURL = environment .getProperty (PROP_METADATA_URL );
219
277
220
278
Timer backgroundTaskTimer = new Timer (true );
221
279
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider (backgroundTaskTimer , new HttpClient (), metadataURL ); httpMetadataProvider .setParserPool (parserPool ());
@@ -281,21 +339,32 @@ public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
281
339
public SAMLFilterSet samlFilter () throws Exception {
282
340
List <SecurityFilterChain > chains = new ArrayList <SecurityFilterChain >();
283
341
chains .add (new DefaultSecurityFilterChain (new AntPathRequestMatcher ("/saml/login/**" ), samlEntryPoint ()));
342
+ chains .add (new DefaultSecurityFilterChain (new AntPathRequestMatcher ("/saml/logout/**" ), samlLogoutFilter ()));
343
+ chains .add (new DefaultSecurityFilterChain (new AntPathRequestMatcher ("/saml/SingleLogout/**" ), samlLogoutProcessingFilter ()));
284
344
chains .add (new DefaultSecurityFilterChain (new AntPathRequestMatcher ("/saml/SSO/**" ), samlWebSSOProcessingFilter ()));
285
345
return new SAMLFilterSet (chains );
286
346
}
287
347
348
+ private final Logger log = LogManager .getLogger (getClass ());
349
+
350
+
351
+
288
352
@ Bean
289
353
public SAMLAuthenticationProvider samlAuthenticationProvider () {
290
354
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider ();
291
355
samlAuthenticationProvider .setUserDetails (new SAMLUserDetailsService () {
292
356
@ Override
293
357
public Object loadUserBySAML (SAMLCredential credential ) throws UsernameNotFoundException {
294
- String nameAttribute = environment .getProperty ("proxy.saml.name-attribute" , DEFAULT_NAME_ATTRIBUTE );
295
- String nameValue = credential .getAttributeAsString (nameAttribute );
296
- if (nameValue == null ) throw new UsernameNotFoundException ("Name attribute missing from SAML assertion: " + nameAttribute );
297
-
298
- List <GrantedAuthority > auth = new ArrayList <>();
358
+
359
+ if (Boolean .parseBoolean (environment .getProperty (PROP_LOG_ATTRIBUTES , "false" ))) {
360
+ AttributeUtils .logAttributes (log , credential );
361
+ }
362
+
363
+ String nameAttribute = environment .getProperty ("proxy.saml.name-attribute" , DEFAULT_NAME_ATTRIBUTE );
364
+ String nameValue = credential .getAttributeAsString (nameAttribute );
365
+ if (nameValue == null ) throw new UsernameNotFoundException ("Name attribute missing from SAML assertion: " + nameAttribute );
366
+
367
+ List <GrantedAuthority > auth = new ArrayList <>();
299
368
String rolesAttribute = environment .getProperty ("proxy.saml.roles-attribute" );
300
369
if (rolesAttribute != null && !rolesAttribute .trim ().isEmpty ()) {
301
370
String [] roles = credential .getAttributeAsStringArray (rolesAttribute );
0 commit comments