Grails - Spring Core
Grails SDK
下载SDK,配置环境变量GRAILS_HOME和PATH
grails-4.0.3.zip
Create APP
grails create-app cn.duchaoqun.demo
cd demo
run-app
Install Grails Spring Security Core Plugin
- 在build中添加引用
compile "org.grails.plugins:spring-security-core:4.0.0.RC2"
stop-app
compile
Create User, Role, and Product Domain Class
- 在添加完插件之后,就可以创建如下命令创建 Domain 了。
s2-quickstart cn.duchaoqun User Role
- 修改grails-app/domain/cn/duchaoqun/Role.groovy
String toString() {
authority
}
- 修改grails-app/domain/cn/duchaoqun/User.groovy
// 在password字段后面添加
String fullname
// 添加约束
static constraints = {
password nullable: false, blank: false, password: true
username nullable: false, blank: false, unique: true
fullname nullable: false, blank: false
}
- 创建 Product实体 grails-app/domain/cn/duchaoqun/Product.groovy
create-domain-class cn.duchaoqun.Product
package cn.duchaoqun
class Product {
String prodCode
String prodName
String prodModel
String prodDesc
String prodImageUrl
String prodPrice
static constraints = {
prodCode nullable: false, blank: false
prodName nullable: false, blank: false
prodModel nullable: false, blank: false
prodDesc nullable: false, blank: false
prodImageUrl nullable: true
prodPrice nullable: false, blank: false
}
String toString() {
prodName
}
}
Create CustomUserDetailsService
- 因为我们修改了User实体,这里我们需要自定义一个CustomUserDetails,创建src/main/groovy/cn/duchaoqun/CustomUserDetails.groovy
package cn.duchaoqun
import grails.plugin.springsecurity.userdetails.GrailsUser
import org.springframework.security.core.GrantedAuthority
class CustomUserDetails extends GrailsUser {
final String fullname
CustomUserDetails(String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired,
boolean accountNonLocked,
Collection<GrantedAuthority> authorities,
long id, String fullname) {
super(username, password, enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, authorities, id)
this.fullname = fullname
}
}
- 创建一个新的 Service
create-service cn.duchaoqun.CustomUserDetails
package cn.duchaoqun
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.userdetails.GrailsUserDetailsService
import grails.plugin.springsecurity.userdetails.NoStackUsernameNotFoundException
import grails.gorm.transactions.Transactional
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException
class CustomUserDetailsService implements GrailsUserDetailsService {
/**
* Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least
* one role, so we give a user with no granted roles this one which gets
* past that restriction but doesn't grant anything.
*/
static final List NO_ROLES = [new SimpleGrantedAuthority(SpringSecurityUtils.NO_ROLE)]
UserDetails loadUserByUsername(String username, boolean loadRoles)
throws UsernameNotFoundException {
return loadUserByUsername(username)
}
@Transactional(readOnly=true, noRollbackFor=[IllegalArgumentException, UsernameNotFoundException])
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = User.findByUsername(username)
if (!user) throw new NoStackUsernameNotFoundException()
def roles = user.authorities
// or if you are using role groups:
// def roles = user.authorities.collect { it.authorities }.flatten().unique()
def authorities = roles.collect {
new SimpleGrantedAuthority(it.authority)
}
return new CustomUserDetails(user.username, user.password, user.enabled,
!user.accountExpired, !user.passwordExpired,
!user.accountLocked, authorities ?: NO_ROLES, user.id,
user.fullname)
}
}
- 然后再
grails-app/conf/spring/resources.groovy
中注册新的 Service
import cn.duchaoqun.UserPasswordEncoderListener
import cn.duchaoqun.CustomUserDetailsService
// Place your Spring DSL code here
beans = {
userPasswordEncoderListener(UserPasswordEncoderListener)
userDetailsService(CustomUserDetailsService)
}
Override Login Auth View
- 自定义登录页面。
- 在 views 文件夹创建一个login 文件夹,然后再里面创建 auth.gsp
grails-app/views/login/auth.gsp
<html>
<head>
<meta name="layout" content="${gspLayout ?: 'main'}"/>
<title><g:message code='springSecurity.login.title'/></title>
</head>
<body>
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div class="card card-signin my-5">
<div class="card-body">
<h5 class="card-title text-center">Please Login</h5>
<g:if test='${flash.message}'>
<div class="alert alert-danger" role="alert">${flash.message}</div>
</g:if>
<form class="form-signin" action="${postUrl ?: '/login/authenticate'}" method="POST" id="loginForm" autocomplete="off">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" name="${usernameParameter ?: 'username'}" id="username" autocapitalize="none"/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" name="${passwordParameter ?: 'password'}" id="password"/>
<i id="passwordToggler" title="toggle password display" onclick="passwordDisplayToggle()">👁</i>
</div>
<div class="form-group form-check">
<label class="form-check-label">
<input type="checkbox" class="form-check-input" name="${rememberMeParameter ?: 'remember-me'}" id="remember_me" <g:if test='${hasCookie}'>checked="checked"</g:if>/> Remember me
</label>
</div>
<button id="submit" class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Sign in</button>
<hr class="my-4">
<p>Don't have an account? <g:link controller="register">Register</g:link></p>
</form>
</div>
</div>
</div>
</div>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
document.forms['loginForm'].elements['username'].focus();
});
function passwordDisplayToggle() {
var toggleEl = document.getElementById("passwordToggler");
var eyeIcon = '\u{1F441}';
var xIcon = '\u{2715}';
var passEl = document.getElementById("password");
if (passEl.type === "password") {
toggleEl.innerHTML = xIcon;
passEl.type = "text";
} else {
toggleEl.innerHTML = eyeIcon;
passEl.type = "password";
}
}
</script>
</body>
</html>
- 将改页面设置为默认页面grails-app/controllers/UrlMappings.groovy
// 修改前
"/"(view: "index")
// 修改后
"/"(controller:'login', action:'auth')
Add User Info and Logout to the Navbar
- 修改grails-app/views/layout/main.gsp
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>
<g:layoutTitle default="Grails"/>
</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<asset:link rel="icon" href="favicon.ico" type="image/x-ico"/>
<asset:stylesheet src="application.css"/>
<g:layoutHead/>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark navbar-static-top" role="navigation">
<a class="navbar-brand" href="/#"><asset:image src="grails.svg" alt="Grails Logo"/></a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarContent" aria-controls="navbarContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" aria-expanded="false" style="height: 0.8px;" id="navbarContent">
<ul class="nav navbar-nav ml-auto">
<g:pageProperty name="page.nav"/>
<sec:ifLoggedIn>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbardrop" data-toggle="dropdown">
<sec:loggedInUserInfo field='fullname'/>
</a>
<div class="dropdown-menu navbar-dark">
<g:form controller="logout">
<g:submitButton class="dropdown-item navbar-dark color-light" name="Submit" value="Logout" style="color:gray" />
</g:form>
</div>
</li>
</sec:ifLoggedIn>
</ul>
</div>
</nav>
<div class="container">
<g:layoutBody/>
</div>
<div class="footer row" role="contentinfo">
<div class="col">
<a href="http://guides.grails.org" target="_blank">
<asset:image src="advancedgrails.svg" alt="Grails Guides" class="float-left"/>
</a>
<strong class="centered"><a href="http://guides.grails.org" target="_blank">Grails Guides</a></strong>
<p>Building your first Grails app? Looking to add security, or create a Single-Page-App? Check out the <a href="http://guides.grails.org" target="_blank">Grails Guides</a> for step-by-step tutorials.</p>
</div>
<div class="col">
<a href="http://docs.grails.org" target="_blank">
<asset:image src="documentation.svg" alt="Grails Documentation" class="float-left"/>
</a>
<strong class="centered"><a href="http://docs.grails.org" target="_blank">Documentation</a></strong>
<p>Ready to dig in? You can find in-depth documentation for all the features of Grails in the <a href="http://docs.grails.org" target="_blank">User Guide</a>.</p>
</div>
<div class="col">
<a href="https://grails-slack.cfapps.io" target="_blank">
<asset:image src="slack.svg" alt="Grails Slack" class="float-left"/>
</a>
<strong class="centered"><a href="https://grails-slack.cfapps.io" target="_blank">Join the Community</a></strong>
<p>Get feedback and share your experience with other Grails developers in the community <a href="https://grails-slack.cfapps.io" target="_blank">Slack channel</a>.</p>
</div>
</div>
<div id="spinner" class="spinner" style="display:none;">
<g:message code="spinner.alt" default="Loading…"/>
</div>
<asset:javascript src="application.js"/>
</body>
</html>
Create Register Controller and View
- 创建register控制器
create-controller cn.duchaoqun.Register
- 填充代码
package cn.duchaoqun.
import grails.validation.ValidationException
import grails.gorm.transactions.Transactional
import grails.plugin.springsecurity.annotation.Secured
import cn.duchaoqun.User
import cn.duchaoqun.Role
import cn.duchaoqun.UserRole
@Transactional
@Secured('permitAll')
class RegisterController {
static allowedMethods = [register: "POST"]
def index() { }
def register() {
if(!params.password.equals(params.repassword)) {
flash.message = "Password and Re-Password not match"
redirect action: "index"
return
} else {
try {
def user = User.findByUsername(params.username)?: new User(username: params.username, password: params.password, fullname: params.fullname).save()
def role = Role.get(params.role.id)
if(user && role) {
UserRole.create user, role
UserRole.withSession {
it.flush()
it.clear()
}
flash.message = "You have registered successfully. Please login."
redirect controller: "login", action: "auth"
} else {
flash.message = "Register failed"
render view: "index"
return
}
} catch (ValidationException e) {
flash.message = "Register Failed"
redirect action: "index"
return
}
}
}
}
- 创建 Register 页面 grails-app/views/register/index.gsp
<html>
<head>
<meta name="layout" content="${gspLayout ?: 'main'}"/>
<title>Register</title>
</head>
<body>
<div class="row">
<div class="col-sm-9 col-md-7 col-lg-5 mx-auto">
<div class="card card-signin my-5">
<div class="card-body">
<h5 class="card-title text-center">Register Here</h5>
<g:if test='${flash.message}'>
<div class="alert alert-danger" role="alert">${flash.message}</div>
</g:if>
<form class="form-signin" action="register" method="POST" id="loginForm" autocomplete="off">
<div class="form-group">
<label for="role">Role</label>
<g:select class="form-control" name="role.id"
from="${cn.duchaoqun.Role.list()}"
optionKey="id" />
</div>
<div class="form-group">
<label for="username">Username</label>
<input type="text" placeholder="Your username" class="form-control" name="username" id="username" autocapitalize="none"/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" placeholder="Your password" class="form-control" name="password" id="password"/>
</div>
<div class="form-group">
<label for="password">Re-Enter Password</label>
<input type="password" placeholder="Re-enter password" class="form-control" name="repassword" id="repassword"/>
</div>
<div class="form-group">
<label for="username">Full Name</label>
<input type="text" placeholder="Your full name" class="form-control" name="fullname" id="fullname" autocapitalize="none"/>
</div>
<button id="submit" class="btn btn-lg btn-primary btn-block text-uppercase" type="submit">Register</button>
<hr class="my-4">
<p>Already have an account? <g:link controller="login" action="auth">Login</g:link></p>
</form>
</div>
</div>
</div>
</div>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", function(event) {
document.forms['loginForm'].elements['username'].focus();
});
</script>
</body>
</html>
Create the Secure Product CRUD Scaffolding
- 创建一个实体
generate-all cn.duchaoqun.Product
- 修改对应的 Controller,grails-app/controllers/ProductController.groovy
package cn.duchaoqun
import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*
import grails.plugin.springsecurity.annotation.Secured
class ProductController {
ProductService productService
static allowedMethods = [save: "POST", update: "PUT", delete: "DELETE"]
@Secured(['ROLE_ADMIN', 'ROLE_USER'])
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond productService.list(params), model:[productCount: productService.count()]
}
@Secured(['ROLE_ADMIN', 'ROLE_USER'])
def show(Long id) {
respond productService.get(id)
}
@Secured('ROLE_ADMIN')
def create() {
respond new Product(params)
}
@Secured('ROLE_ADMIN')
def save(Product product) {
if (product == null) {
notFound()
return
}
try {
productService.save(product)
} catch (ValidationException e) {
respond product.errors, view:'create'
return
}
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.created.message', args: [message(code: 'product.label', default: 'Product'), product.id])
redirect product
}
'*' { respond product, [status: CREATED] }
}
}
@Secured('ROLE_ADMIN')
def edit(Long id) {
respond productService.get(id)
}
@Secured('ROLE_ADMIN')
def update(Product product) {
if (product == null) {
notFound()
return
}
try {
productService.save(product)
} catch (ValidationException e) {
respond product.errors, view:'edit'
return
}
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.updated.message', args: [message(code: 'product.label', default: 'Product'), product.id])
redirect product
}
'*'{ respond product, [status: OK] }
}
}
@Secured('ROLE_ADMIN')
def delete(Long id) {
if (id == null) {
notFound()
return
}
productService.delete(id)
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.deleted.message', args: [message(code: 'product.label', default: 'Product'), id])
redirect action:"index", method:"GET"
}
'*'{ render status: NO_CONTENT }
}
}
protected void notFound() {
request.withFormat {
form multipartForm {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'product.label', default: 'Product'), params.id])
redirect action: "index", method: "GET"
}
'*'{ render status: NOT_FOUND }
}
}
}
- 设置登录成功后的默认页面
grails-app/conf/application.groovy
grails.plugin.springsecurity.successHandler.defaultTargetUrl = '/product'
本文来自博客园,作者:duchaoqun,转载请注明原文链接:https://www.cnblogs.com/duchaoqun/p/12875927.html