We are not able to upgrade from Spring session 1.3.3 to 2.1.2 due to
problems with Spring Security SAML. It seems that Spring Security SAML
cannot verify the InResponseToField value because two session IDs are
being created:
Caused by: org.opensaml.common.SAMLException: InResponseToField of the Response doesn't correspond to sent message abc7b9acgecbde41927g729143f1g2
I have extended the HttpSessionStorageFactory which SAMLContextProvider
uses and added some logging in order to find out what's going on:
INFO 18.12.2018 13:43:27:95 (SAMLDelegatingAuthenticationEntryPoint.java:commence:105) - Session ID before redirect: 205e92ea-7ff3-45be-bfd1-648c2ae8da8e
INFO 18.12.2018 13:43:27:111 (SamlAuthenticationConfig.java:storeMessage:413) - Storing message abc7b9acgecbde41927g729143f1g2 to session 205e92ea-7ff3-45be-bfd1-648c2ae8da8e
[The user is now beeing redirected to the IdP and later sent back to the
application]
Now the following error occurs:
Caused by: org.opensaml.common.SAMLException: InResponseToField of the Response doesn't correspond to sent message abc7b9acgecbde41927g729143f1g2
And this is what we also have logged:
INFO 18.12.2018 13:43:27:466 (SamlAuthenticationConfig.java:retrieveMessage:429) - Message abc7b9acgecbde41927g729143f1g2 not found in session 1bc1f535-9207-4a81-b1ee-031fecc12a79
Notice that the session ID has changed which is the reason why the
SAMLException is thrown—it cannot find the value because it's stored
in another session.
Another thing. Only one IdP fails which is using HTTP Post for SSO response binding. The other one, which works, is using Artifact over HTTP Redirect.
Spring Session is configured using the #EnableRedisHttpSession annotation.
If I debug the contents in Redis, this is what I can see.
w3test-jb03.uio.no:6379> KEYS *nettskjema*
1) "nettskjema:expirations:1545140940000"
[…]
22) "nettskjema:expirations:1545144240000"
23) "nettskjema:sessions:expires:8ae32bf8-28a2-422f-a96a-e42e0a52457a"
24) "nettskjema:expirations:1545137580000"
[…]
36) "nettskjema:expirations:1545146040000"
37) "nettskjema:sessions:8ae32bf8-28a2-422f-a96a-e42e0a52457a"
38) "nettskjema:expirations:1545147000000"
[…]
43) "nettskjema:expirations:1545147120000"
44) "nettskjema:sessions:expires:205e92ea-7ff3-45be-bfd1-648c2ae8da8e"
45) "nettskjema:expirations:1545141900000"
[…]
48) "nettskjema:expirations:1545146400000"
49) "nettskjema:sessions:20afd141-e797-46a3-a6cf-efe8559280cb"
50) "nettskjema:expirations:1545142080000"
[…]
54) "nettskjema:expirations:1545142200000"
55) "nettskjema:sessions:84ff3f22-edf6-400b-83fd-b2e7627acfd3"
56) "nettskjema:expirations:1545145440000"
[…]
62) "nettskjema:expirations:1545145320000"
63) "nettskjema:sessions:expires:517dd25a-f743-47d5-8ad6-96fc3aa34eb2"
64) "nettskjema:expirations:1545138720000"
[…]
95) "nettskjema:expirations:1545137040000"
96) "nettskjema:sessions:517dd25a-f743-47d5-8ad6-96fc3aa34eb2"
97) "nettskjema:expirations:1545144120000"
[…]
100) "nettskjema:expirations:1545140760000"
101) "nettskjema:sessions:5c937506-2ea2-4dc1-94e8-d048d7591a87"
102) "nettskjema:expirations:1545138960000"
[…]
104) "nettskjema:expirations:1545141300000"
105) "nettskjema:sessions:expires:1bc1f535-9207-4a81-b1ee-031fecc12a79"
106) "nettskjema:expirations:1545143280000"
[…]
122) "nettskjema:expirations:1545139440000"
123) "nettskjema:sessions:expires:20bda413-93c6-4475-9163-a88a5689e4ed"
124) "nettskjema:expirations:1545143760000"
[…]
135) "nettskjema:expirations:1545147480000"
136) "nettskjema:sessions:expires:a546038a-bac7-42c1-bb53-2c1b9973fa97"
137) "nettskjema:expirations:1545145620000"
[…]
143) "nettskjema:expirations:1545146880000"
144) "nettskjema:sessions:expires:20afd141-e797-46a3-a6cf-efe8559280cb"
145) "nettskjema:sessions:8cf6b02c-3ac2-4974-a516-83ffd6fbb98c"
146) "nettskjema:expirations:1545144300000"
[…]
149) "nettskjema:expirations:1545141720000"
150) "nettskjema:sessions:expires:8cf6b02c-3ac2-4974-a516-83ffd6fbb98c"
151) "nettskjema:expirations:1545137220000"
[…]
157) "nettskjema:expirations:1545138180000"
158) "nettskjema:sessions:20bda413-93c6-4475-9163-a88a5689e4ed"
159) "nettskjema:expirations:1545146220000"
160) "nettskjema:expirations:1545142380000"
161) "nettskjema:sessions:b32daccd-7e81-4faa-9ae6-11803392f4f1"
162) "nettskjema:expirations:1545137340000"
163) "nettskjema:expirations:1545138420000"
164) "nettskjema:sessions:a546038a-bac7-42c1-bb53-2c1b9973fa97"
165) "nettskjema:sessions:7cf0b74b-5266-42ed-a966-34e34f423396"
166) "nettskjema:expirations:1545146160000"
[…]
169) "nettskjema:expirations:1545139980000"
170) "nettskjema:sessions:1bed0254-b8f5-4fc4-8da2-5805eb130a82"
171) "nettskjema:expirations:1545143400000"
[…]
192) "nettskjema:expirations:1545146580000"
193) "nettskjema:sessions:expires:5c937506-2ea2-4dc1-94e8-d048d7591a87"
194) "nettskjema:expirations:1545139320000"
195) "nettskjema:sessions:c7fb8653-6985-47c2-9bd6-f3012665ca83"
196) "nettskjema:expirations:1545138660000"
197) "nettskjema:sessions:205e92ea-7ff3-45be-bfd1-648c2ae8da8e"
198) "nettskjema:expirations:1545139140000"
[…]
201) "nettskjema:expirations:1545143820000"
202) "nettskjema:sessions:1bc1f535-9207-4a81-b1ee-031fecc12a79"
203) "nettskjema:expirations:1545142980000"
[…]
I have tried two things as well with no luck:
Set redisFlushMode to IMMEDIATE:
#EnableRedisHttpSession(redisNamespace = "nettskjema", maxInactiveIntervalInSeconds = 10800, redisFlushMode = RedisFlushMode.IMMEDIATE)
Configured Spring Security to always create sessions:
create-session="always"
This is the library and Redis versions we are using. Note that we are using Jedis and not Lettuce:
Redis server v=3.2.10 (Redis Sentinel)
spring.session.data.redis.version: 2.1.2.RELEASE
spring.security.version: 5.1.1.RELEASE
org.springframework.version: 5.1.3.RELEASE
jedis.version: 2.9.0
I found the source of the problem. It was related to the SameSite cookie attribute which was added in Spring Session 2.*. Since our IdP (SAML) had an other domain compared to our application, the default lax value caused some problems. The trick was to set the sameSite attribute in DefaultCookieSerializer to null like this:
serializer.setSameSite(null);
In case anyone still uses XML
<bean id="serializer" class="org.springframework.session.web.http.DefaultCookieSerializer">
<property name="sameSite"><null /></property>
</bean>
The Spring Security doc says this about this error:
Make sure that application uses the same HttpSession during sending of the request and reception of the response.
In our case, we were navigating to a DNS hostname to initiate authentication, but SAML was configured to redirect to the machine name. Since SameSite is being enforced on the JSESSIONID associated with the authorization request, the browser did not include the session cookie with the authorization response, and this error was thrown (despite the fact that the InResponseTo value in the response matched the ID of the auth request ... so I guess you can't take the error literally.)
It seems this might happen more often now that browsers have changed the default SameSite behavior from none to Lax (when not specified). https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
tl;dr - make sure you start and end at the exact same domain name via your authentication flow.
Related
When OIDC authentication request is being validated, first readirect_uri and client_id are verified (client must be registered and it must allow specified redirect uri). After redirect_uri is verified all further validation errors should be redirected to redirect_uri with error/error_description parameters (for example "https://example.com/oauth-callback?error=invalid_request").
However there is response_mode param which tells how response (including error response) should be returned to the client. There are some scenarios in which response_mode is present in the authentication request but it cannot be verified/validated:
response_mode is just invalid / unsupported by server - in theory error response with error=invalid_request should be returned to the client afaik, but which response mode should be used in this case? some default response mode for authorization flow represented by response_type?
response_type is invalid/unsupported by server or not allowed by client - in this case error response with error=unsupported_response_type|unauthorized_client should be returned afaik, but which response_mode should be used in this case? The problem is that some response modes are not allowed by some authorization flows (for example response_mode=query in implicit flow). So for example if request contains response_type=invalid&response_mode=form_post, then how error response should be returned to the client? Using form_post?
response_type=token&response_mode=query - this is not allowed, but how error response should be returned to the client? Using query or fragment response_type?
I could not find in OAuth2/OIDC RFCs how to handle those cases. It seems reasonable to:
if response_type is invalid then use fragment response_mode to return error
if response_type is valid but response_mode is invalid or not allowed by client then use fragment response_mode to return error
if response_type is valid and response_mode is valid and allowed by client then, for any further request validation errors, use specified response_mode to return those errors to the client
But not sure, are there any documentation / best practices how OAuth2/OIDC server should behave is such cases?
I am trying to authenticate using bearer token.
When I am trying to call the api with valid oauth2.0 access token
https://**************/api/method/frappe.auth.get_logged_user
I am getting this error response.
{
"exc": "[\"Traceback (most recent call last):\\n File \\\"/home/frappe/frappe-bench/apps/frappe/frappe/app.py\\\", line 66, in application\\n response = frappe.api.handle()\\n File \\\"/home/frappe/frappe-bench/apps/frappe/frappe/api.py\\\", line 56, in handle\\n return frappe.handler.handle()\\n File \\\"/home/frappe/frappe-bench/apps/frappe/frappe/handler.py\\\", line 21, in handle\\n data = execute_cmd(cmd)\\n File \\\"/home/frappe/frappe-bench/apps/frappe/frappe/handler.py\\\", line 54, in execute_cmd\\n is_whitelisted(method)\\n File \\\"/home/frappe/frappe-bench/apps/frappe/frappe/handler.py\\\", line 64, in is_whitelisted\\n raise frappe.PermissionError('Not Allowed, {0}'.format(method))\\nPermissionError: Not Allowed, <function get_logged_user at 0x7f9c027a9c08>\\n\"]",
"_server_messages": "[\"{\\\"message\\\": \\\"Not permitted\\\"}\"]"
}
Hard to tell without the code base. But it looks like the
execute_cmd(..)
is raising a Permissions error. I think the problem is with the script itself running on that machine. i.e. Not necessarily the access token. But without code this is a guess. You could verify by manually running that script when logged into the machine with the same user/permissions as when running as a web server.
I have an auth-service (which does LDAP authentication and DB authorization). - uses spring security and spring session redis
Have a data-service which uses the auth-service for authentication/session management. - uses spring session redis and spring security(uses custom auth provider to call the auth-service via a Feign client)
So when use logs-in it hits the auth-service /user endpoint and gets the sessionid back. When I use the same sessionid for an endpoint in data-service, I get the below error
Uncaught exception thrown
org.springframework.data.redis.serializer.SerializationException: Cannot deserialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to deserialize payload. Is the byte array a result of corresponding serialization for DefaultDeserializer?; nested exception is org.springframework.core.NestedIOException: Failed to deserialize object type; nested exception is java.lang.ClassNotFoundException: XXX (an entity in auth-service)
Redis details
127.0.0.1:6379> KEYS *
1) "spring:session:sessions:expires:ff3705e1-7403-48dc-a026-4b71d4c847f1"
2) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:xxxx"
3) "spring:session:sessions:ff3705e1-7403-48dc-a026-4b71d4c847f1"
4) "spring:session:expirations:1535652420000"
127.0.0.1:6379>
Thanks
We're having a Keycloak with the realm socialBetaTest when I try to initate the Client Initiated Account Linking with the following URL (link with twitter):
https://socialBeta.maio290.de/auth/realms/socialBetaTest/broker/twitter/link?client_id=frontend&redirect_uri=https://localhost:4200/&nonce=someString&hash=someHash
I am getting the following error in my KeyCloak stdout:
WARN [org.keycloak.events] (default task-42) type=CLIENT_INITIATED_ACCOUNT_LINKING_ERROR, realmId=social, clientId=frontend, userId={properUserID}, ipAddress=x.x.x.x, error=invalid_token, redirect_uri=https://localhost:4200/, username={someEmailAddress}
What I notice here, they realm isn't the proper one, why is it called "social" and not "socialBetaTest"? And why is the token invalid, when it was issued (iss in the JWT) by socialBetaTest? Since we don't provide the token by any parameter, I guess it's reading the token out from the cookie and/or local storage.
Does anyone know how to fix this issue?
When asking if my assertion is signed it always returns false, making it hard to do a second validation after the SP filter finish. My IDP is ADFS 2.0 and I have tried to sign the entire samlresponse and only the assertion, but with the same result.
We want to get the assertion out of our ticket in the web application that is protected by the spring SP module. So when the user is authenticated we want to forward our assertion to a service that will validate it and do something if it is a valid assertion. In this service we do not want Spring security and the spring SP. We simply want the assertion forwarded and verified with openSaml library.
Our problem is that when we got the assertion out of Spring sec and forwarded to our service, validation is started with the following line, which always return false:
assertion.isSigned()
The above line of code is derived from:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
SAMLCredential credential = (SAMLCredential) authentication.getCredentials();
credential.getAuthenticationAssertion().isSigned();
This also generates 'false' when called in our top tier webapp.
Overriding the SAMLAuthenticationProvider adding the above code yields 'false' as well.
If we add following code snippet to our webapp:
Configuration.getMarshallerFactory().getMarshaller(credential.getAuthenticationAssertion());
marshaller.marshall(credential.getAuthenticationAssertion());
And then run the
assertion.isSigned();
We get 'true' as a response.
We're using spring-security-saml2-core version 1.0.1.RELEASE.
Logs:
10:07:19,413 DEBUG [org.springframework.security.saml.websso.WebSSOProfileConsumerImpl] (http-/0.0.0.0:8443-3) Verifying issuer of the Response
10:07:19,414 DEBUG [org.springframework.security.saml.websso.WebSSOProfileConsumerImpl] (http-/0.0.0.0:8443-3) Verifying signature
10:07:19,417 DEBUG [org.springframework.security.saml.websso.WebSSOProfileConsumerImpl] (http-/0.0.0.0:8443-3) Processing Bearer subject confirmation
10:07:19,418 DEBUG [org.springframework.security.saml.websso.WebSSOProfileConsumerImpl] (http-/0.0.0.0:8443-3) Verifying received AuthnContext org.opensaml.saml2.core.impl.AuthnContextImpl#3efbe08d against requested null
10:07:19,418 DEBUG [org.springframework.security.saml.websso.WebSSOProfileConsumerImpl] (http-/0.0.0.0:8443-3) Validation of authentication statement in assertion _79ec0857-148d-49ca-8df4-25e685fdc5b9 was successful
10:07:19,422 INFO [org.springframework.security.saml.log.SAMLDefaultLogger] (http-/0.0.0.0:8443-3) AuthNResponse;SUCCESS;172.172.176.103;IAMDemoAppADFS;https://ADFSdomain.test.se/adfs/services/trust;XXX;;
10:07:19,422 DEBUG [org.springframework.security.saml.SAMLProcessingFilter] (http-/0.0.0.0:8443-3) Authentication success. Updating SecurityContextHolder to contain: org.springframework.security.providers.ExpiringUsernameAuthenticationToken#2cb1c6f2: Principal: XXX; Credentials: [PROTECTED]; Authenticated: true; Details: null; Not granted any authorities
Here is our SAML response:
<samlp:Response Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
Destination="https://domain.test.se:8443/IAMDemoAppADFS/saml/SSO"
ID="_a97d2515-6160-4370-8e85-a34143a1e2fb" InResponseTo="a3jdaigh6671c3g5464d3ff472jffdd"
IssueInstant="2015-10-28T09:52:34.745Z" Version="2.0"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://ADFSdomain.test.se/adfs/services/trust</Issuer>
<samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>
<Assertion ID="_574cd222-ec33-4f3d-b77b-a3ab2f16d33d" IssueInstant="2015-10-28T09:52:34.745Z"
Version="2.0" xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
<Issuer>https://ADFSdomain.test.se/adfs/services/trust</Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo><CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<Reference URI="#_574cd222-ec33-4f3d-b77b-a3ab2f16d33d">
<Transforms><Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/><Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/></Transforms><DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<DigestValue>gk/c0lTTLw8zXdKuvkZi48eY4sA=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>kU7WfGIEIQul40i9jObZ2uyb0rnJaEr2n2bBI6E/IS8Dr52quUR8nrMG5jwmGFxgdD63odpo4605SmQVlPKxOAD2GbIOSzgNDG8u/axH8JEEAhzfC5CGfE6i775WkkZ1+LsOrmrWCAJnXjejo/Zrg6z7rSi/USgeB4TmxipwF7twMunnNFKgaPntzv3dVAQjc+zglCR0A3QQwo1orM14mFcrcYlsD6sIGWd1LmumgScWE6iNt5Fif/hPirtcF0K0YpNBPbhiDwxpPZ8NgAZIjliZU8b5Qem6Vi50ysH9lj57r7hMmUD9IjgHS1wpOfZuII2if+BcddUp7aqA+GWmw7Fw==</SignatureValue>
<KeyInfo>
<ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Certificate>MIIC/jCCAeagAwIBAgIQepmcj017xa9GmDEoyznQHjANBgkqhkiG9w0BAQsFADA7MTkwNwYDVQQDEzBBREZTIFNpZ25pbmcgLSBhZGZzLm1zYWQubGFiLm1pZ3JhdGlvbnN2ZXJrZXQuc2UwHhcNMTUwODI5MTkzNjE5WhcNMTYwODI4MTkzNjE5WjA7MTkwNwYDVQQDEzBBREZTIFNpZ25pbmcgLSBhZGZzLm1zYWQubGFiLm1pZ3JhdGlvbnN2ZXJrZXQuc2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDb27miaTa+9Kpdj3kPbno8IqypRQq/NI3shIhtYyAbbaiKYQEs0C6AxFoowpo13MB1yKO9g0EmgmDHsIHE1Gl+tc8NvNC03Dr6izsOb1ePChdi/oJ10BoAS1zmrOuHDoa31HwfqkXwRRIUIE70DZxmroAtyE/GeDGxqGwmtZWRqdPXY5q7MKkfVqUe0e8jbod76ymZpA773n5ka0NxyFu5N8/W+dEAcUqlCBDSu140KEnE6sCG4QqEth9GPom3ttqNb+qvBsDnC8UZ05chv5tHS3xXQ1u8Q2vqdtTX8hrK5ZQMr+Hd/mtNpb/65X3uHTGsemR9sSVmyrcZWX/9oAKbHjAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAD/vJqoPm5FsRzuzWbf1F4FE9s2Cs84KM/UgjIJeIfP35EAW8v2SIbCXAOs2rW+s25Y8X+U4whPxDlGiZ8aO8E7KIq/Mm4AOpmXKMCzahxNyD1MqeRUMZNsuFv4ciw8cNtIDDecqDqSCjDeTMg/asppVEuzTREKw7tP2a5KJgfJrmFM/92tUzqpIfO3gqrJkJsbHz53g4CbwwyleHY8NuSxXYwncw8qzgHvBlPRzpn0GkMSZXJxSRDKH1QXJeaf2jUddaF6S+7FsalPzHyMf/jG0YugDVinrR+6fEeERlA/zM8JYC7CVsZRn/OHmvxFXJpiayrVZTom+N5701DihuzA=</ds:X509Certificate>
</ds:X509Data>
</KeyInfo>
</Signature>
<Subject>
<NameID>XXX</NameID>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"><SubjectConfirmationData InResponseTo="a3jdaigh6671c3g5464d3ff472jffdd"
NotOnOrAfter="2015-10-28T09:57:34.745Z"
Recipient="https://domain.test.se:8443/IAMDemoAppADFS/saml/SSO"/></SubjectConfirmation>
</Subject>
<Conditions NotBefore="2015-10-28T09:52:34.745Z" NotOnOrAfter="2015-10-28T09:53:34.745Z">
<AudienceRestriction>
<Audience>IAMDemoAppADFS</Audience>
</AudienceRestriction>
</Conditions>
<AuthnStatement AuthnInstant="2015-10-28T09:52:34.558Z"
SessionIndex="_574cd222-ec33-4f3d-b77b-a3ab2f16d33d">
<AuthnContext>
<AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef>
</AuthnContext>
</AuthnStatement>
</Assertion>
</samlp:Response>
Try setting releaseDOMto false in WebSSOProfileConsumerImpl. This use-case is documented in the manual with additional details - it doesn't explicitly state that the signature will be removed by default, but it's likely the case.