3

How do you store users in a database with a new plain vanilla Grails 3.0 application?

Background:

  1. The Shiro and Spring Security plugins are not yet available for Grails 3.0 (and it sounds like Spring Boot is the future for Grails security).
  2. There are various examples out there showing how to use inMemoryAuthentication(), but they seem completely pointless as passwords end up being stored in plain text (besides, it only takes about 30 seconds of effort to create a domain model in Grails).
  3. Pretty much all Grails applications need this functionality.
  4. I happen to be using MongoDB, but that's probably irrelevant.
  5. Related: Grails 3 and Spring Security Plugin

I currently have inMemoryAuthentication() working with the following:

build.gradle

compile "org.springframework.boot:spring-boot-starter-security"


grails-app/conf/spring/resources.groovy

import com.tincanworks.AppSecurityConfig
beans = {
   webSecurityConfiguration(AppSecurityConfig)
   }


AppSecurityConfig.groovy

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter

class AppSecurityConfig extends WebSecurityConfigurerAdapter {

   @Override
   protected void configure(HttpSecurity http) throws Exception {
      http
         .authorizeRequests()
            .antMatchers("/").permitAll()
            .antMatchers("/assets/**").permitAll()
            .antMatchers("/admin/**").hasAnyRole("admin")
            .anyRequest().authenticated()
         .and()
            .formLogin().permitAll()
         .and()
            .logout().permitAll()
      }

   @Autowired
   public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
      auth
         .inMemoryAuthentication()
            .withUser("user").password("123456").roles("user")
            .and()
            .withUser("admin").password("1qaz2wsx").roles("user", "admin")
      }

}

It seems like the answer may be related to JdbcDaoImpl, but I have no idea how to hook that up in Grails.

Community
  • 1
  • 1
Dem Pilafian
  • 5,625
  • 6
  • 39
  • 67

2 Answers2

2

GORM-based

I wrote up two blog posts (part 1 - In Memory Auth and part 2 - Gorm-based Auth) on how to use spring-starter-security and GORM in a Grails 3 application. I also created a github repo with a working Grails 3 application using spring-starter-security.

JDBC-based - untested

Alternatively, if you wanted to use the standard JDBC-based authentication you could just create the database tables using the following SQL script

HSQLDB

From http://docs.spring.io/spring-security/site/docs/3.0.x/reference/appendix-schema.html

create table users(
      username varchar_ignorecase(50) not null primary key,
      password varchar_ignorecase(50) not null,
      enabled boolean not null);

  create table authorities (
      username varchar_ignorecase(50) not null,
      authority varchar_ignorecase(50) not null,
      constraint fk_authorities_users foreign key(username) references users(username));
      create unique index ix_auth_username on authorities (username,authority);

MySQL

This comes from http://justinrodenbostel.com/2014/05/30/part-5-integrating-spring-security-with-spring-boot-web/

create table users (
    username varchar(50) not null primary key,
    password varchar(255) not null,
    enabled boolean not null) engine = InnoDb;

create table authorities (
    username varchar(50) not null,
    authority varchar(50) not null,
    foreign key (username) references users (username),
    unique index authorities_idx_1 (username, authority)) engine = InnoDb;

and then change the configureGlobal method to

@Autowired  //not sure if this is needed as you have the AppSecurityConfig bean referenced in resources.groovy
def datasource  //bean injected by Grails

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  auth
     .jdbcAuthentication()
     .dataSource(datasource)
}
dspies
  • 1,545
  • 14
  • 19
0

If you want to avoid building a whole User-management layer from scratch with a DB you could consider Stormpath.

Among other things, they provide a Spring Security Plugin that uses Stormpath as the authentication and authorization provider. They also have a sample Spring Security app that shows how the plugin is used. Since you are using Java Annotations (rather than xml configuration) take a look at this branch.

So, in summary, the key pieces that you will need to define are these:

  1. The Stormpath Client Bean that will provide fast and secure communication with Stormpath via the Stormpath Java SDK:

    //Let's create the Stormpath client using the apiKey.properties file from the User's home folder.
    @Bean
    ClientFactory stormpathClient(CacheManager cacheManager) {
        ClientFactory clientFactory = new ClientFactory();
        clientFactory.setApiKeyFileLocation(System.getProperty("user.home") + File.separator + ".stormpath" + File.separator + "apiKey.properties");
        clientFactory.setCacheManager(cacheManager);
        return clientFactory;
    }
    
  2. You will need to define the Stormpath Authentication Provider so Spring Security can transparently communicate with Stormpath to authenticate and authorize users:

    @Bean
    @Autowired
    public StormpathAuthenticationProvider stormpathAuthenticationProvider(Client client, String applicationRestUrl) throws Exception {
        StormpathAuthenticationProvider stormpathAuthenticationProvider = new StormpathAuthenticationProvider();
        stormpathAuthenticationProvider.setClient(client);
        stormpathAuthenticationProvider.setApplicationRestUrl(applicationRestUrl);
        return stormpathAuthenticationProvider;
    }
    
  3. The applicationRestUrl needs to point to the Stormpath application where all the users/groups will exist:

    @Bean
    public String getApplicationRestUrl() {
        return "https://api.stormpath.com/v1/applications/9TqbyZ2po73eDP4gYo2H92";
    }
    
  4. Your Spring Security configuration needs to be configured to use the Stormpath Authentication Provider:

    //Let's add the StormpathAuthenticationProvider to the `AuthenticationProvider`
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(stormpathAuthenticationProvider);
    }
    
  5. Finally, in order to restrict the access to resources by roles, you will need to define the roles. For example:

    //The access control settings are defined here
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .formLogin()
                .and()
            .authorizeRequests()
                .accessDecisionManager(accessDecisionManager())
                .antMatchers("/account/*").hasAuthority("https://api.stormpath.com/v1/groups/36O9eBTN2oLtjoMSWLdnwL") //you are giving access to "/account/*" to users' that belong to the group univocally identified by this href value
                .and()
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/index.jsp")
                .and()
            .httpBasic()
            .and()
            .csrf().disable();
    }
    

Disclaimer, I am an active Stormpath contributor.

mario
  • 1,154
  • 8
  • 13
  • 1
    This solution may be desirable for some situations (particularly more mature applications), but most developers won't want to sign up for such a service just to create their initial application. For those who want this solution, here are the plans: https://stormpath.com/pricing/ – Dem Pilafian May 16 '15 at 18:52
  • Actually I think that Stormpath is a great starting point for simple applications. Stormpath will not only be quite straightforward to use when starting from scratch but it will also be totally free if they do not surpass 100k API calls per month – mario May 18 '15 at 17:23
  • `"The Stormpath API shut down on August 17, 2017."` – Dem Pilafian May 12 '18 at 22:10