Trong bài viết này, tôi tiếp tục việc
xây dựng một "blog siêu nhỏ" có tên là Blogito. Tôi đã sử dụng lại các Users
trong bài viết trước (" Rewiring Grails with custom URIs and codecs") bởi vì trường
name là một phần của URI đầy đủ. Đã đến lúc
thực thi hệ thống con User đầy đủ. Bạn sẽ học
cách để tạo cơ chế đăng nhập, hạn chế hoạt động của người dùng dựa vào
việc User có đăng nhập hay không, và thậm chí
thêm vào một số quyền dựa trên vai trò của User.
Để bắt đầu, người sử dụng
cần có một cách đăng nhập vào blog sao cho họ có thể bổ sung các entry
mới.
Việc thẩm định quyền có lẽ là một ý tưởng tốt đối với các máy chủ blog hỗ trợ nhiều người dùng. Dĩ nhiên là bạn không muốn một người dùng có tên là John Doe lại tình cờ bổ sung các entry lên blog hệt như người dùng tên Jane Smith. Việc thiết lập cơ sở cho việc thẩm định quyền là trả lời câu hỏi: "Bạn là ai?". Ngay sau đó, bạn sẽ có thêm một chút quyền nữa, và nó trả lời cho câu hỏi: "Bạn được phép làm gì?"
Ví dụ 1 biểu diễn một tệp grails-app/domain/User.groovy mà bạn đã tạo ra vào lần trước:
Ví dụ 1. Lớp
User
class User {
static constraints = {
login(unique:true)
password(password:true)
name()
}
static hasMany = [entries:Entry]
String login
String password
String name
String toString(){
name
}
}
|
Các trường login và password đã được đặt đúng vị trí. Tất cả những gì bạn cần là
cung cấp một điều khiển (controller) và một mẫu biểu (form). Hãy tạo ra
tệp grails-app/controllers/UserController.groovy và thêm vào đoạn mã, như
trong ví dụ 2:
Ví dụ 2. Thêm xác thực quyền
login , và logout cho điều khiển UserController
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")
}
}
|
Một lệnh login rỗng đơn giản sẽ kết thúc ngay
sau khi được gọi. Có nghĩa là việc viếng thăm trang
http://localhost:9090/blogito/user/login bằng trình duyệt sẽ trả về tệp
grails-app/views/user/login.gsp. (Bạn sẽ tạo ra tệp đó ngay lập tức.)
Việc kết thúc authenticate sử dụng một
phương thức GORM thuận tiện —
findByLoginAndPassword() — để tìm ra thông tin
kiểu như: tìm User trong cơ sở dữ liệu, mà giá
trị login và password khớp với giá trị được đưa vào trong các trường này ở
trên mẫu biểu và làm cho chúng khả dụng thông qua ánh xạ băm params. Nếu User tồn
tại, thêm nó vào một phiên làm việc (session). Ngược lại, quay lại mẫu
biểu đăng nhập và cho User một cơ hội khác để
cung cấp thông tin xác thực đúng. Lệnh đăng xuất logout đưa ra lời chào tạm biệt User, xóa thông tin về người sử dụng trong phiên làm việc và
sau đó chuyển đến hành động list trong EntryController.
Đã đến lúc bạn tạo ra tệp login.gsp rồi. Bạn có thể gõ lại đoạn mã trong ví dụ 3 hoặc bạn có thể:
- Gõ
grails generate-views Usertại cửa sổ dòng lệnh (command line). - Sao chép tệp create.gsp vào tệp login.gsp.
- Sửa đi một chút để có mã kết quả.
Ví dụ 3. login.gsp
<html>
<head>
<meta name="layout" content="main" />
<title>Login</title>
</head>
<body>
<div class="body">
<h1>Login</h1>
<g:if test="${flash.message}">
<div class="message">${flash.message}</div>
</g:if>
<g:form action="authenticate" method="post" >
<div class="dialog">
<table>
<tbody>
<tr class="prop">
<td class="name">
<label for="login">Login:</label>
</td>
<td>
<input type="text" id="login" name="login"/>
</td>
</tr>
<tr class="prop">
<td class="name">
<label for="password">Password:</label>
</td>
<td>
<input type="password" id="password" name="password"/>
</td>
</tr>
</tbody>
</table>
</div>
<div class="buttons">
<span class="button">
<input class="save" type="submit" value="Login" />
</span>
</div>
</g:form>
</div>
</body>
</html>
|
Chú ý rằng action của mẫu biểu là authenticate, nó so khớp tên của bao đóng trong
UserController.groovy. Các tên trong các thành phần nhập liệu
— login và password — phù hợp với các tham số
params.login và params.password trong bao đóng authenticate.
Gõ grails
run-app và thực hiện việc thẩm định quyền của bạn lặp lại vài
lần. Thử đăng nhập với tên người dùng là jsmith
và password là foo. (Nhớ rằng trong " Rewiring Grails with custom URIs and codecs (Mắc nối các Grail với
các URI tùy chỉnh và các bộ mã hóa/giải mã)" bạn đã khởi tạo
Blogito với một cặp người dùng trong grails-app/conf/BootStrap.groovy.)
Việc đăng nhập của bạn bị lỗi, như trong hình 1:
Hình 1. Thông báo lỗi đăng nhập
Thử lại với tên đăng nhập jsmith và password là
wordpass. Lần này việc đăng nhập đã thành
công.
Nếu thông báo chào mừng không xuất hiện trong
grails-app/views/entry/list.gsp — thì đơn giản là bạn hãy
— sao chép khối <g:if
test="${flash.message}"> từ tệp login.gsp và dán vào đầu tệp
list.gsp. Đăng nhập lại với tên jsmith để kiểm
chứng rằng thông báo xuất hiện giống như trong hình 2:
Hình 2. Một thông báo động xác thực việc đăng nhập thành công
Đến đây bạn chắc chắn rằng quyền hạn của mình đã được thực thi, bạn nên
tạo ra một thẻ TagLib sao cho việc đăng nhập và
đăng xuất được dễ dàng.
Việc tạo ra một quyền hạn TagLib
Các trang web như Google và Amazon đưa ra một đoạn văn bản liên kết nho nhỏ trong phần đầu trang cho phép bạn đăng nhập và đăng xuất. Bạn cũng có thể làm được điều tương tự trong Grails với chỉ một ít dòng lệnh.
Để bắt đầu, gõ grails create-tag-lib Login
trong cửa sổ dòng lệnh. Thêm vào đoạn mã trong ví dụ 4 vào tệp
grails-app/taglib/LoginTagLib.groovy vừa được tạo ra:
Ví dụ 4. LoginTagLib.groovy
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"}}]"
}
}
}
|
Bây giờ, thêm một thẻ mới <g:loginControl> vào
grails-app/views/layouts/_header.gsp, như trong ví dụ 5:
Ví dụ 5. Thêm thẻ
<loginControl> vào đầu trang
<div id="header">
<p><g:link class="header-main" controller="entry">Blogito</g:link></p>
<p class="header-sub">A tiny little blog</p>
<div id="loginHeader">
<g:loginControl />
</div>
</div>
|
Để hoàn thành, thêm một số định dạng CSS cho loginHeader <div> vào file web-app/css/main.css, như
trong ví dụ 6:
Ví dụ 6. Định dạng CSS cho
loginHeader <div>
#loginHeader {
float: right;
color: #fff;
}
|
Khi bạn khởi động lại Grails và đăng nhập với tên jsmith, màn hình hiển thị như trong hình 3:
Hình 3. Đăng nhập
TagLib trong hành động
Đến đây, Blogito đã biết bạn là ai. Bước tiếp là việc
giới hạn những thứ mà bạn có thể làm. Ví dụ: mọi người đều có thể đọc
Entry, nhưng chỉ người đăng nhập mới có
quyền tạo mới, sửa đổi và xóa một Entry. Để
hoàn thành việc này, Grails đề xuất một phương thức chặn trước beforeInterceptor, cung cấp cho bạn một phương
tiện hoàn hảo để thực hiện các hoạt động phân quyền trước khi bao đóng
đích được gọi.
Thêm đoạn mã như trong ví dụ 7 vào điều khiển EntryController:
Ví dụ 7. Thêm sự phân quyền vào điều khiển
EntryController
class EntryController {
def beforeInterceptor = [action:this.&auth, except:["index", "list", "show"]]
def auth() {
if(!session.user) {
redirect(controller:"user", action:"login")
return false
}
}
def list = {
//snip...
}
}
|
Một sự khác biệt nho nhỏ nhưng quan trọng giữa auth và list là: list là một bao đóng, trong khi đó auth là một phương thức riêng. (Các bao đóng sử
dụng dấu bằng trong phần định nghĩa, còn phương thức thì sử dụng dấu ngoặc
đơn.) Các bao đóng đối với người dùng giống như một URI, trong khi các
phương thức thì không thể truy cập đến từ trình duyệt.
Phương thức
auth kiểm tra xem một User có trong phiên làm việc không. Nếu không có, màn hình
đăng nhập được trả về và thông báo lỗi, khóa việc gọi bao đóng gốc.
Phương thức auth được gọi trước khi mỗi
bao đóng được gọi bởi beforeInterceptor. Hành
động sử dụng ký hiệu Groovy để chỉ đến phương thức auth của lớp this sử dụng ký tự
(&). Danh sách except chứa các bao đóng nên được bỏ qua khi gọi auth. Bạn có thể thay thế except bởi only nếu bạn chỉ muốn
ngăn chặn việc gọi một số ít bao đóng. (Để biết thêm thông tin về beforeInterceptor, xem Tài
nguyên.)
Khởi động lại Grails và kiểm tra beforeInterceptor. Thử vào trang
http://localhost:9090/blogito/entry/create mà không đăng nhập. Bạn sẽ bị
chuyển đến trang đăng nhập. Hãy đăng nhập vào trang với tên jsmith và thử lại. Lần này bạn có thể tạo thành
công một Entry mới.
Sự phân quyền thô
được đề xuất bởi beforeInterceptor mới chỉ là
một sự khởi đầu cho việc phân quyền, nhưng bạn cũng có thể thêm các móc
nối (hooks) phân quyền cho riêng các bao đóng. Ví dụ, như những gì thể
hiện ở đây, bất cứ User — người
nào đã đăng nhập chứ không riêng gì tác giả — đều có thể
sửa đổi Entry. Bạn có thể đóng lỗ hổng an ninh
đó bằng cách thêm bốn dòng lệnh sau vào vị trí thích hợp trong bao đóng
edit trong tệp EntryController.groovy, như
trong ví dụ 8:
Ví dụ 8. Thêm quyền cho bao đóng
edit
def edit = {
def entryInstance = Entry.get( params.id )
//limit editing to the original author
if( !(session.user.login == entryInstance.author.login) ){
flash.message = "Sorry, you can only edit your own entries."
redirect(action:list)
}
if(!entryInstance) {
flash.message = "Entry not found with id ${params.id}"
redirect(action:list)
}
else {
return [ entryInstance : entryInstance ]
}
}
|
Bạn có thể (và nên) khóa các bao đóng delete và
update bằng cách dùng bốn dòng lệnh nói
trên. Nếu bạn không bằng lòng với việc sao chép các dòng lệnh trên và dán
nó vào nhiều nơi khác nhau (việc này là không nên), bạn có thể tạo ra một
phương thức đơn giản và gọi phương thức này trong ba bao đóng. Nếu bạn
thấy rằng bạn đang sử dụng cùng phương thức beforeInterceptor và các phương thức riêng thông qua nhiều
điều khiển (controllers), bạn có thể đẩy cách xử lý thường gặp vào trong
một điều khiển chủ đơn và có thêm các điều khiển khác mở rộng nó khi cần
bằng cách sử dụng các lớp Java.
Bạn có thể thêm một thứ nữa vào hạ tầng cơ sở thẩm định quyền để làm cho nó mạnh hơn: các vai trò.
Việc gán một vai trò cho các Users là một cách thuận lợi để nhóm người dùng thành từng
nhóm. Bạn có thể gán quyền cho cả nhóm thay vì cho từng người sử dụng. Ví
dụ: ngay bây giờ bất cứ ai cũng có thể tạo ra một User mới. Chỉ việc kiểm tra những người đăng nhập không đảm
bảo an toàn. Tôi thích giới hạn khả năng của người dùng bằng cách giao
quyền quản lý các tài khoản User cho một người
quản trị.
Ví dụ 9 về việc thêm trường vai trò (role) cho User, cũng như ràng buộc về việc giới hạn các giá
trị cho author hoặc admin:
Ví dụ 9. Việc thêm một trường vai trò cho
User
class User {
static constraints = {
login(unique:true)
password(password:true)
name()
role(inList:["author", "admin"])
}
static hasMany = [entries:Entry]
String login
String password
String name
String role = "author"
String toString(){
name
}
}
|
Chú ý rằng giá trị role mặc định được gán bằng
author. Các ràng buộc inList có trong một hộp kết hợp (combo box) với chỉ hai lựa
chọn đúng. Hình 4 cho thấy điều đó khi hoạt động:
Hình 4. Giới hạn các vai trò cho người dùng mới, là
author hoặc là
admin
Tạo ra một admin User trong tệp
grails-app/conf/BootStrap.groovy, như trong ví dụ 10. Đừng quên thêm author role vào hai Users đang tồn tại.
Ví dụ 10. Thêm một
admin User
import grails.util.GrailsUtil
class BootStrap {
def init = { servletContext ->
switch(GrailsUtil.environment){
case "development":
def admin = new User(login:"admin",
password:"password",
name:"Administrator",
role:"admin")
admin.save()
def jdoe = new User(login:"jdoe",
password:"password",
name:"John Doe",
role:"author")
//snip...
def jsmith = new User(login:"jsmith",
password:"wordpass",
name:"Jane Smith",
role:"author")
//snip...
break
case "production":
break
}
}
def destroy = {
}
}
|
Và cuối cùng, thêm đoạn mã trong ví dụ 11 để giới hạn tất cả các User không được hoạt động như những người có vai
trò admin:
Ví dụ 11. Giới hạn việc quản lý tài khoản
User đối với vai trò admin
class UserController {
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
}
}
//snip...
}
|
Để kiểm tra được việc phân quyền dựa trên vai trò, đăng nhập vào blog với
tên jsmith và sau đó thử vào trang
http://localhost:9090/blogito/user/create. Bạn sẽ bị chuyển sang màn hình
đăng nhập như trong hình 5:
Hình 5. Việc chặn truy cập với tài khoản không có quyền người quản trị
Bây giờ hãy đăng nhập như một admin. Bạn có thể
truy xuất đến tất cả các bao đóng.
Chuyển blog lên mức tiếp theo bằng việc sử dụng trình gắn vào (plug-ins)
Các hệ thống xác thực và phân quyên "siêu nhỏ" cho
ứng dụng blog "siêu nhỏ" này hiện đã có. Bạn có thể mở rộng nó dễ dàng
theo các hướng mới. Có lẽ bạn sẽ thích Users có
khả năng quản lý các tài khoản của họ nhưng không thể tác động vào tài
khoản của người khác. Có thể các admins nên
được quyền sửa đổi tất các Entries chứ không
phải chỉ các entry của riêng họ. Trong mỗi trường hợp bạn không có nhiều
hơn hai cách bố trí các dòng lệnh để thêm chức năng mới.
Một lỗi
đơn giản thường gặp là thiếu khả năng đáp ứng yêu cầu. Blogito mới có ít
hơn 200 dòng lệnh — đó là tính cả các lệnh kiểm tra tích
hợp và kiểm tra đơn vị. Gõ grails stats tại cửa
sổ dòng lệnh để xác nhận điều này. Kết quả được hiển thị trong ví dụ 12.
Nhưng việc thiếu sự phức tạp của Blogito không có nghĩa là blog này không
sẵn sàng cho giờ cao điểm.
Ví dụ 12. Kích thước của những ứng dụng "siêu nhỏ"
$ 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 | +----------------------+-------+-------+ |
Ngay từ bài viết đầu tiên trong loạt bài viết này, mục đích của tôi là chỉ
cho bạn thấy sức mạnh vốn có của lõi Grails và biểu diễn ngắn gọn của ngôn
ngữ Groovy. Chẳng hạn, khi bạn hiểu được mã lệnh trong Grails, thật dễ
dàng để băm mật khẩu được lưu trong cơ sở dữ liệu thay vì hiển thị nguyên
dòng chữ. (Để biết thêm thông tin về HashCodec,
xem Tài nguyên.) Tạo ra
grails-app/utils/HashCodec.groovy và thêm đoạn mã vào như trong ví dụ 13:
Ví dụ 13. Việc tạo ra một
HashCodec đơn giản
import java.security.MessageDigest
import sun.misc.BASE64Encoder
import sun.misc.CharacterEncoder
class HashCodec {
static encode = { str ->
MessageDigest md = MessageDigest.getInstance('SHA')
md.update(str.getBytes('UTF-8'))
return (new BASE64Encoder()).encode(md.digest())
}
}
|
Với việc đặt lệnh HashCodec đúng chỗ, chỉ một
sự thay đổi đơn giản về việc tham chiếu, từ User.password trong các bao đóng login, save và update trong điều khiển UserController thành User.password.encodeAsHash(). Một điều đáng ngạc nhiên là chỉ
với 10 dòng lệnh thôi, bạn đã nâng ứng dụng của mình lên một mức phức tạp
mới.
Nhưng chỉ cần giảm bớt một dòng lệnh thôi cũng gây ra sự
chồng chéo tại một số điểm. Trong trường hợp của Grails, câu hỏi kinh điển
"xây dựng hay mua" được chuyển thành "xây dựng hay tải về trình gắn vào."
Một số trình gắn vào tại trang http://grails.org/plugin/list#security+tags
cố gắng giải quyết các thách thức về việc thẩm định quyền và xác thực theo
cách xử lý nhanh của grails install-plugin.
Ví dụ, trình cắm vào Authentication đề xuất một vài đặc tính hấp
dẫn như: cho phép Users đăng ký một tài khoản
thay vì bắt admin tạo ra tài khoản như một
người được ủy quyền. Bạn có thể cấu hình trình cắm vào để gửi một thông
báo xác thực đến User và nói rằng, "Một tài
khoản người dùng mới đã được tạo ra và sử dụng địa chỉ thư điện tử này.
Nhấn chuột vào liên kết để xác thực tài khoản mới của bạn."
Trình cắm vào OpenID đưa ra một cách tiếp cận khác. Thay vì bắt buộc người dùng cuối tạo ra một tổ hợp tên người dùng và mật khẩu mới mà họ chắc chắn sẽ bị quên, sự thẩm định quyền sẽ được ủy nhiệm cho nhà cung cấp OpenID mà họ chọn. Trình cắm vào Lightweight Directory Access (LDAP - Giao thức truy cập thư mục đơn giản) cũng đưa ra cách tiếp cận tương tự, cho phép các ứng dụng Grails thúc đẩy cơ sở hạ tầng LDAP mà bạn đã có.
Sự thẩm định
quyền và các trình gắn vào OpenID giới hạn chính bản thân nó vào câu
chuyện thẩm định quyền. Các giải pháp thẩm định khác cũng đề xuất các giải
pháp phân quyền. Trình cắm vào JSecurity đưa toàn bộ khung làm việc bảo
mật vào trong quá trình chạy, đề xuất các lớp miền soạn sẵn cho Users, Roles, và Permissions. Trình cắm vào Spring Security thúc
đẩy sự phát triển của các thư viện Spring Security (trước đây là Acegi
Security), cho phép bạn sử dụng lại mã nguồn chương trình và tài liệu an
ninh Spring Security mà bạn đã có.
Như bạn đã biết, có nhiều chiến lược xác thực và phân quyền có sẵn trong Grails vì yêu cầu về chúng trong các ứng dụng khác nhau rất nhiều. Với mỗi bước thực hiện thẩm định quyền trong khả năng có thể, độ phức tạp chương trình của bạn sẽ được nâng lên một mức phức tạp tương ứng. Tôi đã từng sử dụng rất nhiều loại trình gắn vào mà tôi đã liệt kê ra ở đây trong ứng dụng của mình, nhưng đó là sau khi tôi đã biết chắc chắn về những lợi ích thiết thực của những chiến lược phân quyền thủ công đơn giản mà tôi đã trình bày với bạn ở đầu bài viết.
Đến
đây bạn đã có một blog Blogito được đảm bảo về mặt an ninh. Users có hai cách để đăng nhập và đăng xuất, đồng
thời cũng có một tập liên kết thuận tiện để làm việc đó. Cảm ơn bạn đã tạo
ra LoginTagLib. Trong một số trường hợp, chỉ
cần đăng nhập đơn giản vào một ứng dụng là đã đảm bảo độ an toàn, như được
biểu thị bởi beforeInterceptor trong điều khiển
EntryController làm nhiệm vụ xác nhận thẩm
quyền. Trong những trường hợp khác, các vai trò làm xuất hiện một lớp phức
tạp khác trong mẫu biểu thực hiện phân quyền. Việc thêm những vai trò đơn
giản cho User cho phép bạn hạn chế việc quản lý
người dùng truy cập vào khu vực quản trị.
Sau khi đã có một blog Blogito an toàn rồi, bài viết tiếp theo về Làm chủ Grails có thể tập trung vào những nhiệm vụ chính sắp tới — đó là việc cung cấp cách thức để xác thực những người dùng được quyền tải tệp và cách những người sử dụng cuối thuê bao một kênh thông tin Atom. Với sự hỗ trợ này, Blogito sẽ thực sự trở thành một ứng dụng blog. Đến khi đó, hãy vui với việc làm chủ Grails.
Học tập
- Làm chủ Grails: Đọc thêm về loạt bài viết này để hiểu
biết thêm về Grails và những gì bạn cần để có thể làm việc với
nó.
- Grails: Vào xem trang Web về Grails.
- Grails Framework Reference Documentation (Tài liệu tham khảo khung
làm việc Grails): The Grails bible.
- Action
Interceptors: Đọc thêm về việc xử lý việc chặn dựa trên yêu cầu
(request), phiên (session), hoặc các trạng thái ứng dụng.
- Simple Dynamic
Password Codec: Bạn có thể tạo ra một cách băm mật khẩu với bộ mã
hóa/giải mã Grails.
- Groovy Recipes
(Scott Davis, Pragmatic Programmers, 2008): Học thêm về Groovy và Grails
trong các cuốn sách mới nhất của Scott Davis.
- Practically Groovy: Đây là một loạt các bài viết trên
developerWork được thiết kế chuyên dụng cho việc khám phá kỹ năng sử dụng
Groovy và hướng dẫn bạn cách thức áp dụng chúng một cách thành
công.
- Groovy: Học thêm về Groovy tại trang Web của dự án.
- AboutGroovy.com: Tìm hiểu những tin tức mới nhất về Groovy và các
kết nối đến các bài viết.
- Kho sách công nghệ: Duyệt tìm sách về chủ đề này và các kỹ thuật
khác.
- developerWorks Java technology
zone: Tìm được hàng trăm bài viết về mỗi khía cạnh của lập trình
Java.
Lấy sản phẩm và công nghệ
- Grails: Tải về phiên
bản mới nhất của Grails.
- Kiểm tra các
trình gắn vào về bảo mật cho Grails:
- Blogito: Bạn có thể tải về ứng dụng
Blogito đầy đủ.
Thảo luận
- Kiểm tra các blog về developerWorks và
các thông tin liên quan trong cộng đồng developerWorks.
Scott Davis là một tác giả, diễn giả và nhà phát triển phần mềm được cộng đồng quốc tế thừa nhận, ông có những cuốn sách như Groovy Recipes: Greasing the Wheels of Java (Các cách thức Groovy: Bôi trơn các bộ máy hoạt động của Java), GIS for Web Developers: Adding Where to Your Application (GIS cho các nhà phát triển web: Thêm vào đâu trong ứng dụng của bạn), The Google Maps API (các bản đồ Google của API) và JBoss At Work (JBoss trong công việc)