Spring Security SAML One Login Global Single Logout LogoutRequest Parsing Issue - spring

I am implementing Spring Security SAML with One Login. I have set all the configuration files and meta data is set.
I am able to get login work and logout is working if I logoff from the same application I logged in. In this scenario from SAML IDP I get LogoutResponse and Spring Security is able to parse and process it.
http://localhost:8080/web/saml/SingleLogout?SAMLResponse=..............
Problem is when I login in two applications, currently I login to One Login admin console, there is a link to my app, I click on it and I am able to login directly in my application, now when I log off from One Login admin console, my application gets LogoutRequest.
http://localhost:8080/web/saml/SingleLogout?SAMLRequest=.........
Spring Security parses it fine and passes the object to a validation check logic.
org.springframework.security.saml.websso.processLogoutRequest(SAMLMessageContext context, SAMLCredential credential)
This method has following check.
// Make sure request was authenticated if required, authentication is done as part of the binding processing
if (!context.isInboundSAMLMessageAuthenticated() && context.getLocalExtendedMetadata().isRequireLogoutRequestSigned()) {
throw new SAMLStatusException(StatusCode.REQUEST_DENIED_URI, "LogoutRequest is required to be signed by the entity policy");
}
I tried to follow the trace but the context object's field inboundSAMLMessageAuthenticated is never set to true. The above check fails and exception is thrown.
In debug mode I explicitly chencged the value to true, it went ahead but there is one more issue.
In the same method there is another check.
try {
// Fail if NameId doesn't correspond to the currently logged user
NameID nameID = getNameID(context, logoutRequest);
if (nameID == null || !equalsNameID(credential.getNameID(), nameID)) {
throw new SAMLStatusException(StatusCode.UNKNOWN_PRINCIPAL_URI, "The requested NameID is invalid");
}
} catch (DecryptionException e) {
throw new SAMLStatusException(StatusCode.RESPONDER_URI, "The NameID can't be decrypted", e);
}
The method equalsNameId is as follows.
private boolean equalsNameID(NameID a, NameID b) {
boolean equals = !differ(a.getSPProvidedID(), b.getSPProvidedID());
equals = equals && !differ(a.getValue(), b.getValue());
equals = equals && !differ(a.getFormat(), b.getFormat());
equals = equals && !differ(a.getNameQualifier(), b.getNameQualifier());
equals = equals && !differ(a.getSPNameQualifier(), b.getSPNameQualifier());
equals = equals && !differ(a.getSPProvidedID(), b.getSPProvidedID());
return equals;
}
Here it fails on differ(a.getFormat(), b.getFormat())
Question
I am not sure is there something I am missing, kind of lost where exactly to check to tackle this issue.
My binding for Single Logout is HTTP-Redirect.
Would appreciate if pointers are provided. Let me know if more information is required.
Thanks for time.
Stack (Legacy Application):
Spring 3.0.6
Spring Security 3.1.2
Spring Security SAML 1.0.0
Tomcat 7.x

Having encountered this two years after the original post, I had to do some further research. I still have some reading to do regarding the SAML specifications, but I think I found an entry in SAML 2.0 Errata on the structure of the NameIDType which underlies the NameID and Issuer. All four elements in this type are optional. OneLogin appears to be following this document and does not send a NameID.Format in the SingleLogout request.
So, the inboundMessage has a nameID with a null format while the credential's nameID has a format of "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress". This is what causes the getFormat line to return false and fail the entire check.

Related

Unable to find the group information of the logged in user -JWT token OKTA

