I am using spring security core plugin (1.2.7) with grails 2.0
Let's say that I have controller with a method that uses #Secured annotation.
class ArticleController {
def springSecurityService
#Secured(['ROLE_PREMIUM_USER'])
def listPremium() {
render 'premium content'
}
}
in my unit test I would like to test if a user with role 'ROLE_PREMIUM_USER' can see content of listPremium method. How can I do this?
I know that it should start as follows:
#TestFor(ArticleController)
#Mock([SpringSecurityService])
class ArticleControllerTests {
void testListPremium() {
defineBeans {
springSecurityService(SpringSecurityService)
}
//but how to login the user here in order to see premium content?
controller.listPremium()
assert response.text() == 'premium content'
}
}
I am not sure how can I authenticate user or mock action that checks ROLE_PREMIUM_USER. Any help?
You may be able to use
SpringSecurityUtils.reauthenticate username, null
We created our custom AuthenticationHelper:
public final class AuthenticationHelper {
public static Authentication authenticate(UserDetailsService userDetailsServiceImpl, String userName) {
UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(userName);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword());
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, token.getCredentials(), userDetails.getAuthorities());
result.setDetails(token.getDetails());
Authentication auth = result;
SecurityContextHolder.getContext().setAuthentication(auth);
auth = SecurityContextHolder.getContext().getAuthentication();
Assert.assertTrue(auth.isAuthenticated());
return auth;
}
}
The important part is:
SecurityContextHolder.getContext().setAuthentication(auth);
Related
Suppose I have multiple Geb/Spock tests that beings with logging in. For example:
#Stepwise
Class AddNewPictureSpec extends GebSpec {
def "User at login page"() {
given: "User beings from login page"
to LoginPage
}
def "User gets redirected to Main page"() {
given: "User at Login page"
at LoginPage
when: "User signs in"
signIn "username", "pw"
to MainPage
then:
at MainPage
def "other test sequences follow...."() {
}
}
And another test spec with the exact same start sequence:
#Stepwise
Class EditPictureSpec extends GebSpec {
def "User at login page"() {
given: "User beings from login page"
to LoginPage
}
def "User gets redirected to Main page"() {
given: "User at Login page"
at LoginPage
when: "User signs in"
signIn "username", "pw"
to MainPage
then:
at MainPage
def "other test sequences follow...."() {
}
}
How do I refactor/extract out the common login "steps" so that I do not have duplicate code? Or am I writing my tests wrongly? Thanks.
I think the 'geb' way to do this is to use modules.
You can create a login module like this:
class LoginModule extends Module {
static content = {
loginForm {$("form")}
loginButton {$("input", value: "Sign in")}
}
void login(String username, String password = "Passw0rd!") {
loginForm.j_username = username
loginForm.j_password = password
loginButton.click()
}
}
Include it in your LoginPage:
class LoginPage extends Page {
static url = "login/auth"
static at = {title == "My Grails Application"}
static content = {
loginModule { module LoginModule }
}
}
Then in your test, you can reference your module's login method:
#Stepwise
class EditPictureSpec extends GebSpec {
def setupSpec() {
to LoginPage
loginModule.login(loginUsername)
}
def "some test"() {
...
}
}
You can create a login method and put it in a BaseSpec (that you would also create), which you would then extend in your tests. Eg:
class BaseSpec extends GebReportingSpec {
def login(name, pw) {
to LoginPage
// login code here...
}
}
Since you're using #StepWise, I'm assuming you're logging in once per spec, so use setupSpec() thusly...
Class AddNewPictureSpec extends BaseSpec {
def setupSpec() {
login("username", "password")
}
}
One possibility is to have one Spec for verifying the actual login behavior (e.g. LoginSpec) that is completely written out as it is now. For other Specs that need to login before doing the actual test you can abstract the entire login process behind a method in the LoginPage. Like you do now with singIn.
When you have a lot of Specs that need to login before they can really start testing the functionality they intend to test, doing the login steps through the browser again and again can take a lot of time.
An alternative can be to create a specific controller that is only loaded in the dev/test environments and that offers a login action.
So instead of going through all steps (go to page, enter name, enter password, ...) you can simply go to the URL /my-app/testLogin/auth?username=username.
Below an example how we do this in our Grails + Spring Security setup. We also bundle other utility methods in that controller that are used in the setup of multiple Specs and that would otherwise require several clicks in the browser, e.g. changing the interface language.
// Example TestLoginController when using the Spring Security plugin
class TestLoginController {
def auth = { String userName, String startPage = 'dashboard' ->
// Block the dev login functionality in production environments
// Can also be done with filter, ...
Environment.executeForCurrentEnvironment {
production {
render(status: HttpServletResponse.SC_NOT_FOUND)
return
}
}
def endUser = getYourEndUserDataByUsername()
if (endUser) {
// Logout existing user
new SecurityContextLogoutHandler().logout(request, null, null)
// Authenticate the user
UserDetails userDetails = new User(endUser)
def authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, userDetails.password, userDetails.authorities)
SecurityContextHolder.context.setAuthentication(authenticationToken)
// Bind the security context to the (new) session
session.SPRING_SECURITY_CONTEXT = SecurityContextHolder.context
redirect(action: "index", controller: startPage)
}
}
The correct solution is to create methods on a geb Page which encapsulate common functionality:
class LoginPage extends Page {
static url = "login/auth"
static at = {title == "Login"}
static content = {
username { $("#user") }
password { $("#password") }
}
def login(String email, String passwd) {
emailInput.value(email)
passwordInput.value(passwd)
passwordInput << Keys.ENTER
}
}
Then your test looks like this:
#Stepwise
class ThingSpec extends GebSpec {
def setupSpec() {
to LoginPage
page.login("user", "pass")
}
def "some test"() {
...
}
}
From an OOP perspective this is the best solution as the login procedure is only applicable to the login page. It doesn't make sense to use a module because no other page has a login box (unless it does, then modules make sense.)
It also doesn't make sense to use inheritance, you'll end up with an unorganized pile of methods and class names like "BaseSpec", bleh.
My Grails app uses the Spring Security plugin. Whenever a user successfully logs in I want to:
store something in the session
redirect them to a custom page (depending on their role)
I need to handle logout events similarly, which was pretty straightforward because the plugin provides a bean named logoutSuccessHandler that can be overriden. I was hoping to similarly find a bean named loginSuccessHandler, but no such luck.
I read the page in the plugin's docs about event handling, but neither of the event handling mechanisms appears to give me access to the current request or session.
If you want to do some stuff upon successful login. You can listen to InteractiveAuthenticationSuccessEvent
class AuthenticationSuccessEventListener implements
ApplicationListener<InteractiveAuthenticationSuccessEvent> {
#Override
public void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
.......do some stuff here
}
}
And then register AuthenticationSuccessEventListener as a spring bean in resources.groovy
You can do whatever you want here, however you wont be able to do redirect from listener.
Here's another similar question
Add a config parameter:
grails.plugins.springsecurity.successHandler.defaultTargetUrl = '/myLogin/handleSuccessLogin'
Then add your custom login-handling in the action that handles this URL
class MyLoginController {
def springSecurityService
def handleSuccessLogin() {
session.foo = 'bar'
if (springSecurityService.currentUser.username == 'bob') {
redirect action: 'bobLogin'
} else {
redirect action: 'defaultLogin'
}
}
def bobLogin() {
// bob's login handler
}
def defaultLogin() {
// default login handler
}
}
I recently used this in a project for logging in. Its kind of a hack but works for me. I'm using version 1.2.7.3 of the plugin.
def auth() {
def config = SpringSecurityUtils.securityConfig
if (springSecurityService.isLoggedIn()) {
def user = User.get(principal.id)
def roles = user.getAuthorities()
def admin_role = Role.findByAuthority("ROLE_ADMIN")
//this user is not admin
if(!roles.contains(admin_role)){
//perform redirect to appropriate page
}
redirect uri: config.successHandler.defaultTargetUrl
//log.info(getPrincipal().username + "logged in at :"+new Date())
return
}
String view = 'auth'
String postUrl = "${request.contextPath}${config.apf.filterProcessesUrl}"
render view: view, model: [postUrl: postUrl,
rememberMeParameter: config.rememberMe.parameter]
}
For logging out I used a Logout controller, performed some action before redirecting to the logout handler:
class LogoutController {
/**
* Index action. Redirects to the Spring security logout uri.
*/
def index = {
// perform some action here
redirect uri: SpringSecurityUtils.securityConfig.logout.filterProcessesUrl
}
}
I have integrated Spring security core plugin in my Grails application.
grails.plugins.springsecurity.successHandler.defaultTargetUrl = "/user/home"
This is what I have done to set default home page after successful login. But I would like to have different home page depending upon user roles
Currently I have 2 user roles
1)"ROLE_ADMIN"
2)"ROLE_USER"
How would I implement this?
One quick way would be to do the logic in the controller action. For example, the home action could render a different view based on role, e.g.:
import grails.plugin.springsecurity.annotation.Secured
class UserController {
def home() {
String view
if (SpringSecurityUtils.ifAllGranted('ROLE_ADMIN')) {
view = 'admin'
}
else if (SpringSecurityUtils.ifAllGranted('ROLE_USER')) {
view = 'user'
}
else {
// ???
}
render view: view, model: [...]
}
}
If you want to distribute the logic among different controllers, you could redirect based on role:
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
class UserController {
def home() {
if (SpringSecurityUtils.ifAllGranted('ROLE_ADMIN')) {
redirect controller: '...', action: '...'
return
}
if (SpringSecurityUtils.ifAllGranted('ROLE_USER')) {
redirect controller: '...', action: '...'
return
}
// ???
}
}
You can configure an authentication success handler too which will redirect users to specific controllers based on the roles.
class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
LinkGenerator linkGenerator
private static final ADMIN_ROLE = 'ROLE_Admin'
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
if(SpringSecurityUtils.ifAllGranted(ADMIN_ROLE)) {
return linkGenerator.link(controller: 'admin', action: "index")
}
return super.determineTargetUrl(request, response);
}
}
See Spring Security Core : Redirect users to different screen based on role
I'm working on a nearly clean grails 1.3.7 project with weceem 1.0RC2, spring-security-core 1.1.3, spring-security-ui 0.1.2, weceem-spring-security 1.0 and their dependencies installed.
Everything works fine except the user login. When I want to login over http://localhost:8080/appname/login i only get the following error message:
Sorry, we were not able to find a user with that username and password.
But the user still exists in the database and i get the same error message if i use a user created by spring-security-ui. To encode passwords i'm using springSecurityService.encodePassword('password'). The LoginController was generated by spring-security (s2-quickstart).
I think there could be a problem with the weceem - spring-security bridge, what's your oppinion?
Best regards,
whitenexx
import grails.converters.JSON
import javax.servlet.http.HttpServletResponse
import org.codehaus.groovy.grails.plugins.springsecurity.SpringSecurityUtils
import org.springframework.security.authentication.AccountExpiredException
import org.springframework.security.authentication.CredentialsExpiredException
import org.springframework.security.authentication.DisabledException
import org.springframework.security.authentication.LockedException
import org.springframework.security.core.context.SecurityContextHolder as SCH
import org.springframework.security.web.WebAttributes
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
class LoginController {
/**
* Dependency injection for the authenticationTrustResolver.
*/
def authenticationTrustResolver
/**
* Dependency injection for the springSecurityService.
*/
def springSecurityService
/**
* Default action; redirects to 'defaultTargetUrl' if logged in, /login/auth otherwise.
*/
def index = {
if (springSecurityService.isLoggedIn()) {
redirect uri: SpringSecurityUtils.securityConfig.successHandler.defaultTargetUrl
}
else {
redirect action: auth, params: params
}
}
/**
* Show the login page.
*/
def auth = {
def config = SpringSecurityUtils.securityConfig
if (springSecurityService.isLoggedIn()) {
redirect uri: config.successHandler.defaultTargetUrl
return
}
String view = 'auth'
String postUrl = "${request.contextPath}${config.apf.filterProcessesUrl}"
render view: view, model: [postUrl: postUrl,
rememberMeParameter: config.rememberMe.parameter]
}
/**
* The redirect action for Ajax requests.
*/
def authAjax = {
response.setHeader 'Location', SpringSecurityUtils.securityConfig.auth.ajaxLoginFormUrl
response.sendError HttpServletResponse.SC_UNAUTHORIZED
}
/**
* Show denied page.
*/
def denied = {
if (springSecurityService.isLoggedIn() &&
authenticationTrustResolver.isRememberMe(SCH.context?.authentication)) {
// have cookie but the page is guarded with IS_AUTHENTICATED_FULLY
redirect action: full, params: params
}
}
/**
* Login page for users with a remember-me cookie but accessing a IS_AUTHENTICATED_FULLY page.
*/
def full = {
def config = SpringSecurityUtils.securityConfig
render view: 'auth', params: params,
model: [hasCookie: authenticationTrustResolver.isRememberMe(SCH.context?.authentication),
postUrl: "${request.contextPath}${config.apf.filterProcessesUrl}"]
}
/**
* Callback after a failed login. Redirects to the auth page with a warning message.
*/
def authfail = {
def username = session[UsernamePasswordAuthenticationFilter.SPRING_SECURITY_LAST_USERNAME_KEY]
String msg = ''
def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
if (exception) {
if (exception instanceof AccountExpiredException) {
msg = SpringSecurityUtils.securityConfig.errors.login.expired
}
else if (exception instanceof CredentialsExpiredException) {
msg = SpringSecurityUtils.securityConfig.errors.login.passwordExpired
}
else if (exception instanceof DisabledException) {
msg = SpringSecurityUtils.securityConfig.errors.login.disabled
}
else if (exception instanceof LockedException) {
msg = SpringSecurityUtils.securityConfig.errors.login.locked
}
else {
msg = SpringSecurityUtils.securityConfig.errors.login.fail
}
}
if (springSecurityService.isAjax(request)) {
render([error: msg] as JSON)
}
else {
flash.message = msg
redirect action: auth, params: params
}
}
/**
* The Ajax success redirect url.
*/
def ajaxSuccess = {
render([success: true, username: springSecurityService.authentication.name] as JSON)
}
/**
* The Ajax denied redirect url.
*/
def ajaxDenied = {
render([error: 'access denied'] as JSON)
}
}
I just resolved a problem with identical symptoms.
It turned out that the mapping closure I had in my Config.groovy had a typo, and I was mapping a field that didn't exist to the 'password' field in the weceem view of the user.
So the custom UserDetailsService injected by the plugin just hated my user objects, and nothing worked right.
I changed passwd to password on the domain side of the mapping to make it match what was actually in my User object, and all was right with the world.
Its a bit tricky to tell from the little info you have provided. The Weceem Spring Security plugin bridges Spring Security Core to Weceem's authentication mechanism.
It does this by providing a custom UserDetailsService implementation that maps from a domain class to the session data object used by Spring Security Core.
This login URL, is it mapped to your own login controller detailed above? The UserDetailsService in the weceem-spring-security plugin uses the configured user domain class to call findByUsername(username):
void afterPropertiesSet() {
def conf = grailsApplication.config
def clsname = conf.grails.plugins.springsecurity.userLookup.userDomainClassName
domainClass = grailsApplication.getDomainClass(clsname).clazz
def mapper = conf.weceem.springsecurity.details.mapper
if (!(mapper instanceof Closure)) {
throw new IllegalArgumentException(
"Your Config must specify a closure in weceem.springsecurity.details.mapper "+
"that maps the domain model to a non-domain object, providing at least: ${REQUIRED_MAPPED_FIELDS}")
}
detailsMapper = mapper
}
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
domainClass.withTransaction { status ->
def user = domainClass.findByUsername(username)
if (!user) throw new UsernameNotFoundException('User not found', username)
...
So as you can see from the above, I think that last line may be where it is ditching out for you, due to some spring domain class / username issue?
If the problem is related to logging into Weceem once installed (which it doesn't appear to be) you need to make sure you have configured how Weceem Spring Security should map from your user domain class to the internal data needed by weceem and spring sec core to function, see:
http://grails.org/plugin/weceem-spring-security
What is best practise in Spring when creating a new user with custom attributes...to extend org.springframework.security.core.userdetails.User or to create the User in the UserDetailsService (this is the approach taken in the IceFaces tutorial).
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
AppUser user = userDAO.findUser(username);
if (user == null)
throw new UsernameNotFoundException("User not found: " + username);
else {
return makeUser(user);
}
}
private User makeUser(AppUser user) {
return new User(user.getLogin(), user
.getPassword(), true, true, true, true,
makeGrantedAuthorities(user));
}
If your user permissions fit into the context of the Spring Security User class then simply writing your own UserDetails service is fine. But if your Users have other attributes that are not encapsulated in the available fields in the default Userclass then you will need to do both. There's not really a "best practice" - both are fine options it really depends on your use case.