I have this service class:
class UserService {
def springSecurityService
#Listener(topic = 'currentUser', namespace = 'auth')
public User getCurrentUser(){
return springSecurityService.currentUser
}
def authenticate(OAuthToken oAuthToken){
SecurityContextHolder.context.authentication = oAuthToken
return springSecurityService.currentUser
}
}
If I use the authenticate method, springSecurityService.currentUser returns the current user. But if I am already logged in I want to get a current user with getCurrentUser method and it returns null. I debugged the Springsecurityservice.getCurrentUser method and the method "isLoggedIn" returns false.
But if I try this in my view it works:
<sec:ifLoggedIn>Yeeees</sec:ifLoggedIn>
UPDATE:
When I run UserService.getCurrentUser the SCH.context.authentication in SecurityService returns null. After that call, the view is being rendered and SCH.context.authentication returns a right authentication.
UPDATE 2:
I analysed it and it seems to happen only if I use it with event bus. If I call getCurrentUser directly from controller, it works fine, but if I use event bus it doesn't.
I use Event Bus from platform-core plugin. Is there another SCH.context for Event Bus?
I found a solution. The problem was that Event Bus works within a thread and spring context will not be delivered to threads by default.
The option 'context holder strategy' in config solve this:
grails.plugin.springsecurity.sch.strategyName = org.springframework.security.core.context.SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
http://grails-plugins.github.io/grails-spring-security-core/latest/#miscProperties
You can get the principal and then search the user, in mycase the domain is AppUser
def user = springSecurityService.getPrincipal()
def testUser =AppUser.findByUsername(user.username)
Related
I have a modified User class in my spring security which use attribute email instead of username. Now I want to a functionality to switch users, so an admin can login seamlessly as a particular user without logging out. I came across this Link , which shows there is a switchUserFilter to achieve this. So I tried to get it working by passing j_username as email,but it gets redirected to a blank page and the user does not switch.
I have tried all these things but still could not figure out a way around it:
1) Added to Config.groovy:
grails.plugins.springsecurity.userLookup.usernamePropertyName='email'
2) Create a method in User class getUserName() to return email.
P.S: I looked into the source code of springSecurity switchUserFilter(link)and came across this code on line 209:
protected Authentication attemptSwitchUser(HttpServletRequest request)
throws AuthenticationException {
UsernamePasswordAuthenticationToken targetUserRequest;
String username = request.getParameter(usernameParameter);
But I am not sure if that is the issue and do not want to make changes in the plugin.
The usernameParameter property of the SwitchUserFilter is set to username by default. That does seem to be part of your problem.
The SwitchUserFilter has a method named setUsernameParameter() that allows you to change this default. It seems the filter is a bean, so you might be able to do something like this in grails-app/conf/spring/Config.groovy
import org.springframework.security.web.authentication.switchuser.SwitchUserFilter
beans = {
switchUserFilter {
usernameParameter = 'email'
}
}
Or maybe something like this in grails-app/config/BootStrap.groovy
def switchUserFilter
def init = { servletContext ->
switchUserFilter.usernameParameter = 'email'
}
Finally found the solution: Add this to the config.groovy file
grails.plugin.springsecurity.userLookup.usernamePropertyName = 'email'
grails.plugin.springsecurity.useSwitchUserFilter = true
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
....
'/j_spring_security_switch_user': ['ROLE_SWITCH_USER', 'isFullyAuthenticated()'],
'/j_spring_security_exit_user': ['isFullyAuthenticated()'],
'/public/**': ['permitAll']
.....
]
2) Then create a Role ROLE_SWITCH_USER in bootstrap.groovy
def switchUserRole = Role.findByAuthority('ROLE_SWITCH_USER') ?: new Role(authority: 'ROLE_SWITCH_USER').save(flush: true, failOnError: true)
And assign it to a super user
3) Then follow the instruction(here) to update the view to add a switch button
Using this link if we register a call back in grails, how to access springSecurityService in plain groovy/java class, so that we can get the current user domain class and save the login time?
Update:
I have done this using the below:
appCtx.springSecurityService.currentUser.id
If you are using the callback closures you can get the information from the AuthenticationSuccessEvent.
grails.plugin.springsecurity.onAuthenticationSuccessEvent = { e, appCtx ->
// handle AuthenticationSuccessEvent
println "User id ${e.authentication.principal.id} was authenticated"
}
I am running Grails 2.2.1.
I have a Grails service, which acts as an atmosphere handler..
Refer this link: https://bitbucket.org/bgoetzmann/grails-atmosphere-plugin/wiki/Home
I am trying to use spring security service inside this, in your simple way, ie, by injecting it via def springSecurityService.
But when a service hits the handler,
springSecurityService.getCurrentUser() returns null.
User is logged in, I am able to get him in my controllers. I think the service is not injecting somehow.
After some research I came across this question
Injecting service into Grails Atmosphere Handler class
but both answers are outdated...
Please help!
EDIT: my service goes like:
AtmosphereRequest req = r.getRequest();
if (req.getMethod().equalsIgnoreCase("GET")) {
log.info "got get, and suspending."
r.suspend();
} else if (req.getMethod().equalsIgnoreCase("POST")) {
def data = req.getReader().readLine().trim()
log.info "got some data:\n $data"
if (data == "GET_NEARBY"){
log.info "finding nearby..."
def user = springSecurityService.getCurrentUser()
log.info "user is $user" //USER IS NULL HERE
//...some logic
}
}
Try this:
def ctx = ServletContextHolder.servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
def springSecurityService = ctx. springSecurityService
def currentUser = springSecurityService.currentUser
now you should be able to use springSecurityService
In my grails app I have customized the post authorization workflow by writing a custom auth success handler (in resources.groovy) as shown below.
authenticationSuccessHandler (MyAuthSuccessHandler) {
def conf = SpringSecurityUtils.securityConfig
requestCache = ref('requestCache')
defaultTargetUrl = conf.successHandler.defaultTargetUrl
alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
targetUrlParameter = conf.successHandler.targetUrlParameter
useReferer = conf.successHandler.useReferer
redirectStrategy = ref('redirectStrategy')
superAdminUrl = "/admin/processSuperAdminLogin"
adminUrl = "/admin/processAdminLogin"
userUrl = "/admin/processUserLogin"
}
As you can from the last three lines in the closure above, depending on the Role granted to the logging in User I am redirecting her to separate actions within the AdminController where a custom UserSessionBean is created and stored in the session.
It works fine for a regular login case which in my app is like so:
User comes to the app via either http://localhost:8080/my-app/ OR http://localhost:8080/my-app/login/auth
She enters her valid login id and password and proceeds.
The app internally accesses MyAuthSuccessHandler which redirects to AdminController considering the Role granted to this User.
The UserSessionBean is created and stored it in the session
User is taken to the app home page
I have also written a custom MyUserDetailsService by extending GormUserDetailsService which is correctly accessed in the above flow.
PROBLEM SCENARIO:
Consider a user directly accessing a protected resource (in this case the controller is secured with #Secured annotation) within the app.
User clicks http://localhost:8080/my-app/inbox/index
App redirects her to http://localhost:8080/my-app/login/auth
User enters her valid login id and password
User is taken to http://localhost:8080/my-app/inbox/index
The MyAuthSuccessHandler is skipped entirely in this process and hence my UserSessionBean is not created leading to errors upon further use in places where the UserSessionBean is accessed.
QUESTIONS:
In the problem scenario, does the app skip the MyAuthSuccessHandler because there is a target URL for it to redirect to upon login?
Can we force the process to always pass through MyAuthSuccessHandler even with the target URL present?
If the answer to 2 is no, is there an alternative as to how and where the UserSessionBean can still be created?
You can implement a customized eventListener to handle the post-login process, without disrupting the original user requested url.
In config.groovy, insert a config item:
grails.plugins.springsecurity.useSecurityEventListener = true
In you resources.groovy, add a bean like this:
import com.yourapp.auth.LoginEventListener
beans = {
loginEventListener(LoginEventListener)
}
And create a eventListener in src/groovy like this:
package com.yourapp.auth
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
import org.springframework.web.context.request.RequestContextHolder as RCH
class LoginEventListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent> {
//deal with successful login
void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
User.withTransaction {
def user = User.findByUsername(event.authentication.principal.username)
def adminRole = Role.findByAuthority('ROLE_ADMIN')
def userRole = Role.findByAuthority('ROLE_USER')
def session = RCH.currentRequestAttributes().session //get httpSession
session.user = user
if(user.authorities.contains(adminRole)){
processAdminLogin()
}
else if(user.authorities.contains(userRole)){
processUserLogin()
}
}
}
private void processAdminLogin(){ //move admin/processAdminLogin here
.....
}
private void processUserLogin(){ //move admin/processUserLogin here
.....
}
}
Done.
1) Yes, because it is an "on-demand" log in.
2) Yes, you can set it to always use default. The spring security plugin has a setting for it "successHandler.alwaysUseDefault" change that to true it defaults to false.
Also if you need more details check out the spring docs look for the Setting a Default Post-Login Destination section.
3) If you want to still create the user session bean and then redirect to the original URL you have two options create the bean in an earlier filter or expose the needed data via a custom UserDetailsService. Personally I would go the route of a custom details service.
I've added an onAuthenticationSuccessEvent to my config.groovy in order to try add a Login model to a list within the users User model. The problem i'm having, is that the event listener is a closure, and as such has no HibernateSession or access to state outside of the closure.
I know its possible to bind a HibernateSession by doing something like this:
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
Login.withTransaction{
login = new Login()
}
}
The problem is that the Login belongs to a User, and needs to have the User defined in its constructor. How do I pass the User into the closure?
I want to do something like:
def user = grails.admin.User.read(appCtx.springSecurityService.currentUser.id)
Login.withTransaction{ user ->
login = new Login(user: user)
}
but dont know how to pass the specific user in.
You can't do Login.withTransaction{ user -> since the parameter of the closure you pass to the withTransaction method takes an argument which is the TransactionStatus - you don't get to specify the type, just the name.
But I'm not sure why you're seeing what you're seeing with regard to closure scope. The cool thing about closures is that they enclose their scope, hence the name. So variables outside of the closure are always available inside the closure.
You would want the User to be loaded inside the withTransaction block though so it's in the same Hibernate session as the transaction's, otherwise the Login save will fail since the User will be disconnected. And use load(), not read() since you're just setting the foreign key in the Login, so load() works best since it just creates a proxy and doesn't hit the database, but makes the id available for the Login save. For the same reason you'd want to avoid using the currentUser method since it's just a shortcut for User.get().
Having said all that, this worked for me:
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
Login.withTransaction { status ->
new Login(user: appCtx.springSecurityService.currentUser).save()
}
}
but since the e variable is an InteractiveAuthenticationSuccessEvent you're better off using this:
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
Login.withTransaction { status ->
def user = User.load(e.authentication.principal.id)
new Login(user: user).save()
}
}
Why Can't you do some thing like below, Just fetch the user inside the closure.
Login.withNewSession {
def user = grails.admin.User.read(appCtx.springSecurityService.currentUser.id)
login = new Login(user: user)
}
OR
Login.withTransaction{
def user = grails.admin.User.read(appCtx.springSecurityService.currentUser.id)
login = new Login(user: user)
}