DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Spring OAuth Server: Token Claim Customization
  • Identity Federation and SSO: The Fundamentals
  • What D'Hack Is DPoP?
  • How to Implement Two-Factor Authentication in a Spring Boot OAuth Server? Part 1: Configuration

Trending

  • Run Gemma 4 on Your Laptop: A Hands-On Guide to Google's Latest Open Multimodal LLM
  • Your AI Agent Tests Are Passing, But Your Agent Is Still Broken
  • Bringing Intelligence Closer to the Source: Why Real-Time Processing is the Heart of Edge AI
  • Feature Flag Debt: Performance Impact in Enterprise Applications
  1. DZone
  2. Coding
  3. Frameworks
  4. Diving Into SSO With Spring SAML and SSOCircle

Diving Into SSO With Spring SAML and SSOCircle

How to configure Spring SAML on Spring Boot + Thymeleaf project via java configuration and connecting it to SSOCircle Identity Provider.

By 
Ion Pascari user avatar
Ion Pascari
·
Dec. 02, 20 · Tutorial
Likes (6)
Comment
Save
Tweet
Share
14.7K Views

Join the DZone community and get the full member experience.

Join For Free

Recently I've been working on integrating SSO in an old Spring project and for that very reason, I created a small PoC to get my hands on Spring SAML and all its subtleties. In this article, I am going to share my experience of adding Spring SAML to a Spring MVC project and integrating it with SSOCircle. Spring did a wonderful job of documenting everything SAML related, setting up the .xml configuration, and even SSOCircle integration, but there are places where you could still fall. So before reading this article make sure to read the whole Spring SAML documentation and SAML documentation itself. 

I would assume that you already are familiar with the concept of SSO and its details, but for the sake of a better understanding of the article and the keywords that I'm going to use I will briefly describe the basics of SSO.

What Is SSO?

Single sign-on (SSO) is an authentication scheme that allows a user to log in with a single ID and password to any of several related, yet independent, software systems - Wikipedia

So there are several related, yet independent, software systems – service providers and an entity that checks the user's identity – identity provider.  Then there is a secure way for these 2 to communicate and one of the most-used security languages that define the relationship between identity providers and service providers is Security Assertion Markup Language (SAML).

The Security Assertion Markup Language (SAML), developed by the Security Services Technical Committee of OASIS, is an XML-based framework for communicating user authentication, entitlement, and attribute information. As its name suggests, SAML allows business entities to make assertions regarding the identity, attributes, and entitlements of a subject (an entity that is often a human user) to other entities, such as a partner company or another enterprise application. - OASIS

There are:

  • Service Provider (SP) – the application providing services
  • Identity Provider (IdP) – the entity providing identities
  • SAML – the language of communication between SP and IdP
    • SAML Request – authentication request (AuthNRequest)
    • SAML Response – assertion of the authenticated user (AuthNResponse) 
    • Assertion - a package of information that supplies zero or more statements made by a SAML authority

Let's see a brief overview of how everything assembles

SAML metadata

Having a user named John Doe:

  1. John Doe via the browser tries to access the SP
  2. SP redirects to the IdP for the identity check/authentication
  3. IdP returns the SAML response to the SP via the browser's redirection
  4. Based on the response SP sends the requested resource or handles the error

Now for this communication to work IdP and SP need to know about each other, and this is done via the SAML metadata. SAML Metadata is an XML document containing various important information about IdP/SP. Typically the SP will generate one metadata containing URLs, IDs, a public key, and other SAML-related information for IdP to import it and the same goes for the IdP – SP  will import IdP's metadata.

Now that we have some understanding of what is going on, let's take a quick look at the application. The application is a simple task manager (things to do manager) named taskr on Spring Boot with Thymeleaf. taskr enables its user to create/delete and view tasks, you can check it on GitHub. And then there is SSOCircle which is the IdP for taskr. 

SSO circle

Let's take a look at the SAML configuration of the taskr 

Java
 




