Grails & Spring Security - Saved request - Empty parameters and unbound command object - spring

I have a controller which accepts a form POST. This method/action of the controller is protected by Spring Security. In this situation the user's session has expired and they click the submit button.
Spring security is creating a saved request and redirecting the user to the login page. Upon logging in Spring Security is redirecting to the POST url for the form. However, params within the controller method/action is empty (except for controller and action name) and the command object is unpopulated.
Using the some simple code within the onInteractiveAuthenticationEvent I can see that the Saved Request has all the parameters in the Parameter Map.
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
def request = org.codehaus.groovy.grails.plugins.springsecurity.SecurityRequestHolder.getRequest()
def response = org.codehaus.groovy.grails.plugins.springsecurity.SecurityRequestHolder.getResponse()
org.springframework.security.web.savedrequest.SavedRequest savedRequest = new org.springframework.security.web.savedrequest.HttpSessionRequestCache().getRequest(request, response);
println savedRequest
if (savedRequest) {
savedRequest.getParameterMap().each { k, v ->
println "${k}: ${v}"
}
}
}
Any ideas as to why the params and command object for the controller action/method are empty and unbound? I would expect that after successful login the saved request would be used to populate both params and then in turn the command to bind to the parameters.
I have tested this with both Grails 2.0.4, Spring Security Core Plugin 1.2.7.3 as well as Grails 2.4.2 and Spring Security Core Plugin 2.0-RC3.

Since this appears to be left to you (the user of Spring Security) to implement I decided to do so using a Grails filter. I know it could have been implemented using a Spring Security filter and placed in the filter chain (as indicated by the Spring Security API documentation) but I needed to move on to other things.
So, here is an example of what I did.
package com.example
class SavedRequestFilters {
def filters = {
// only after a login/auth check to see if there are any saved parameters
savedRequestCheck(controller: 'login', action: 'auth') {
after = {
org.springframework.security.web.savedrequest.SavedRequest savedRequest = new org.springframework.security.web.savedrequest.HttpSessionRequestCache().getRequest(request, response)
if (savedRequest) {
// store the parameters and target uri into the session for later use
session['savedRequestParams'] = [
uri: savedRequest.getRedirectUrl(),
data: savedRequest.getParameterMap()
]
}
}
}
all(controller:'*', action:'*') {
before = {
// if the session contains dr saved request parameters
if (session['savedRequestParams']) {
def savedRequestParams = session['savedRequestParams']
// only if the target uri is the current will the params be needed
if (savedRequestParams.uri.indexOf("/${controllerName}/") > -1) {
savedRequestParams.data.each { k, v ->
params[k] = v.join(",")
}
// clear the session storage
session['savedRequestParams'] = null
}
}
}
}
}
}

Related

HttpContext.Session.Abandon() doesn't work in MVC core. Session.clear doesn't log my user out

I get an error that says "ISession does not contain a definition for 'Abandon' and no accessible extension method 'Abandon' accepting a first argument of type 'ISession' could be found".
I have tried using session.clear but even after logging out if I open the website the user is logged in.
This is the error I get
This is how I have implemented Session in my ASP .NET CORE project:
Create a SessionTimeout filter:
public class SessionTimeout : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Session == null ||!context.HttpContext.Session.TryGetValue("UserID", out byte[] val))
{
context.Result =
new RedirectToRouteResult(new RouteValueDictionary(new
{
controller = "Pages",
action = "SessionTimeout"
}));
}
base.OnActionExecuting(context);
}
}
Register this filter in your Startup.cs:
In your ConfigureServices method:
services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(10);
});
In your Configure add:
app.UseSession();
And finally decorate your class/method with your filter like:
[SessionTimeout]
public class DashboardController : Controller
To destroy your session based on Logout event from your View:
public IActionResult Logout()
{
HttpContext.Session.Clear();
return RedirectToAction("Login", new { controller = "Pages" });
}
It seems my session is being stored in cookies and not getting cleared/deleted when used session.clear()
so I've used this and it seems to work like a charm.
foreach (var cookie in Request.Cookies.Keys)
{
if (cookie == ".AspNetCore.Session")
Response.Cookies.Delete(cookie);
}
HttpContext.Session.Clear() wasn't working for me on my live site in the Controller for my Account/Logout page.
I found out that setting a href of /Account/Logout/ was the problem. I changed my links to /Account/Logout.
If you're having Session problems in .NET Core 3.1 then try this. It may also account for why I couldn't get Cookie Authentication to work - I gave up in the end and switched to using Sessions.

RestAssured testing, get user token

