1

Hi all I have a newbie grails / spring security question. We set up oauth2 in our grails 3 project using spring-security-oauth2-provider and things seem to work for protecting our REST APIs.

But then we started to add a Web front end in the same project using GSPs and came to a crossroads. Usually oauth2 works by authenticating to an end point and receiving a token, with which it can use in an HTTP header in subsequent requests to keep accessing protected resources. But my Web front end has a login page. So initially we thought to treat the Web front end as one of the clients (we have 1 client for our iOS app and 1 client for our Android app, so why not also have 1 for our web app). But it seems odd to make an HTTP request from our controller code (for logging in) to our oauth2 provider end point because it's in the same project; and most subsequent requests that my Web front end needs to make, we want to directly access underlying services and domain objects so adding an extra hop seems counter-productive.

So what we have opted for is, when I login using my login.gsp, in the controller code, I bypass oauth2 login and just do a straight spring security authentication by using authenticationManager.authenticate() with a UsernamePasswordAuthenticationToken that I construct from the username and password fields passed into my form and then calling SecurityContextHolder.getContext().setAuthentication() on the response. This sort of half solves my problem because I was told from this post that doing so will only set up SecurityContextHolder on the current thread, and since SecurityContextHolder is cleared at the end of the filter chain, subsequent requests won't be authenticated. And so what I did was if this authentication passes (i.e. no exceptions thrown), then I put my user object in the HTTP session and in all subsequent requests, try to retrieve it as a way of "telling" that I am authenticated. But this seems both hacky and dirty; and it leads to my user object not being attached to a DB session (causing lazy initialization exceptions).

I did find other similar suggestions from posts like this which puts the entire SecurityContext in the HTTP session but don't seem to indicate how to use that SecurityContext in subsequent requests.

I guess my ultimate question is, are we going about this the wrong way? Is there a better and cleaner way of accomplishing what I want to do? I suppose we can't be the first people to try to do this.

2 Answers 2

1

A bit sad that I'm answering my own question. But I wanted to see whether my solution is a correct way of solving this problem.

So what we ended up doing was to use Filters. We know that Interceptors should be the way to go in Grails 3 but before getting there we just want to have a working version with Filters.

In our AuthController.signIn method, we go through the UsernamePasswordAuthenticationToken authentication process and actually shove the SecurityContext into the HTTP Session. As an aside, funny enough, I later found that this is actually already done for me (using the "SPRING_SECURITY_CONTEXT" key) somehow, without me having to explicitly do the session.setAttribute. My rationale is that since the SecurityContext is only authenticated for this request using the UsernamePasswordAuthenticationToken, I need to keep it in the HTTP session so I can reload it for subsequent requests. So in my filter that I placed sequentially as the first, I check for the SecurityContext in the HTTP session and copy its "authentication" into the current SecurityContext. Note that we also already have a filter, sequentially placed last, to redirect the user to the login page if nothing was found in the HTTP Session.

With this change we are now able to access springSecurityService within our Controller to access the currently logged in user (before we could not). And other niceties we get include being able to use sec:ifAllGranted in our GSP pages. But something is still not feasible - we still can't seem to use the @Secured("#oauth2.isUser()") annotation to protect our Controller methods, always hitting 401. Maybe our using UsernamePasswordAuthenticationToken to do authentication is still not the "complete" authentication we need?

I tried further by shoving into HTTP Session the OAuth2 Access token instead of the SecurityContext, and in my filters adding the "Authorization" HTTP header with the value of "Bearer "+accessToken. This doesn't work and preliminary logging showed that in one HTTP request (page view), I come into my filter numerous times, and only the first time it was successful in getting the OAuth2 access token to put into the request's HTTP headers.

So at the end of the day, what I wanted to find out is, was my approach correct above, to use filters to keep the user connected in a session? Or is there a different way I should have done this? Also how do I make it possible to protect my Controller with the @Secured annotation?

Sign up to request clarification or add additional context in comments.

1 Comment

I shouldn't be surprised anymore that I'm answering my own question again. Perhaps it's because the answer was too obvious, or perhaps no one has ever encountered this; either way, I'm just going to "blog" my journey here on SO in case someone else finds this useful. The short answer to my question is, no - all that is absolutely unnecessary. Spring Security comes with a lot of default / built-in functionality that we can configure. But as comments have a max of 600 characters, I'm going to post my addendum in the form of another answer, but not before I exhaust all the space I'm given.
1

I wonder how SO will treat this answer given that I have already given answer. Alas, who really cares - I'm the one who asked the question in the first place anyway.

Through all the documentation I have looked at, e.g. Grails Security - Reference Documentation and Spring Security Core Plugin - Reference Documentation and Grails Spring Security Core Plugin Custom Authentication and How to customize spring security plugin Login page in grails I was able to find that even though we are using the Spring Security OAuth2 Plug-In, we were also given all the facilities that Spring Security Core Plug-In provided. This means that form-based login is supported out of the box, and customized login form is also supported.

And with custom login form, instead of having the login.gsp call our own AuthController's signIn method, we could still use the "authenticate" method of LoginController (that came with the Spring Security plug-in) using ${request.contextPath}/login/authenticate.

So I actually tried that, after disabling our Interceptors first of course but then ended up having a redirect loop that caused the browser to bail. I think there's some configuration that we needed to do (with chainMap or filters) that we don't have, that's why it still doesn't work.

One thing that helped was to add the following two lines in grails-app/conf/logback.groovy

logger 'org.springframework.security', DEBUG, ['STDOUT'], false
logger 'grails.plugin.springsecurity', DEBUG, ['STDOUT'], false

This gave me a lot of logging information to help debug. For some reason, I realized that it somehow shoved an anonymous token in the request for me because something wasn't authenticated.

After spending some time debugging, I finally found that the answer lies in application.groovy. The problem I had was that since all apps by default has a Spring Security filterChain that has a last line of /** matching JOINED_FILTERS. And the line that we added when installing Spring Security OAuth2 Provider has /** match JOINED_FILTERS minus a bunch of other filters. That line is actually the correct one, but because we never removed the original /** line and the order of precedence is that one comes before this one. So all I needed to do is to remove the first line of /** matching to JOINED_FILTERS and the login now works.

At the end, the lines we have in the filterChain are:

[pattern: '/rest/**', filters: 'JOINED_FILTERS,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-oauth2BasicAuthenticationFilter,-exceptionTranslationFilter'],
// We want all the other resources to be Web-based
[pattern: '/web/**',  filters: 'JOINED_FILTERS,-statelessSecurityContextPersistenceFilter,-oauth2ProviderFilter,-clientCredentialsTokenEndpointFilter,-oauth2BasicAuthenticationFilter,-oauth2ExceptionTranslationFilter'],
// This is just a catch-all that defaults to the same logic as before, it also would match things like /oauth/** or /auth/**
[pattern: '/**',      filters: 'JOINED_FILTERS']

And we also have a few other configuration for overrides that pointed to our own AuthController:

grails.plugin.springsecurity.auth.loginFormUrl = '/auth/login' // This is our own Login Form
grails.plugin.springsecurity.failureHandler.defaultFailureUrl = '/auth/login' // Exception message shown in controller
grails.plugin.springsecurity.adh.errorPage = '/auth/denied' // Copied from LoginController

1 Comment

For what it's worth, I've found your question and answers very helpful. I have inherited an old Grails 2 project which I have upgraded to Grails 6 and I'm due to integrate it with our Okta OAuth2 solution soon whilst maintaining our existing Spring Security/MySQL username and password authentication Login page. What you've written here, and the resources you have referenced, has helped me get unstuck and understand how best to move forward.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.