xxxxxxxxxx
1
284


 
1
package com.evil.inc.taskrssosaml.config;
2

          
3
//imports omitted
4

          
5
@Configuration
6
@EnableWebSecurity
7
@ComponentScan(basePackages = {"org.springframework.security.saml", "com.evil.inc.taskrssosaml"})
8
public class SAMLSecurityConfig extends WebSecurityConfigurerAdapter {
9

          
10
  
11
    public static final int MAX_AUTHENTICATION_AGE = 28800;
12

          
13
  
14
    private final String idpMetadataUrl = "https://idp.ssocircle.com/idp-meta.xml";
15
    @Value("${taskr.entityId}")
16
    private String entityId;
17
    @Value("${taskr.entityBaseUrl}")
18
    private String entityBaseUrl;
19

          
20
  
21
    @Bean
22
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
23
        return new PropertySourcesPlaceholderConfigurer();
24
    }
25

          
26
  
27
    @Bean
28
    public static SAMLBootstrap samlBootstrap() {
29
        return new CustomSAMLBootstrap();
30
    }
31

          
32
  
33
    @Bean
34
    public SAMLContextProviderImpl contextProvider() {
35
        SAMLContextProviderImpl samlContextProvider = new SAMLContextProviderImpl();
36
        samlContextProvider.setStorageFactory(emptyStorageFactory());
37
        return samlContextProvider;
38
    }
39

          
40
  
41
    @Override
42
    protected void configure(HttpSecurity http) throws Exception {
43

          
44
        http.authorizeRequests()
45
                .antMatchers("/**").fullyAuthenticated()
46
                .antMatchers("/saml/**").permitAll()
47
                .anyRequest()
48
                .authenticated();
49
        http.exceptionHandling().defaultAuthenticationEntryPointFor(samlEntryPoint(), new AntPathRequestMatcher("/"));
50
        http.csrf().disable();
51

          
52
        http.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class);
53
        http.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
54
    }
55

          
56
  
57
    @Override
58
    public void configure(WebSecurity web) throws Exception {
59
        web.ignoring()
60
                .antMatchers("/templates/**")
61
                .antMatchers("/login")
62
                .antMatchers("/static/**");
63
    }
64

          
65
  
66
    @Override
67
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
68
        auth.authenticationProvider(samlAuthenticationProvider());
69
    }
70

          
71
  
72
    @Bean