What I want to do: I want to test my endpoint using RestAssured. The key is that the endpoint is available only for users who are logged in. For logging in I'm using spring security default endpoint with custom successHandler in which I'm setting some random token, saving it to database and returning in header "User-Token". I'm not creating a session on the back end. When I want to access a secured endpoint, front-end makes a call to it, with "User-Token" header. Then I'm using the token for checking in the database. Each token is different and random. Also I don't use any spring-security things for token. Now I want to test this behavior.
Technologies: React & Redux, Spring Boot, RestAssured, JUnit, Tomcat
What's not working: First of all, I'm not really sure how to obtain the token. I mean I can force it by hand to database to some test user, but AFAIK it's a bad bad practice. I read the documentation and come across part about auth().form. But below it was mentioned that it's not the best approach as have to made to the server in order to retrieve the webpage with the login details and it's not possible - webpage is totally separated from backend. I did try the approach nevertheless but it didn't work.
#Before
public void LogInUser(){
String loginUrl = "http://localhost:8080/login";
userToken =
given().auth().form("username","password").
when().get(loginUrl).getHeader("User-Token");
System.out.println(userToken);
}
So then I thought that maybe I don't need auth() at all - I don't need session, so calling the endpoint itself with data should be enough. I checked how data is passed from front-end to back-end and did this:
Form Data: username=something&password=something
#Before
public void LogInUser(){
String loginUrl = "http://localhost:8080/login";
userToken =
given().parameter("username=oliwka&password=jakies")
.when().get(loginUrl).getHeader("User-Token");
System.out.println(userToken);
}
And while it's passing, userToken is null. It's declared as class variable not method variable and it's String.
How can I obtain token for user and test my endpoint for which I need a token?
You can use below procedure to get the access token.
Step 1 : Create a method that will accept a json string and parse the data and return the access token. below is the method. You can use your preferable json parser library.
public String getAccessToken(String jsonStr) {
JSONParser parser = new JSONParser();
Object obj = null;
try {
obj = parser.parse(jsonStr);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JSONObject jsonObject = (JSONObject) obj;
String accessToken = (String) jsonObject.get("access_token");
System.out.println("access_token : " + accessToken);
return accessToken;
}
Step 2 : Now call your login api with username and password like below
String loginUrl = "http://localhost:8080/login";
Response res = null;
String returnValue = "";
response = given().param("username", "yourUserName")
.param("password", "yourpassword")
.param("client_id", "If Any otherwise skip it")
.param("grant_type", "If Any otherwise skip it")
.param("clear_all", "true")
.post(loginUrl);
returnValue = response.body().asString();
String accessToken = getAccessToken(returnValue);
Please let me know if you can get your desired access token.

RedirectAttributes in Spring MVC 3.2.8

I have a project based in Spring Web model-view-controller (MVC) framework. The version of the Spring Web model-view-controller (MVC) framework is 3.2.8.
I have this method
#RequestMapping(value = { "/newdesign/manage/device/award",
"/newdesign/manage/device/award/"}, method = {RequestMethod.POST})
public String awardDeviceProduct(
#ModelAttribute("deviceForm") DeviceForm deviceForm,
HttpServletRequest request,
Model model,
final RedirectAttributes redirectAttributes) throws Exception {
checkUser (request, UserRole.MARKETING);
Device device = manageLicenseService.getDeviceById(deviceForm.getDevice().getId());
if (deviceForm.getDevice().getIos()==null) {
model.addAttribute ("errorMessage", "Licence Number cannot be null !");
redirectAttributes.addFlashAttribute("errorMessage", "Licence Number cannot be null !");
} else if (deviceForm.getSelectedItems()!=null &&
!deviceForm.getSelectedItems().isEmpty()) {
// check LICENCE DUPLICATED
manageLicenseService.applyStatusChange (device, deviceForm.getSelectedItems(), Status.AWARDED );
} else {
model.addAttribute ("errorMessage", "no Items selected !");
model.addAttribute ("productGroup", getNotExpiredProductGroups (request));
}
return "redirect:/newdesign/manage/device/" + deviceForm.getDevice().getId();
}
But in the JSP I can't find the attribute "errorMessage" !!!! when (deviceForm.getDevice().getIos()==null)
The problem with your code relies in the difference between "redirect" and "forward".
If you return with a redirect statement, the response will first return to the browser, and then it will request the new url. The problem with this approach is, that the redirected new request will have a completely new context, and will not have access to the Model, set in your previous response.
The forward response however is processed by the server side itself, transferring the request to the new URL. It is faster and the context can be maintained.
You can find more details here

How to customize the System.Web.Http.AuthorizeAttribute with Microsoft.Owin.Security?

