Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ import io.micronaut.spring.boot.starter.EnableMicronaut

}
@if(features.contains("spring-boot-starter-security")) {
import org.springframework.context.annotation.Import
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.crypto.factory.PasswordEncoderFactories
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.security.web.SecurityFilterChain

}
import grails.boot.GrailsApp
Expand All @@ -50,10 +54,32 @@ import grails.plugins.metadata.PluginSource
@@EnableMicronaut
}
@if(features.contains("spring-boot-starter-security")) {
@@Import(SecurityConfig)
@@EnableWebSecurity
}
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
@if(features.contains("spring-boot-starter-security")) {


def beans = {

bean(PasswordEncoder) {
PasswordEncoderFactories.createDelegatingPasswordEncoder()
}

bean('filterChain', SecurityFilterChain) { HttpSecurity http ->
http
.authorizeHttpRequests {
it.requestMatchers('/', '/index', '/index.gsp', '/error', '/assets/**').permitAll()
.requestMatchers('/user/**').hasRole('ADMIN')
.anyRequest().authenticated()
}
.formLogin { }
.logout { it.logoutSuccessUrl('/') }
http.build()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@ class BootStrap {
def init = {
@if(features.contains("spring-boot-starter-security")) {
User.withTransaction {
def passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()
def password = UUID.randomUUID().toString()
new User(username: 'admin', password: PasswordEncoderFactories.createDelegatingPasswordEncoder().encode(password), roles: ['ROLE_ADMIN']).save(failOnError: true)
new User(username: 'admin', password: passwordEncoder.encode(password), roles: ['ROLE_ADMIN']).save(failOnError: true)
log.warn "Generated admin credentials: admin / $password"
// A second, non-admin account: the URL rules in Application distinguish ROLE_ADMIN
// from an ordinary logged-in user, and without this nothing exercises that.
def userPassword = UUID.randomUUID().toString()
new User(username: 'user', password: passwordEncoder.encode(userPassword), roles: ['ROLE_USER']).save(failOnError: true)
log.warn "Generated user credentials: user / $userPassword"
}
}
@if(features.contains("grails-spring-security")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import org.grails.forge.application.Project;
import org.grails.forge.application.generator.GeneratorContext;
import org.grails.forge.build.dependencies.Dependency;
import org.grails.forge.feature.security.template.securityConfig;
import org.grails.forge.feature.security.template.user;
import org.grails.forge.feature.security.template.userController;
import org.grails.forge.feature.security.template.userService;
Expand All @@ -33,8 +32,9 @@

/**
* Secures the application with plain Spring Security: the spring-boot-starter-security
* dependency, the shared user artifacts and a {@code SecurityConfig} with form login.
* No Grails security plugins are involved.
* dependency, the shared user artifacts, and form login wired through the {@code beans}
* DSL on the application's own {@code Application} class. No Grails security plugins are
* involved, and no separate configuration class is generated.
*
* @since 8.0
*/
Expand Down Expand Up @@ -83,8 +83,5 @@ public void apply(GeneratorContext generatorContext) {
new RockerTemplate("grails-app/services/{packagePath}/UserService.groovy", userService.template(project)));
generatorContext.addTemplate("securityUserSpec",
new RockerTemplate(generatorContext.getTestSourcePath("/{packagePath}/User"), userSpec.template(project)));
generatorContext.addTemplate("securityConfig",
new RockerTemplate(generatorContext.getSourcePath("/{packagePath}/SecurityConfig"),
securityConfig.template(project)));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class User implements UserDetails {

static constraints = {
username blank: false, unique: true
password blank: false
password blank: false, password: true
}

@@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,48 @@ class SpringBootStarterSecuritySpec extends ApplicationContextSpec implements Co
user.contains('static hasMany = [roles: String]')
user.contains('new SimpleGrantedAuthority(it)')

and: 'the password field is marked as a password so scaffolding masks it'
user.contains('password blank: false, password: true')

and: 'the scaffolded controller and UserDetailsService-backed service are generated'
output['grails-app/controllers/example/grails/UserController.groovy'].contains('@Scaffold(RestfulServiceController<User>)')
def service = output['grails-app/services/example/grails/UserService.groovy']
service.contains('@Scaffold(User)')
service.contains('class UserService implements UserDetailsService')
service.contains("User.findByUsername(username, [fetch: [roles: 'join']])")

and: 'SecurityConfig enables form login with a delegating password encoder'
def config = output['src/main/groovy/example/grails/SecurityConfig.groovy']
config.contains('@EnableWebSecurity')
config.contains('PasswordEncoderFactories.createDelegatingPasswordEncoder()')
config.contains('.formLogin { }')
config.contains(".logout { it.logoutSuccessUrl('/') }")
and: 'no separate configuration class is generated'
!output.containsKey('src/main/groovy/example/grails/SecurityConfig.groovy')

and: 'Application imports the security configuration'
and: 'Application declares the security beans through the beans DSL'
def application = output['grails-app/init/example/grails/Application.groovy']
application.contains('import org.springframework.context.annotation.Import')
application.contains('@Import(SecurityConfig)')
application.contains('import org.springframework.security.web.SecurityFilterChain')
application.contains('@EnableWebSecurity')
application.contains('def beans = {')
application.contains('bean(PasswordEncoder) {')
application.contains('PasswordEncoderFactories.createDelegatingPasswordEncoder()')
application.contains("bean('filterChain', SecurityFilterChain) { HttpSecurity http ->")
application.contains('.formLogin { }')
application.contains(".logout { it.logoutSuccessUrl('/') }")
!application.contains('@Import(SecurityConfig)')

and: 'the home page and static assets are public, the user admin is ROLE_ADMIN, the rest requires login'
application.contains("it.requestMatchers('/', '/index', '/index.gsp', '/error', '/assets/**').permitAll()")
application.contains(".requestMatchers('/user/**').hasRole('ADMIN')")
application.contains('.anyRequest().authenticated()')
!application.contains('anyRequest().permitAll()')

and: 'BootStrap seeds an admin user with a generated password'
def bootStrap = output['grails-app/init/example/grails/BootStrap.groovy']
bootStrap.contains('import org.springframework.security.crypto.factory.PasswordEncoderFactories')
bootStrap.contains('PasswordEncoderFactories.createDelegatingPasswordEncoder().encode(password)')
bootStrap.contains('PasswordEncoderFactories.createDelegatingPasswordEncoder()')
bootStrap.contains('passwordEncoder.encode(password)')
bootStrap.contains('Generated admin credentials: admin')

and: 'BootStrap also seeds a non-admin account so the ROLE_ADMIN rules are exercised'
bootStrap.contains("roles: ['ROLE_USER']")
bootStrap.contains('Generated user credentials: user')

and: 'a domain spec covering constraints and authorities is generated'
def userSpec = output['src/test/groovy/example/grails/UserSpec.groovy']
userSpec.contains('DomainUnitTest<User>')
Expand All @@ -91,7 +108,8 @@ class SpringBootStarterSecuritySpec extends ApplicationContextSpec implements Co

then:
!output['build.gradle'].contains('spring-boot-starter-security')
!output['grails-app/init/example/grails/Application.groovy'].contains('@Import(SecurityConfig)')
!output['grails-app/init/example/grails/Application.groovy'].contains('def beans = {')
!output['grails-app/init/example/grails/Application.groovy'].contains('@EnableWebSecurity')
!output['grails-app/init/example/grails/BootStrap.groovy'].contains('Generated admin credentials')
!output.containsKey('grails-app/domain/example/grails/User.groovy')
!output.containsKey('src/main/groovy/example/grails/SecurityConfig.groovy')
Expand Down
Loading