We have developed an application using Spring Security and Spring SAML that works in our development environment where we use SSOCircle as our IDP. When we move into our customer's environment using their IDP, we are able to authenticate and navigate through the application without a problem as long as we do not pause. If the user pauses on a page for more than minute before submitting it, the application will redirect to the original landing page and the submitted data is lost.
The logs show:
o.s.s.w.a.ExceptionTranslationFilter
- Authentication exception occurred; redirecting to authentication entry point
org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.providers.ExpiringUsernameAuthenticationToken
Prior to this you the logs show at about every minute something similar to:
SecurityContextHolder not populated with anonymous token, as it already contained: 'org.springframework.security.providers.ExpiringUsernameAuthenticationToken#e6313ceb: Principal: REDACTED
We have been told by the customer that their IDP has a timeout of 60 seconds with a +-30sec skew time.
We asked them to temporarily adjust the IDP timeout to 30 minutes and our problem went away. When we go to production we must have the original setting of 60 seconds.
Our application is using the SAMLAuthenticationProvider:
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(samlAuthenticationProvider());
}
#Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();
samlAuthenticationProvider.setForcePrincipalAsString(false);
samlAuthenticationProvider.setUserDetails(samlUserDetailsService);
return samlAuthenticationProvider;
}
How do we configure ExpiringUsernameAuthenticationToken to use this? And why does the original authentication work if it is not set?
And why is the application trying to re-authenticate when the IDP's session expires?
WebSSOProfileConsumerImpl and SingleLogoutProfileImpl both provide ways to set the responseSkew. Should this be set equal to, less than, or greater than the IDP's skew time?
Spring SAML by default observes the SessionNotOnOrAfter field provided by IDP in its SAML Response. This field tells that that once time hits the provided value user must be re-authenticated.
Spring SAML tries to re-authenticate user by sending the current Authentication object to AuthenticationManager, which tries to find an AuhenticationProvier which supports Authentiction object of such type (ExpiringUsernameAuthenticationToken in case of Spring SAML). In your case there is no such provider - that's why you see the ProviderNotFoundException exception. After this error Spring Security probably invokes the default EntryPoint, which redirects your user to the login page.
In order to ignore the SessionNotOnOrAfter value simply extend class SAMLAuthenticationProvider, override method getExpirationDate and make it return null. Then use your new class in the securityContext.xml.
But the correct solution is for your IDP to return SessionNotOnOrAfter value with a sensible session length - I wonder why they insist on using 60 seconds there.
Related
For a software in active development we are using Spring Boot (with Spring Security) and the Keycloak Adapter.
The goal is to:
require valid authentication for all endpoints except those annotated with #Public (see the code snippet) (this works)
the authentication must be via OAuth - the client gets a token directly from Keycloak and the Spring Security + Keycloak adapter make sure it is valid
optionally, Basic Auth is also supported (the Keycloak adapter can be configured to perform a login and make it appear like a regular token auth for the rest of the code) (this works as well)
Everything is working fine as it stands, but I have some problems understanding a few details:
The KeycloakWebSecurityConfigurerAdapter enables CSRF protection. I think this is only done so it can register its own Matcher to allow requests from Keycloak
It enables session management and requires some according beans
Even though requests are made with token authentication, a JSESSIONID cookie is returned
According to my understanding:
sessions should not be needed since stateless token authentication is used (so why is the KeycloakWebSecurityConfigurerAdapter enabling it). Is this only for the BASIC Auth part?
since sessions are enabled, CSRF protection is indeed needed - but I don't want the sessions in the first place and then the API would not need CSRF protection, right?
even if I set http.sessionManagement().disable() after the super.configure(http) call the JSESSIONID cookie is set (so where is this coming from?)
As stated in the code snippet, SessionAuthenticationStrategy is not set to the null once since we use the Authorization part of Keycloak and the application is a Service Account Manager (thus managing those resource records).
Would be great if someone can clear things up. Thanks in advance!
#KeycloakConfiguration
public class WebSecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Inject private RequestMappingHandlerMapping requestMappingHandlerMapping;
#Override
protected void configure(final HttpSecurity http) throws Exception {
super.configure(http);
http
.authorizeRequests()
.requestMatchers(new PublicHandlerMethodMatcher(requestMappingHandlerMapping))
.permitAll()
.anyRequest()
.authenticated();
}
// ~~~~~~~~~~ Keycloak ~~~~~~~~~~
#Override
#ConditionalOnMissingBean(HttpSessionManager.class)
#Bean protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
/**
* {#link NullAuthenticatedSessionStrategy} is not used since we initiate logins
* from our application and this would not be possible with {#code bearer-only}
* clients (for which the null strategy is recommended).
*/
#Override
#Bean protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
/**
* HTTP session {#link ApplicationEvent} publisher needed for the
* {#link SessionRegistryImpl} of {#link #sessionAuthenticationStrategy()}
* to work properly.
*/
#Bean public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Override
#Bean public KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
return super.keycloakAuthenticationProvider();
}
}
You may fall into excessive JWT token usage. Look at this article for example https://blog.logrocket.com/jwt-authentication-best-practices/. Especially look at the references at the end of the article about JWT as a session token.
For your web-application UI you are using sessions in most of the cases. It doesn't matter what type of token is used for authentication. Keycloak does everything correctly - it gives back httpOnly secure cookie for session management and tracks user status at backend. For better understanding of how it works you may look at the example code here: examples
For better separation of stateless backend (micro-)services and user UI session keycloak documentation suggest to use 2 different authentication stratagies: RegisterSessionAuthenticationStrategy for sessions and NullAuthenticatedSessionStrategy for bearer-only services
I've been reading a lot about session fixation attacks and the most popular solutions I've come across are changing the SessionID when user logs in and creating an additional cookie using a GUID to verify the user "belongs" to the SessionID.
My question is this: Isn't it enough to just delete the SessionID cookie (ASP.NET_SessionID) to ensure a new SessionID is generated?
In MVC 5, when the user logs in an additional encrypted user claims cookies is created (AspNet.ApplicationCookie) which Identity uses to authenticate the user upon each request. The additional "GUID cookie" seems unnecessary.
I’m originally a .NET desktop application developer writing my first MVC app and the learning curve has been a bit steep… although refreshingly enjoyable.
Thanks for any help.
Let me try to explain the issue and the solution by using comparisons between desktop and web apps (both in .Net)
When you start your desktop app, the first thing the app shows is a login screen, after which your access to the UI is granted. Now, each time the app's exe is started, it writes the "RunID" to a text file and shows the login screen. The RunID is how the rest of your usage of the app is going to be tracked/correlated.
Assume for a second that the file was on C:\RunID.txt.
An attacker (hacker) can start the exe (without logging in) on Machine1 and copy the contents of C:\RunID.txt to Machine2. Now as soon as you log in on Machine1, the RunID token from Machine1 will also work on Machine2, this is called session fixation.
The ideal way to fix it is to ABANDON the pre-authentication token, and issue a NEW Post-Authentication token. So, you would get a new Token after authentication (or in your case, an additional GUID) which will NOT EXIST on Machine2 and hence provide a level of security in addition to the RunID random token (Session ID)
Let me know if you'd like further explaination, but that is why even in MVC, you should abandon the previous session and create a new session post-auth to avoid session fixation, as a compensating control, you can add a GUID cookie too correspond with the Session ID cookie.
You can do this to avoid that situation:
SessionIDManager Manager = new SessionIDManager();
string NewID = Manager.CreateSessionID(Context);
string OldID = Context.Session.SessionID;
bool redirected = false;
bool IsAdded = false;
Manager.SaveSessionID(Context, NewID, out redirected, out IsAdded);
Response.Write("Old SessionId Is : " + OldID);
if (IsAdded)
{
Response.Write("<br/> New Session ID Is : " + NewID);
}
else
{
Response.Write("<br/> Session Id did not saved : ");
}
Support link:
Link
I'm really trying to understand how Spring Security works, but I'm a bit lost at the moment. Here's the simple scenario:
User visits the website home page but doesn't log in
SecurityContextPersistenceFilter logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter populates SecurityContextHolder with an anonymous token
A session is created with ID = C2A35ED5A41E29865FF53162B0024D52
User lets the page sit idle until the session times out
User clicks on the About page (or home page again)
SecurityContextPersistenceFilter again logs that no SecurityContext was available and a new one will be created
AnonymousAuthenticationFilter again populates SecurityContextHolder with an anonymous token
SessionManagementFilter logs that requested session ID C2A35ED5A41E29865FF53162B0024D52 is invalid
SessionManagementFilter logs that it is starting a new session and redirecting to /invalidsession
These pages are configured to .authorizeRequests().antMatchers("/","/home","/about").permitAll(). I have the invalid session option turned on to handle authenticated users: .sessionManagement().invalidSessionUrl("/errors/invalidSession"). If I comment out that option, then everything described above is exactly the same EXCEPT for step #10 - SessionManagementFilter sees that the requested session ID is invalid (#9) but does NOT start a new session and perform the redirect (#10).
WHY? What can I do to keep the invalid session option but correctly handle anonymous users, i.e., not be redirected? Or is that just not possible and I'll have to handle authenticated users separately? I'd be very grateful if anyone can help me understand what's happening here and point me in a direction to solve this. Let me know if you need to see my full http configuration.
EDIT
I ran a series of tests with anonymous and registered (authenticated) users. If .sessionManagement().invalidSessionUrl("/errors/invalidSession") is enabled then both types of users will eventually arrive at the error page. Authenticated users with RememberMe unchecked are the same as anon users. If RememberMe is checked, then the error page appears once RememberMe times out.
If I disable the invalid session option, no users ever get the error page (which makes sense). Both types of users can browse public pages as long as they want and authenticated users will be asked to log in after the session or RememberMe expires.
If you're interested the code involved here is in SessionManagementFilter
if (invalidSessionStrategy != null) {
invalidSessionStrategy
.onInvalidSessionDetected(request, response);
return;
}
If .sessionManagement().invalidSessionUrl is enabled the default method SimpleRedirectInvalidSessionStrategy is called, which executes this piece of code:
if (createNewSession) {
request.getSession();
}
redirectStrategy.sendRedirect(request, response, destinationUrl);
The createNewSession boolean can be set through setCreateNewSession(boolean createNewSession), which is described as:
Determines whether a new session should be created before redirecting (to avoid possible looping issues where the same session ID is sent with the redirected request). Alternatively, ensure that the configured URL does not pass through the SessionManagementFilter.
So, it looks to me like .sessionManagement().invalidSessionUrl works best for sites where all pages are authenticated. The options I'm looking at are a custom filter placed before the SessionManagementFilter that checks the page access and turns 'createNewSession' on/off as needed or turning off the invalid session option and handling it elsewhere for authenticated pages (?). I also stumbled across <%# page session=“false” %> in this SO question - Why set a JSP page session = “false” directive? - which I'm going to look into further. Being so new to Spring Security I don't have a good sense of the best practice for handling this situation correctly. Any help would be appreciated.
OK, so I've spent the last couple of weeks digging around in Spring Security trying to understand how it all fits together. I'm still learning, but for this particular situation I found two approaches that work.
The obvious one is to just bypass security for public pages like this:
#Override
public void configure(WebSecurity web) throws Exception
{
web
.ignoring()
.antMatchers("/", "/home", "/about", "/login**", "/thankyou", "/user/signup**", "/resources/**")
;
}
I still don't know enough about web security in general to know if this is an acceptable approach or not, but it allows anonymous users to browse the site w/o ever getting an invalid session error.
The harder solution (for a Java and Spring noob like me) is based upon these SO questions:
Spring security invalid session redirect
How to set a custom invalid session strategy in Spring Security
The default SimpleRedirectInvalidSessionStrategy class is final which meant I had to create basically a copy of that class (not sure how good an idea that is). You can't use a session attribute because the session has been destroyed by the time it gets to this strategy so I created a helper class for a session cookie called authUser (I can post the class if anyone wants to see it). The cookie is created or updated in the LoginSuccessHandler or RememberMeSuccessHandler and it indicates if the user is anonymous or authenticated:
authCookie.setCookie(request, response, "anonymousUser");
or
authCookie.setCookie(request, response, authentication.getName());
I'm currently using the actual login only for testing purposes - it will ultimately be just a simple yes/no indicator of some sort. CustomLogoutSuccessHandler resets it to anonymousUser
The invalid session method looks like this:
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String url = destinationUrl;
//reset context default value
redirectStrategy.setContextRelative(false);
if (authCookie.isCurrentCookieAnonymous()) {
//pass the URL originally requested by the anonymous user
url = request.getRequestURI();
//the URL needs to have the context removed
redirectStrategy.setContextRelative(true);
}
//always revert to anonymous user
authCookie.setCookie(request, response, "anonymousUser");
logger.debug("Starting new session (if required) and redirecting to '" + url + "'");
if (createNewSession)
request.getSession();
redirectStrategy.sendRedirect(request, response, url);
}
Again, I can post the full class if requested.
The SecurityConfig class includes the following:
#Bean
public SessionManagementBeanPostProcessor sessionManagementBeanPostProcessor() {
return new SessionManagementBeanPostProcessor();
}
protected static class SessionManagementBeanPostProcessor implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof SessionManagementFilter) {
SessionManagementFilter filter = (SessionManagementFilter) bean;
filter.setInvalidSessionStrategy(new RedirectInvalidSession("/errors/invalidSession"));
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
My testing so far has been successful for both anonymous and authenticated users, but this approach has not been production tested.
I'm creating a webapi project with oauth bearer token authenthication and external login providers (google, twitter, facebook etc.). I started with the basic VS 2013 template and got everything to work fine!
However, after a user successfully logs is, the owin infrastructure creates a redirect with the folllowing structure:
http://some.url/#access_token=<the access token>&token_type=bearer&expires_in=1209600
In my server code I want to add an additional parameter to this redirect because in the registration process of my app, a new user needs to first confirm and accept the usage license before he/she is registered as a user. Therefore I want to add the parameter "requiresConfirmation=true" to the redirect. However, I've no clue about how to do this. I tried setting AuthenticationResponseChallenge.Properties.RedirectUri of the AuthenticationManager but this doesn't seem to have any affect.
Any suggestions would be greatly appreciated!
It should be relatively easy with the AuthorizationEndpointResponse notification:
In your custom OAuthAuthorizationServerProvider implementation, simply override AuthorizationEndpointResponse to extract your extra parameter from the ambient response grant, which is created when you call IOwinContext.Authentication.SignIn(properties, identity).
You can then add a custom requiresConfirmation parameter to AdditionalResponseParameters: it will be automatically added to the callback URL (i.e in the fragment when using the implicit flow):
public override Task AuthorizationEndpointResponse(OAuthAuthorizationEndpointResponseContext context) {
var requiresConfirmation = bool.Parse(context.OwinContext.Authentication.AuthenticationResponseGrant.Properties.Dictionary["requiresConfirmation"]);
if (requiresConfirmation) {
context.AdditionalResponseParameters.Add("requiresConfirmation", true);
}
return Task.FromResult<object>(null);
}
In your code calling SignIn, determine whether the user is registered or not and add requiresConfirmation to the AuthenticationProperties container:
var properties = new AuthenticationProperties();
properties.Dictionary.Add("requiresConfirmation", "true"/"false");
context.Authentication.SignIn(properties, identity);
Feel free to ping me if you need more details.
I'm getting a "Message signature was incorrect" exception when trying to authenticate with MyOpenID and Yahoo.
I'm using pretty much the ASP.NET MVC sample code that came with DotNetOpenAuth 3.4.2
public ActionResult Authenticate(string openid)
{
var openIdRelyingParty = new OpenIdRelyingParty();
var authenticationResponse = openIdRelyingParty.GetResponse();
if (authenticationResponse == null)
{
// Stage 2: User submitting identifier
Identifier identifier;
if (Identifier.TryParse(openid, out identifier))
{
var realm = new Realm(Request.Url.Root() + "openid");
var authenticationRequest = openIdRelyingParty.CreateRequest(openid, realm);
authenticationRequest.RedirectToProvider();
}
else
{
return RedirectToAction("login", "home");
}
}
else
{
// Stage 3: OpenID provider sending assertion response
switch (authenticationResponse.Status)
{
case AuthenticationStatus.Authenticated:
{
// TODO
}
case AuthenticationStatus.Failed:
{
throw authenticationResponse.Exception;
}
}
}
return new EmptyResult();
}
Working fine with Google, AOL and others. However, Yahoo and MyOpenID fall into the AuthenticationStatus.Failed case with the following exception:
DotNetOpenAuth.Messaging.Bindings.InvalidSignatureException: Message signature was incorrect.
at DotNetOpenAuth.OpenId.ChannelElements.SigningBindingElement.ProcessIncomingMessage(IProtocolMessage message) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\OpenId\ChannelElements\SigningBindingElement.cs:line 139
at DotNetOpenAuth.Messaging.Channel.ProcessIncomingMessage(IProtocolMessage message) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\Messaging\Channel.cs:line 992
at DotNetOpenAuth.OpenId.ChannelElements.OpenIdChannel.ProcessIncomingMessage(IProtocolMessage message) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\OpenId\ChannelElements\OpenIdChannel.cs:line 172
at DotNetOpenAuth.Messaging.Channel.ReadFromRequest(HttpRequestInfo httpRequest) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\Messaging\Channel.cs:line 386
at DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.GetResponse(HttpRequestInfo httpRequestInfo) in c:\Users\andarno\git\dotnetopenid\src\DotNetOpenAuth\OpenId\RelyingParty\OpenIdRelyingParty.cs:line 540
Appears that others are having the same problem: http://trac.dotnetopenauth.net:8000/ticket/172
Does anyone have a workaround?
Turns out this was an issue with using DotNetOpenAuth in a web farm environment.
When you create your OpenIdRelyingParty make sure you pass null in the constructor.
This puts your web site into OpenID stateless or 'dumb' mode. It's slightly slower for users to log in (if you even notice) but you avoid having to write an IRelyingPartyApplicationStore to allow DotNetOpenAuth to work across your farm;
var openIdRelyingParty = new OpenIdRelyingParty(null);
All this discussion revolves around the following question:
How does Relying Party (RP) make sure the request containing the authentication token is coming from the OP(OpenId Provider ) to which he forwarded the user’s request to?
Following steps explains how it happens
User Request comes to the Replying Party (RP), our website in our case
Application stores a unique signature corresponding to this user in a local signature store (LSS) and then embeds this signature in the Message and forward this Message to OpenId Provider(OP)
User types his credentials and the OP authenticates his Message and then forwards this Message, which has the signature still embedded in it, back to RP
RP compare the signature which is embedded in the Message to the signature which is in LSS and if they match RP authenticate the user
If the LSS vanishes (somehow) before the Message comes back from OP there is nothing for RP to compare the signature with thus it fails to authenticate user and throws error: Message signature was incorrect.
How can LSS Vanish:
ASP.net refreshes the application pool
IIS is restarted
In web farm the Message is served by application hosted on different server
Two solutions to this issue:
RP run’s in dumb mode
a. It does not store and signature locally and thus does not use signature comparison to make sure the Message is coming from the OP to which he forwarded the user to for authentication
b. Instead, once RP received the authentication Message from the OP it send the Message back to OP and ask him to check if he is the one who has authenticate this user and is the originator of the Message. If OP replies Yes I am the originator of this Message and I have created this message then the user is authenticated by RP
Implement your own persistence store that does not vanish, not matter what ASP.net does to the process, much like using SQL to store session state.
We fixed this issue by implementing IRelyingPartyApplicationStore (IOpenIdApplicationStore in newer versions of DotNetOpenAuth) and adding the store class name to the .config
<dotNetOpenAuth>
<openid ...>
<relyingParty>
...
<store type="some.name.space.MyRelyingPartyApplicationStore, some.assembly"/>
</relyingParty>
</openid>
...
</dotNetOpenAuth>
The interface is a composition of two other interfaces with five members all together.
/// <summary>
/// A hybrid of the store interfaces that an OpenID Provider must implement, and
/// an OpenID Relying Party may implement to operate in stateful (smart) mode.
/// </summary>
public interface IOpenIdApplicationStore : ICryptoKeyStore, INonceStore
{
}
We used dumb mode as a quick fix to get up an running, but in the end you'll probably want something like this.