I've implemented a custom AuthorizeAttribute in my WebAPI (note that this is different from the MVC AuthorizeAttribute).
I've overridden the OnAuthorization method. In this method I check if the user is authenticated. If not authenticated, I challenge the user to login.
Part of my custom logic is to check authenticated users if they are authorized to continue (basically I check their name/email. if it exists in a predefined list, then they have access).
The issue I see is this:
After the user successfully authenticates BUT FAILS to be authorized, I see that there is an infinite loop redirection to the login page.
Again, the challenege for user credentials is in the OnAuthorization method.
What might be causing this infinite looping, and how to prevent this once user has been determined to have no authorization?
* Updated with snippet *
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
base.OnAuthorization(actionContext); // Should this be here?
var owinContext = HttpContext.Current.GetOwinContext();
var authenticated = owinContext.Authentication.User.Identity.IsAuthenticated;
var request = System.Web.HttpContext.Current.Request;
if (!authenticated)
{
// Challenge user for crednetials
if (!request.IsAuthenticated)
{
// This is where the user is requested to login.
owinContext.Authentication.Challenge(
new AuthenticationProperties { RedirectUri = "/" },
WsFederationAuthenticationDefaults.AuthenticationType);
}
}
else
{
// At this point the user ia authenticated.
// Now lets check if user is authorized for this application.
var isAuthorized = SecurityHelper.IsUserAuthorized();
if (isAuthorized)
{
// authorized.
return;
}
// not authorized.
actionContext.Response = new System.Net.Http.HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);
}
}
You could try removing OnAuthorization and adding this:
protected override bool IsAuthorized(HttpActionContext actionContext)
{
var owinContext = HttpContext.Current.GetOwinContext();
var authenticated = owinContext.Authentication.User.Identity.IsAuthenticated;
return authenticated & SecurityHelper.IsUserAuthorized();
}
I don't get why you're redirecting on failed authentication, surely an API should just return 401?
I'm wondering about this bit of code right here:
actionContext.Response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
Somewhere you must be configuring your OWIN layer using something like the following:
var cookieAuthenticationOptions = new CookieAuthenticationOptions
{
LoginPath = new PathString(loginPath)
}
app.UseCookieAuthentication(cookieAuthenticationOptions);
When you return a 401 from the authentication filter the OWIN infrastructure is automatically going to redirect you to whatever LoginPath you specified. But when trying to satisfy that request it's invoking your filter, but because the user isn't authorized it returns a 401 which causes a redirect to the LoginPath, and so on, and so on.
Because this is an API call you need to handle the 401 differently. The following blog post talks about this situation.
http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/
In a nutshell, when configuring your CookieAuthenticationOptions you need to specify your own Provider and only direct if it's not an AJAX request.
var cookieAuthenticationOptions = new CookieAuthenticationOptions
{
LoginPath = new PathString(loginPath),
Provider = new CookieAuthenticationProvider()
{
OnApplyRedirect = context =>
{
if (!context.Request.IsAjaxRequest())
{ context.Response.Redirect(context.RedirectUri); }
}
}
}

Cannot get session ID for "remember me" users inside spring security login event listener

I'm writing a web app using grails and spring-security 3.0.3 which requires me to keep a databse record of all successful logins, including the username, time, ip, and sessionId.
I have created a login listener as so(groovy code):
class DpLoginListener implements ApplicationListener<InteractiveAuthenticationSuccessEvent> {
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
def source = event.getSource()
def principal = source.principal
def details = source.details
TrafficLogin.withTransaction {
new TrafficLogin(userId: principal.id, ipAddress: details.remoteAddress, sessionId: details.sessionId ?: 'remember me').save(failOnError: true)
}
}
}
This fires as expected when a user logs in, but when someone comes back to a site as a "remember me" user, the sessionId is null. Is this the expected behavior? Shouldn't a session be created by the time login has succeeded?
I tried to workaround this by adding a separate SessionCreationEvent listener, which would find the latest databse login record for the user and update that row with the correct sessionId as soon as the session exists, but it seems that this session creation event never fires, and I can't figure out why.
The session creation listener looks like this:
class DpSessionCreatedListener implements ApplicationListener<SessionCreationEvent> {
void onApplicationEvent(SessionCreationEvent event) {
def source = event.getSource()
def principal = source.principal
def details = source.details
TrafficLogin.withTransaction {
def rememberMe = TrafficLogin.find("from TrafficLogin as t where t.userId=? and t.sessionId='remember me' order by t.dateCreated desc", principal.id)
if (rememberMe) {
rememberMe.sessionId = details.sessionId
rememberMe.save(failOnError:true)
}
}
}
}
And a bean is defined for it in my resources.groovy file, in the same manner as the login listener, which fires fine.
Any ideas how to correctly set this sessionId?
Not related to grails, but this SO question may offer a workaround if grails supports it.

Resources