I am new to Okta so apologies if my questions are not clear.
So what I want to do is basically parse the JWT token generated by
okta and extract the group information of the logged in user
associated with it.
I am under the impression that this information should be there in the
OidcUser object. I do see user name/email id / token validity etc
information inside this object. Unfortunately I can't see group id
which I need for further processing.
#RequestMapping("/")
public String hello(#AuthenticationPrincipal OidcUser user){
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> entry : user.getClaims().entrySet()) {
sb.append(entry.getKey() + ":" + entry.getValue().toString());
sb.append("\n");
}
sb.append("|");
sb.append(user.getClaims());
return sb.toString();
}
Here is my okta plugin inside spring boot
okta.oauth2.issuer=https://dev-XXXXXXXX.okta.com/oauth2/default
okta.oauth2.client-id=XXXXXXXXXX
okta.oauth2.client-secret=XXXXXXXXXXXX
I am wondering if my approach is proper and what more I need to do to extract User group from Okta JWT token.
To get user groups you need to make an additional request to /userinfo endpoint, assuming you requested groups scope during /authorize call.
Please see a guide here
Not exactly spring-boot response, but it's always beneficial to know how things work under-the-hood

Migrating User to Cognito on Sign In

I am trying to migrate users to Cognito when they sign in the first time. For this I wrote a lambda function that does call an API to check if the users exist in db or not ? if the user exists, it will be created in cognito but I am not sure how do I tell the application that user is created and it should allow the user to login .
Here is the code in c#:
public async Task<Stream> FunctionHandlerAsync(Stream stream, ILambdaContext context)
{
RootObject rootObj = DeserializeStream(stream);
User user = new User(rootObj.userName, rootObj.request.password);
ApiResponse apiResponse = await MobileAuthenticateAsync(user.UserName, user.Password);
// Considering apiResponse returns "user authenticated", we create the user in //cognito. This is working.
// How do I send response back to Application so it knows that user is // //created and authenticated and should be allowed to login.
//Before returning stream, I am setting following 2 status.
rootObj.response.finalUserStatus = "CONFIRMED"; // is this correct ?
rootObj.response.messageAction = "SUPPRESS";
return SerializeToStream(rootObj);;
}
You're pretty close.
You can see the full documentation on the Migrate User Lambda Trigger page, however in short you need your response to look like:
{
response: {
userAttributes: {
email: 'user#example.com',
email_verified: true,
custom:myAttribute: 123,
},
finalUserStatus: 'CONFIRMED',
messageAction: 'SUPPRESS',
forceAliasCreation: false,
}
}
Where:
userAttribute: this is a dictionary/map of the user's attributes keys in cognito (note that any custom attributes need to be prefixed with custom:), to the values from the system you're migrating from. You do not need to provide all of these, although if you're using an email alias you may want to set email_verified: true to prevent the user having to re-verify their e-mail address.
finalUserStatus: if you set this to CONFIRMED then the user will not have to re-confirm their email address/phone number, which is probably a sensible default. If you are concerned that the password is given as plain-text to cognito this first-time, you can instead use RESET_REQUIRED to force them to change their password on first sign-in.
messageAction: should probably be SUPPRESS unless you want to send them a welcome email on migration.
forceAliasCreation: is important only if you're using email aliases, as it stops users who manage to sign-up into cognito being replaced on migration.
If you respond with this (keeping the rest of the original rootObj is convenient but not required then the user will migrated with attributes as specified.
If you throw (or fail to respond with the correct event shape) then the migration lambda fails and the user is told that they couldn't migrated. For example, because they do not exist in your old user database, or they haven't provided the right credentials.

Saml2 Single Logout (SingleLogoutServiceResponseUrl) with Sustainsys and Identity Server 4

I am using Sustainsys Saml2 with Identity Server 4. A customer has asked me if we support support SAML Single Logout.
They have asked for:
Single Logout Request URL
Single Logout Response URL
From what I can see this is probably supported by Sustainsys given the following properties exist.
var idp = new Sustainsys.Saml2.IdentityProvider(new EntityId("https://sso.acme.com"), opt.SPOptions)
{
MetadataLocation = "/metadata/sso-meta.xml",
LoadMetadata = true,
AllowUnsolicitedAuthnResponse = true,
SingleLogoutServiceResponseUrl = "INSERT",
SingleLogoutServiceBinding = Saml2BindingType.HttpRedirect
};
I have two questions:
I can only see one property which matches their request - the SingleLogoutServiceResponseUrl (I don't see a property for the SingleLogoutServiceRequestUrl). How do I configure the Single logout request Url?
How do I determine what the values are for these Url's?
Thanks
Outbound logout requests are sent to the SingleLogoutUrl configured on the Idp. The SingleLogoutResponseUrl is a special one - it's only used when responses should be sent to a different endpoint on the Idp than requests. Normally they are the same and if SingleLogoutResponseUrl is not set, the SingleLogoutUrl is used for both responses and requests.
Ask the Idp people for those.
And as an additional note: You're loading metadata. Then everything should already be in the metadata and you can shorten your code to
var idp = new Sustainsys.Saml2.IdentityProvider(new
EntityId("https://sso.acme.com"), opt.SPOptions)
{
MetadataLocation = "/metadata/sso-meta.xml",
AllowUnsolicitedAuthnResponse = true,
};

Spring Security #PreAuthorize - Restrict certain roles by using Spring EL

Using Spring Security 3.1.3.RELEASE
So if there are a list of roles (over 10) and there is a need to block just ONE from accessing a Spring Controller method. Can this be done using Spring Expression Language, and avoid listing each and very accepted role?
For example, by including the Not sign.
#PreAuthorize("!hasRole('ROLE_FREE_USER')")
over listing all the roles like this
#PreAuthorize("hasAnyRole('ROLE_ADMIN','ROLE_PAID_USER','ROLE_PREM_USER',...)
I've looked at the documentation over here: http://static.springsource.org/spring-security/site/docs/3.0.x/reference/el-access.html
But there seems to be nothing on the NOT EQUAL to cases. Anyone face similar issue?
I'm pretty sure that NOT-sign (!) is supported in Spring Expression Language (SPEL). Naturally, it returns a boolean result.
An Example from the official documentation:
// evaluates to false
boolean falseValue = parser.parseExpression("!true").getValue(Boolean.class);
// -- AND and NOT --
String expression = "isMember('Nikola Tesla') and !isMember('Mihajlo Pupin')";
boolean falseValue = parser.parseExpression(expression).getValue(societyContext, Boolean.class);
Spring Expression Language didn't work for me in this case. Initially I tried with the following,
#RequestMapping("/post/edit")
#PreAuthorize("hasRole('ROLE_OWNER') AND !hasRole('ROLE_ADMIN')")
public String editPost(Model model, Principal principal, HttpServletRequest request, #RequestParam("postId") String postId) {
}
However, I had to recheck the Role from inside the method and redirect the page in case user has Admin Privileges.
#RequestMapping("/post/edit")
#PreAuthorize("hasRole('ROLE_OWNER')")
public String editPost(Model model, Principal principal, HttpServletRequest request{
//Admin Users does not have access to post edit page
if (request.isUserInRole("ROLE_ADMIN")) {
return TemplateNamesConstants.POST_WALL;
}
}
Do update this thread in case you found a better/alternate solution.

Spring security, bypass login page after changing password

I'm relatively new to Java/Spring/MVC. I hope someone can shine some light on this, because this problem is about to ruin yet another weekend without getting solved.
At the moment I'm working on a project, implementing a couple of RFC's. Each RFC has it's own 'challenges', ranging from changing hibernate queries and stored procedures to minor textual changes and adding extra fields to forms.
The problem I am now facing has (I think) to do with Spring security.
The present situation:
To login to the application, user is presented by a login page (/user/login.jsp). After entering a username and password, user is sent to welcome page (/welcome/hello.jsp).
If a user forgets their password, they use a link that will sent an email providing a link to a page (/welcome/newPassword?code=xxx) where a new password can be entered. Entering the Submit-button will do some validation, save the password in the database and displays a 'password changed' message on the page.
To enter the application, user will now have to login with the new password.
What the customer wants:
After submitting the new password, the user should be automatically logged in with the new password and redirected to /welcome/hello.jsp
I searched the web, but couldn't find the scenario I was looking for (or maybe didn't recognize it!).
What I thought would do the trick:
I have this method in WelcomeController which presently handles the new password:
#RequestMapping(method = RequestMethod.POST)
public void newPassword(Model model, HttpSession httpSession, ModelAttribute(PASSWORD) Passwords password, BindingResult result) {
logger.debug("Posting password for new password");
ScreenMessage message = null;
//
try {
// errors from initBinder - back to page
if (result.hasErrors()) {
return;
}
// Validate input: on error go back to form
passwordValidator.validate(password, result);
if (result.hasErrors()) {
return;
}
// Save password
passwordService.createNewOwnPassword(result, password);
if (result.hasErrors()) {
return;
}
// No errors
message = new ScreenMessage();
message.setText(CHANGED);
message.setArg(THIS_PASSWORD, 0);
model.addAttribute(MESSAGE, message);
httpSession.setAttribute(ACTION_READ, Boolean.TRUE);
// Errors are fatal.
} catch (Exception e) {
logger.error("ERROR-CHANGE-password for user: " + password.getUser().getLoginName(), e);
result.reject( ERROR_FATAL, new Object[] { password}, ERROR_FATAL);
} finally {
model.addAttribute(PASSWORD, password);
}
}
I thought I could simply change this method to have it return a ModelAndView and use redirect:
ModelAndView mav = new ModelAndView("redirect:/welcome/hello", (ModelMap) model);
return mav;
But: It keeps sending me to /user/login, exactly the page I don't want to go to...
Extra information that could be handy:
As far as I can tell the application is using Spring 2.5.
(I hope relevant) Part of my security-applicationContext.xml:
<!-- always-use-default-target="true" -->
<security:form-login login-page="/dyn/user/login"
login-processing-url="/dyn/user/j_spring_security_check"
default-target-url="/dyn/welcome/hello"
always-use-default-target="true"
authentication-failure-handler-ref="exceptionTranslationRouting" />
<security:access-denied-handler error-page="/dyn/welcome/hello" />
<security:logout logout-success-url="/dyn/user/logout" invalidate-session="true" />
Is my idea of redirecting to /welcome/hello too simple or am I just missing something small?
Firstly, you'd better move your logic from web controller (I don't like password validation) to component dedicated to password change. This will allow you to logically separate parts of your application. Filter maybe? (more later) Or special service?
Secondly, take a look at the source code of Spring Security. From my experience, I can tell you that any customization in Spring Security is only possible if you understand its architecture (especially filter chain). I would recommend taking look at class AbstractAuthenticationProcessingFilter.
It's responsible for authenticating users when they provide username and password in your application. Take a look at the method AbstractAuthenticationProcessingFilter.successfulAuthentication.
This line of that method sets security context which makes the user logged in:
SecurityContextHolder.getContext().setAuthentication(authResult);
Reference authResult is of type Authentication. It is an interface implemented by for instance UsernamePasswordAuthenticationToken. Take a look at UsernamePasswordAuthenticationFilter.attemptAuthentication method for example of how to construct such an object.
Going back to filter solution:
I personally would think about implementing my own filter that would extend AbstractAuthenticationProcessingFilter. It would be responsible for password change. That way you don't have to think about things like session change on successful authentication. You just need to inject it in you XML.
To sum up,
classes that are recommended to look at:
AbstractAuthenticationProcessingFilter
UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationFilter
You should read Spring Security Documentation, especially parts about architecture, filter chain and its role in authenticating users.
http://static.springsource.org/spring-security/site/docs/3.1.x/reference/springsecurity-single.html#overall-architecture

Resources