延續「微型部落格」Blogito 的建置,本文實作完整的使用者子系統:登入驗證、角色授權、細粒度權限控管,並探討插件化的進階安全方案。
class User {
static constraints = {
login(unique: true)
password(password: true)
name()
role(inList: ['author', 'admin'])
}
static hasMany = [entries: Entry]
String login, password, name
String role = 'author'
String toString() { name }
}
role 預設 author,inList 限制只能是 author 或 admin,表單自動產生下拉選單。
class UserController {
def scaffold = User
def login = {}
def authenticate = {
def user = User.findByLoginAndPassword(params.login, params.password)
if (user) {
session.user = user
flash.message = "Hello ${user.name}!"
redirect(controller: 'entry', action: 'list')
} else {
flash.message = "Sorry, ${params.login}. Please try again."
redirect(action: 'login')
}
}
def logout = {
flash.message = "Goodbye ${session.user.name}"
session.user = null
redirect(controller: 'entry', action: 'list')
}
}
login.gsp 表單送往 authenticate,欄位名稱對應 params.login/params.password。


class LoginTagLib {
def loginControl = {
if (session.user) {
out << "Hello ${session.user.name} "
out << """[${link(action: 'logout', controller: 'user') { 'Logout' }}]"""
} else {
out << """[${link(action: 'login', controller: 'user') { 'Login' }}]"""
}
}
}
嵌入 _header.gsp:
<div id="loginHeader">
<g:loginControl />
</div>
CSS 靠右浮動,體驗媲美 Google/Amazon 頁首。

class EntryController {
def beforeInterceptor = [action: this.&auth, except: ['index', 'list', 'show']]
def auth() {
if (!session.user) {
redirect(controller: 'user', action: 'login')
return false
}
}
// ...
}
未登入嘗試存取 /entry/create 即被導向登入頁;登入後放行。
def edit = {
def entryInstance = Entry.get(params.id)
if (!(session.user.login == entryInstance.author.login)) {
flash.message = "Sorry, you can only edit your own entries."
redirect(action: 'list')
}
// ...
}
同樣套用至 delete、update,或抽取為私有方法複用。
BootStrap.groovy 建立 admin 使用者:
def admin = new User(login: 'admin', password: 'password', name: 'Administrator', role: 'admin')
admin.save()
UserController 限制帳號管理僅限 admin:
def beforeInterceptor = [action: this.&auth, except: ['login', 'authenticate', 'logout']]
def auth() {
if (!(session?.user?.role == 'admin')) {
flash.message = "You must be an administrator to perform that task."
redirect(action: 'login')
return false
}
}

class HashCodec {
static encode = { str ->
MessageDigest md = MessageDigest.getInstance('SHA')
md.update(str.getBytes('UTF-8'))
return (new BASE64Encoder()).encode(md.digest())
}
}
在 UserController 中改用 params.password.encodeAsHash(),10 行代碼提升安全等級。
手寫策略適合簡單場景,複雜需求建議採用成熟插件:
原則:優勢大於複雜度時才引入插件。
$ grails stats
+----------------------+-------+-------+
| Name | Files | LOC |
+----------------------+-------+-------+
| Controllers | 2 | 95 |
| Domain Classes | 2 | 32 |
| Tag Libraries | 2 | 21 |
| Unit Tests | 5 | 20 |
| Integration Tests | 1 | 10 |
+----------------------+-------+-------+
| Totals | 12 | 178 |
+----------------------+-------+-------+
不足 200 行完成完整身分驗證與授權系統。
Blogito 現具備登入/登出、角色授權、細粒度權限控管。下一篇將實作檔案上傳與 Atom 聯合供稿,讓 Blogito 成為真正可用的部落格引擎。