73
    public FilterChainProxy samlFilter() throws Exception {
74
        List<SecurityFilterChain> chains = new ArrayList<>();
75
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"), samlEntryPoint()));
76
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"), samlLogoutFilter()));
77
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/metadata/**"), metadataDisplayFilter()));
78
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"), samlWebSSOProcessingFilter()));
79
        chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"), samlLogoutProcessingFilter()));
80
        return new FilterChainProxy(chains);
81
    }
82

          
83
  
84
    @Bean
85
    public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
86
        return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
87
    }
88

          
89
  
90
    @Bean
91
    public SAMLAuthenticationProvider samlAuthenticationProvider() {
92
        SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
93
        samlAuthenticationProvider.setForcePrincipalAsString(false);
94
        return samlAuthenticationProvider;
95
    }
96

          
97
  
98
    @Bean
99
    public SAMLEntryPoint samlEntryPoint() {
100
        SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
101
        samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
102
        return samlEntryPoint;
103
    }
104

          
105
  
106
    @Bean
107
    public WebSSOProfileOptions defaultWebSSOProfileOptions() {
108
        WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
109
        webSSOProfileOptions.setIncludeScoping(false);
110
        return webSSOProfileOptions;
111
    }
112

          
113
  
114
    @Bean
115
    public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
116
        SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
117
        samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
118
        samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
119
        samlWebSSOProcessingFilter.setAuthenticationFailureHandler(failureRedirectHandler());
120
        return samlWebSSOProcessingFilter;
121
    }
122

          
123
  
124
    @Bean
125
    public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
126
        SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
127
        successRedirectHandler.setDefaultTargetUrl("/");
128
        return successRedirectHandler;
129
    }
130

          
131
  
132
    @Bean
133
    public SimpleUrlAuthenticationFailureHandler failureRedirectHandler() {
134
        SimpleUrlAuthenticationFailureHandler simpleUrlAuthenticationFailureHandler = new SimpleUrlAuthenticationFailureHandler();
135
        simpleUrlAuthenticationFailureHandler.setUseForward(true);
136
        simpleUrlAuthenticationFailureHandler.setDefaultFailureUrl("/error");
137
        return simpleUrlAuthenticationFailureHandler;
138
    }
139

          
140
  
141
    @Bean
142
    public SAMLLogoutFilter samlLogoutFilter() {
143
        return new SAMLLogoutFilter(successLogoutHandler(), new LogoutHandler[]{logoutHandler()}, new LogoutHandler[]{logoutHandler()});
144
    }
145

          
146
  
147
    @Bean
148
    public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
149
        SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
150
        simpleUrlLogoutSuccessHandler.setDefaultTargetUrl("/login");
151
        simpleUrlLogoutSuccessHandler.setAlwaysUseDefaultTargetUrl(true);
152
        return simpleUrlLogoutSuccessHandler;
153
    }
154

          
155
  
156
    @Bean
157
    public SecurityContextLogoutHandler logoutHandler() {
158
        SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
159
        logoutHandler.setInvalidateHttpSession(true);
160
        logoutHandler.setClearAuthentication(true);
161
        return logoutHandler;
162
    }
163

          
164
  
165
    @Bean
166
    public MetadataDisplayFilter metadataDisplayFilter() {
167
        return new MetadataDisplayFilter();
168
    }
169

          
170
  
171
    @Bean
172
    public KeyManager keyManager() {
173
        ClassPathResource storeFile = new ClassPathResource("/security/taskrSamlKeystore.jks");
174
        String storePass = "123456";
175
        Map<String, String> passwords = new HashMap<>();
176
        passwords.put("taskrsaml", "123456");
177
        return new JKSKeyManager(storeFile, storePass, passwords, "taskrsaml");
178
    }
179

          
180
  
181
    @Bean
182
    public SAMLProcessor processor() {
183
        return new SAMLProcessorImpl(Arrays.asList(httpPostBinding(), httpRedirectDeflateBinding()));
184
    }
185

          
186
  
187
    @Bean
188
    public SAMLDefaultLogger samlLogger() {
189
        SAMLDefaultLogger samlDefaultLogger = new SAMLDefaultLogger();
190
        samlDefaultLogger.setLogMessages(true);
191
        return samlDefaultLogger;
192
    }
193

          
194
  
195
    @Bean
196
    public EmptyStorageFactory emptyStorageFactory() {
197
        return new EmptyStorageFactory();
198
    }
199

          
200
  
201
    @Bean
202
    public WebSSOProfile webSSOprofile() {
203
        return new WebSSOProfileImpl();
204
    }
205

          
206
  
207
    @Bean
208
    public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
209
        WebSSOProfileConsumerHoKImpl webSSOProfileConsumerHoK = new WebSSOProfileConsumerHoKImpl();
210
        webSSOProfileConsumerHoK.setMaxAuthenticationAge(MAX_AUTHENTICATION_AGE);
211
        return webSSOProfileConsumerHoK;
212
    }
213

          
214
  
215
    @Bean
216
    public WebSSOProfileConsumer webSSOprofileConsumer() {
217
        WebSSOProfileConsumerImpl webSSOProfileConsumer = new WebSSOProfileConsumerImpl();
218
        webSSOProfileConsumer.setMaxAuthenticationAge(MAX_AUTHENTICATION_AGE);
219
        return webSSOProfileConsumer;
220
    }
221

          
222
  
223
    @Bean
224
    public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
225
        WebSSOProfileConsumerHoKImpl webSSOProfileConsumerHoK = new WebSSOProfileConsumerHoKImpl();
226
        webSSOProfileConsumerHoK.setMaxAuthenticationAge(MAX_AUTHENTICATION_AGE);
227
        return webSSOProfileConsumerHoK;
228
    }
229

          
230
  
231
    @Bean
232
    public SingleLogoutProfile logoutprofile() {
233
        return new SingleLogoutProfileImpl();
234
    }
235

          
236
  
237
    @Bean
238
    public MetadataGeneratorFilter metadataGeneratorFilter() {
239
        return new MetadataGeneratorFilter(metadataGenerator());
240
    }
241

          
242
  
243
    @Bean
244
    public MetadataGenerator metadataGenerator() {
245
        MetadataGenerator metadataGenerator = new MetadataGenerator();
246
        metadataGenerator.setEntityId(entityId);
247
        metadataGenerator.setExtendedMetadata(extendedMetadata());
248
        metadataGenerator.setIncludeDiscoveryExtension(false);
249
        metadataGenerator.setEntityBaseURL(entityBaseUrl);
250
        metadataGenerator.setKeyManager(keyManager());
251
        return metadataGenerator;
252
    }
253

          
254
  
255
    @Bean
256
    public ExtendedMetadata extendedMetadata() {
257
        ExtendedMetadata extendedMetadata = new ExtendedMetadata();
258
        extendedMetadata.setIdpDiscoveryEnabled(false);
259
        extendedMetadata.setSignMetadata(false);
260
        return extendedMetadata;
261
    }
262
  
263

          
264
    @Bean
265
    @Qualifier("metadata")
266
    public CachingMetadataManager metadata() throws MetadataProviderException {
267
        List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
268
        providers.add(idpExtendedMetadataProvider());
269
        return new CachingMetadataManager(providers);
270
    }
271

          
272
  
273
    @Bean
274
    public ExtendedMetadataDelegate idpExtendedMetadataProvider() throws MetadataProviderException {
275
        HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(backgroundTimer(), httpClient(), idpMetadataUrl);
276
        httpMetadataProvider.setParserPool(parserPool());
277
        ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider, extendedMetadata());
278
        extendedMetadataDelegate.setMetadataTrustCheck(true);
279
        extendedMetadataDelegate.setMetadataRequireSignature(false);
280
        return extendedMetadataDelegate;
281
    }
282

          
283
  
284
    @Bean
285
    public Timer backgroundTimer() {
286
        return new Timer(true);
287
    }
288

          
289
  
290
    @Bean
291
    public HttpClient httpClient() {
292
        return new HttpClient(multiThreadedHttpConnectionManager());
293
    }
294

          
295
  
296
    @Bean
297
    public MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager() {
298
        return new MultiThreadedHttpConnectionManager();
299
    }
300

          
301
  
302
    @Bean(initMethod = "initialize")
303
    public StaticBasicParserPool parserPool() {
304
        return new StaticBasicParserPool();
305
    }
306

          
307
  
308
    @Bean(name = "parserPoolHolder")
309
    public ParserPoolHolder parserPoolHolder() {
310
        return new ParserPoolHolder();
311
    }
312

          
313
  
314
    @Bean
315
    public HTTPPostBinding httpPostBinding() {
316
        return new HTTPPostBinding(parserPool(), VelocityFactory.getEngine());
317
    }
318

          
319
  
320
    @Bean
321
    public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
322
        return new HTTPRedirectDeflateBinding(parserPool());
323
    }
324
  
325
}
326

          



Cryptography

Basically what you see is what the Spring documentation already offers with some small adjustments. Now I want to talk about some things that in my opinion are tricky. 

First of all, is the keyManager

Java
 




xxxxxxxxxx
1


 
1
@Bean
2
public KeyManager keyManager() {
3
    ClassPathResource storeFile = new ClassPathResource("/security/taskrSamlKeystore.jks");
4
    String storePass = "123456";
5
    Map<String, String> passwords = new HashMap<>();
6
    passwords.put("taskrsaml", "123456");
7
    return new JKSKeyManager(storeFile, storePass, passwords, "taskrsaml");
8
}



SAML communication uses cryptography for signing and encryption of the data and everything this related is done through the keyManager which relies on a single JKS key store containing all the private and public keys. To generate a key store I've used the following command:

keytool -genkeypair -alias taskrSaml -storepass 123456

-dname "CN=John Doe, OU=Taskr, O=EvilInc, L=Cupertino, S=California, C=US"

-keypass 123456 -keyalg RSA -keysize 2048 -sigalg SHA256withRSA

-keystore taskrSamlKeystore.jks

And then you can place it under your resources and provide the path. Also, note that there is KeyStore Explorer which facilitates easy handling of the key stores and it comes in very handy when you have to deal with importing the IdP certificates. Another thing to mention here is that Maven might corrupt your .jks file during the build process and it becomes unusable, so to deal with that I added the following plugin in pom.xml under the build plugins.

XML
 




xxxxxxxxxx
1
11


 
1
<plugin>
2
   <groupId>org.apache.maven.plugins</groupId>
3
   <artifactId>maven-resources-plugin</artifactId>
4
   <version>3.0.2</version>
5
   <configuration>
6
      <nonFilteredFileExtensions>
7
         <nonFilteredFileExtension>jks</nonFilteredFileExtension>
8
      </nonFilteredFileExtensions>
9
   </configuration>
10
</plugin>



Since I've already mentioned the IdP signing/encryption certificates let's discuss them. IdP certificates may be provided by the person handling the IdP or you can grab them from the IdP's metadata. SSOCircle's metadata URL is https://idp.ssocircle.com/idp-meta.xml (please note this is the same URL used for the HTTPMetadataProvider bean initialization) and opening that you want to search for the following 2 tags 

  • <KeyDescriptor use="signing">   
  • <KeyDescriptor use="encryption">

These are client certificates provided by SSOCircle as a mean of strong authentication, but the integration works without adding them into your key store too. Some IdPs might require a signature trust establishment, so for that reason, I'm going to explain this. Under them, in the  <ds:X509Certificate> tag you'll find IdP's certificates. I created 2 files with the .p12 extension, one for the signing certificate, and the other for the encryption certificate and I pasted the content of the X509 Certificates between these 2 lines.

-----BEGIN CERTIFICATE-----

-----END CERTIFICATE-----

After that, I imported them into my key store using KeyStore Explorer – Import Certificate. 

Being on the cryptography topic, let's discuss another interesting thing - samlBootstrap bean. Though integrating with SSOCircle most probably you won't encounter this problem, but dealing with other IdPs you might get an error saying that the message is not signed with the expected signature algorithm, and that message is signed with the signature algorithm SHA1 whilst the expected one is SHA256.  That is because SAML by default signs its messages with SHA1 and to overcome that, we need to explicitly specify the signature algorithm and digest method via a custom SAML bootstrap. 

Java
 




xxxxxxxxxx
1


 
1
final class CustomSAMLBootstrap extends SAMLBootstrap {
2
    @Override
3
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
4
        super.postProcessBeanFactory(beanFactory);
5
        BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
6
        config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
7
        config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
8
    }
9
}



Communication

Let's talk about communication, how IdP is going to know where to redirect, or where to address its requests. It is already mentioned in the Spring SAML documentation.

In case you use automatically generated metadata make sure to configure entityBaseURL matching the front-end URL in your metadataGeneratorFilter bean - Spring Docs

so setting this URL in metadataGenerator bean in the metadata you'll get generated the following URLs :

http://localhost:8080/saml/SingleLogout

http://localhost:8080/saml/SSO 

This usually is enough to establish the communication between the SP and IdP, but if you're facing a  situation where multiple back-end servers process SAML requests forwarded by a reverse-proxy/load balancer or you're trying to expose your local URL using ngrok, then you might need to configure another SAMLContextProvider.

Java
 




xxxxxxxxxx
1
12


 
1
@Bean
2
public SAMLContextProviderLB contextProvider() {
3
    SAMLContextProviderLB samlContextProviderLB = new SAMLContextProviderLB();
4
    samlContextProviderLB.setScheme(scheme);
5
    samlContextProviderLB.setServerName(serverName);
6
    samlContextProviderLB.setContextPath(contextPath);
7
    samlContextProviderLB.setServerPort(serverPort);
8
    samlContextProviderLB.setIncludeServerPortInRequestURL(true); 
9
    samlContextProviderLB.setStorageFactory(emptyStorageFactory());
10
    return samlContextProviderLB;
11
}



As it is already well-documented I want to mention just one thing, make sure to set samlContextProviderLB.setIncludeServerPortInRequestURL(true); if you're dealing with your server port and you need it in your URL. Also pay attention that if the server port is less or equal to 0 the port won't be included in the URL, might come in handy when you are dealing with multiple environments with different URLs. 

Since we are talking about URLs, I want to tell you another interesting thing that I encountered, I call it “Cyclic SAML Authentication”, but first a little preface. When you are dealing with SAML you can configure 2 kinds of logouts: local logout and global logout.

  • Local logout logs you out just out of the current SP's session, but keeps your IdP session and allows the user to use other SPs still being authenticated. Local logout can be invoked at saml/logout by specifying the local request parameter equal to true  (http://localhost:8080/saml/logout?local=true). 
  • Global logout logs you out of IdP terminating all the SPs sessions forcing the user to enter his credentials once again at the IdP to access any of the SP and can be invoked at saml/logout without specifying any parameters.
HTML
 




xxxxxxxxxx
1


 
1
<form class="left" th:action="@{/saml/logout}" method="get" style="margin-left: auto; margin-right: 5px;">
2
        <input class="btn btn-primary" type="submit" value="Global Logout" data-toggle="tooltip" data-placement="bottom" title="Log out from SSOCircle"/>
3
</form>
4
<form class="left" th:action="@{/saml/logout}" method="get">
5
    <input type="hidden" name="local" value="true"/>
6
    <input class="btn btn-secondary" type="submit" value="Local Logout" data-toggle="tooltip" data-placement="bottom" title="Log out from taskr"/>
7
</form>



Another thing to mention related to the user's session is that you might get a message stating that the authentication statement is too old to be used if you don't get in sync the maximum authentication age of your SP and IdP. You can fix this problem by setting the property on the webSSOprofileConsumer bean. You might be tempted to use the Integer.MAX_VALUE as a maximum authentication age, but this will result in not checking the authentication statement at all or it won't even work as it happened to me.

Java
 




xxxxxxxxxx
1


 
1
@Bean
2
public WebSSOProfileConsumer webSSOprofileConsumer() {
3
    WebSSOProfileConsumerImpl webSSOProfileConsumer = new WebSSOProfileConsumerImpl();
4
    webSSOProfileConsumer.setMaxAuthenticationAge(MAX_AUTHENTICATION_AGE);
5
    return webSSOProfileConsumer;
6
}



Now, what happens after you log out? What I have experienced is that either you log out locally or globally you get redirected to "/" and therefore not being authenticated you automatically get redirected to the login page at IdP in case of a global logout or you're getting a new session which redirects you to the requested page in case of a local logout. 

So from a UX perspective, the local logout doesn't make any sense you try to logout from the current SP and then you get to use it again after some redirection as if you never logged out. A solution would be to create an unsecured login or logout page on the SP side and whenever the user locally logs out you redirect him there and let him decide if he wants to login again or not (saml/login).  So to configure that you need to specify the logout success URL

Java
 




x


 
1
@Bean
2
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
3
    SimpleUrlLogoutSuccessHandler simpleUrlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
4
    simpleUrlLogoutSuccessHandler.setDefaultTargetUrl("/login");
5
    simpleUrlLogoutSuccessHandler.setAlwaysUseDefaultTargetUrl(true);
6
    return simpleUrlLogoutSuccessHandler;
7
}



And for it to work I registered the authentication entry point like this 

Java
 




xxxxxxxxxx
1


 
1
http.exceptionHandling().defaultAuthenticationEntryPointFor(samlEntryPoint(), new AntPathRequestMatcher("/"));



IdP Configuration — SSOCircle

And last but not least I wanted to share my experience on SSOCircle configuration. First what you want to do is to create an account on SSOCircle. The user that you'll be creating here will be the user of your SP.

SSO circle registrationOnce you have your user you need to add the service provider to SSOCircle.

manage your service provider

On the next page, you need to enter the SP's id, metadata, and check the needed assertions that you're going to validate via your custom SAMLUserDetailsService.

The SP's id is the same id used in the metadataGenerator as the entityId and the SP's metadata can be grabbed at http://localhost:8080/saml/metadata

SAML service

That's it, I hope, you've learned something.

authentication security Spring Framework Service provider

Opinions expressed by DZone contributors are their own.

Related

  • Spring OAuth Server: Token Claim Customization
  • Identity Federation and SSO: The Fundamentals
  • What D'Hack Is DPoP?
  • How to Implement Two-Factor Authentication in a Spring Boot OAuth Server? Part 1: Configuration

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook