Security is a broad topic that is applicable across multiple layers of your application. From the view layer to the database, making your application immune to the various forms of attack is a nontrivial task. Scary things like cross-site scripting (XSS) and SQL injection attacks require careful attention when building your application. As well as covering techniques that help avoid such attacks, in this chapter we'll cover how you can secure your application through authentication and authorization.
Authentication refers to the act of establishing a client's identity. The ubiquitous login form is typically used to establish identity in web applications. Authorization, on the other hand, is about granting a client specific rights (often referred to as privileges or permissions).
Of course, there is no point in reinventing the wheel, so we'll cover how you can use one of the security frameworks already available to implement a more generic solution for authentication and authorization.
Hacking Internet sites has become a challenge for not just malicious individuals but also for security firms that research potential holes in an application's makeup. The more media coverage an application has, the more likely it is subject to such attacks. Banks and large Internet sites are at particular risk.
When developing an application, you should pay careful attention to the security requirements. Is it exposed to the outside world, or is it an intranet application? What are the implications of a breach? An application with heightened security requirements will take longer to develop and require more user acceptance testing and probing.
As for Grails, some vulnerabilities are completely beyond its control. No matter how cautious you are, Grails won't save you if there is a vulnerability at the operating system or web server level. Having said that, Grails does provide you with the tools to implement application-layer security, but ultimately it is up to you to keep security at the forefront of your mind. Unit and functional testing can help you spot problems in this area. Your application can be breached in many ways. In the next few sections, we'll cover some of those ways and how you can help avoid any issues occurring in the first place.
One way to launch a Denial of Service (DoS) attack is to use SQL or HQL injection. Essentially, if you use HQL that is built up from values obtained from request parameters, it is possible for an attacker to modify the incoming parameters to give the HQL a different meaning. This may cause invalid data to be returned from the HQL query or, worse still, data held in the database to be removed or changed! To illustrate the problem, consider the code in Listing 14-1.
Listing 14-1. An Action Vulnerable to HQL Injection
def search = {
Album.findAll("from Album as a where a.title='"+ params.title +"'")
}
With the code in Listing 14-1, an attacker could pass a value as the title
parameter that could compromise the query and lead to a DoS attack. For example, say the attacker decided to send a request parameter with the following value:
' or a.title not null
This would result in the following HQL query:
from Album as a where a.title='' or a.title not null
The result is that instead of returning only a few records, the query could return thousands or millions of records, causing a potential OutOfMemoryError
. Worse still, if the attacker initiates 10,000 requests using the same parameters, you could get threads blocking while these long-running queries execute. With no threads left in the pool, your server will become unresponsive, and the hacker will have successfully completed a DoS attack.
Of course, this phenomenon is not specific to Grails; any application that builds HQL or SQL up dynamically comes up against it. So, how do you prevent such an attack? The secret is never, ever to build up queries from String
values, as you saw in Listing 14-1. Instead, use either named or ordinal parameters for the query or, even better, criteria queries. Listing 14-2 shows four possible alternatives to the query from Listing 14-1.
Listing 14-2. Alternatives That Avoid HQL Injection
// using ordinal parameters
Album.findAll("from Album as a where a.title = ?", [params.title])
// using named parameters
Album.findAll("from Album as a where a.title = :title", [title:params.title])
// using criteria
Album.withCriteria {
eq('title', params.title)
}
// using a dynamic finder
Album.findAllByTitle(params.title)
In all the examples from Listing 14-2, Hibernate will automatically deal with escaping the values passed into the query, making it impossible to execute an HQL injection attack. In the next section, we'll show another potential avenue for attack that is specific to Groovy and Grails— Groovy injection.
HQL injection vulnerabilities are dangerous for sure, but the unguarded parsing of Groovy scripts from user input could be even more harmful. Called Groovy injection, this involves accepting input from a user that is then executed as a Groovy script. Listing 14-3 shows an example of this technique.
Listing 14-3. Groovy Injection
def execute = {
new GroovyShell().evaluate(params.script)
}
Writing code like that shown in Listing 14-3 is, to be blunt, not the smartest thing to do. Bringing the whole container down is a simple matter of sending a parameter with the following value:
System.exit(1)
Or worse, the user could send code that modifies key system files, corrupting the operating system. The GroovyShell
class places no restrictions on what code the user is able to run. Generally, as is the case with other dynamic languages such as Ruby and JavaScript, it is not advisable to dynamically evaluate user input in this manner. If you really must have this functionality, then you need to make sure the GroovyShell
instance is set up with the appropriate Java security permissions. The Groovy website has good documentation on how to achieve this at http://groovy.codehaus.org/Security.
XSS attacks are probably the most well known but least understood security exploit. The technique involves injecting JavaScript written by the attacker into the page. An attacker able to control the JavaScript on your site is an incredibly dangerous scenario. She could do all manner of things, from stealing a user's cookie to changing a login form so that it sends requests to another server that captures usernames and passwords.
XSS attacks are amazingly common; the site xssed.com even keeps an up-to-date list of the latest known vulnerabilities in major public sites. You'll notice many prominent industry names there; as you can see, even some of the most well-known companies in the software industry make mistakes. The main reason XSS attacks are so common is that they are very hard to test for. Automated testing in most cases is insufficient to trace every potential XSS problem. In fact, the current implementation of the gTunes application already has an XSS vulnerability that we left in there on purpose (honest!). To reproduce it, try the following:
<script type="text/javascript">alert ('hello')</script>
.Figure 14-1 shows the form populated with the data from these steps.
Figure 14-1. Entering malicious data into the registration form
When you click the "Register" button, you'll see an alert box pop up with the message "hello." The JavaScript you entered into the "First Name" field has been executed! The gTunes application is currently vulnerable to an XSS attack. Figure 14-2 shows an example of the message box appearing in Firefox.
Figure 14-2. An XSS vulnerability in action
But why? The reason for the vulnerability lies in the grails-app/views/user/_welcomeMessage.gsp
template. If you look at the code for the template, it has the following snippet of HTML:
Welcome back <span id="userFirstName">${session?.user?.firstName}!</span><br><br>
Using the GSP expression syntax ${..}
on the first name simply dumps out the value; there is no HTML escaping happening here. So, what is the solution? A robust and future-proof solution would be to make all ${..}
expressions HTML escaped by default using the grails.views.default.codec
setting in grails-app/conf/Config.groovy
:
grails.views.default.codec="html"
By setting the default codec Grails uses to encode data in GSP views to HTML, you can ensure all GSP expressions are HTML escaped by default. The downside of this approach is that if you're using GSPs to produce any format other than HTML, such as JSON or raw text, then this may be problematic since the setting is global. An alternative is to use the defaultCodec
page directive to enable HTML escaping on a page-by-page basis:
<%@ defaultCodec="html" %>
By inserting the previous line of code at the top of a GSP, you can enable escaping all expressions for only the current page. Finally, you can also use the encodeAsHTML()
method provided by Grails to explicitly encode the data, as shown in Listing 14-4.
Listing 14-4. Using encodeAsHTML to HTML Escape a Value
Welcome back
<span id="userFirstName">${session?.user?.firstName?.encodeAsHTML()}!</span><br><br>
Another important thing to note is that Grails' built-in form tags, such as <g:textField>
, automatically use the encodeAsHTML()
method for you. So, you need to be concerned only when the data is being used outside of Grails' built-in tags.
In the previous section, you saw how a user can launch an XSS exploit if you don't correctly encode data as HTML by calling the encodeAsHTML()
method. However, when creating URLs programmatically from user input, it is equally important to URL encode the data used to make up a link. If you're using Grails' built-in <g:link>
tag and all the other built-in tags that use URLs, then you don't have to worry. Grails will ensure all the data is appropriately URL encoded.
However, if you decide to bypass the built-in tags and do your own link creation, maybe through a tag library, then it is critical you URL escape the programmatically created links. Listing 14-5 shows an example of a potentially vulnerable link.
Listing 14-5. A Vulnerable Link
<a href="/gTunes/albums?title=${params.title}">Show Album</a>
Simply by fiddling with the title
parameter in a GET
request an attacker could perform an XSS attack. To avoid this problem, you can call the encodeAsURL()
method on any data to be included in the URL. Listing 14-6 shows an example of this.
Listing 14-6. Escaping URLs
<a href="/gTunes/albums?title=${params.title?.encodeAsURL()}">Show Album</a>
You'll be learning more about the encodeAsHTML()
and encodeAsURL()
methods in the section "Using Dynamic Codecs." For now, let's stay on the topic of vulnerabilities with a further look into DoS attacks.
You've already seen how HQL injection can be used to cause a DoS attack and bring your system down. However, there are other ways you can be vulnerable to a DoS attack even if you avoid using String
concatenation to build queries. One of the most common ways is through pagination. As you'll recall, GORM methods like list
and the dynamic finders accept parameters such as offset
and max
that allow you to paginate through the records available in the database. Listing 14-7 presents an example of a simple list
action that does this.
Listing 14-7. Listing All Albums
def list = {
if(!params.max) params.max = 10
[albumList: Album.list(params)]
}
As innocent as it may seem, the code in Listing 14-7 is vulnerable to a DoS attack. The reason is that the code doesn't set the maximum value of the max
argument. An attacker could pass a max
value of 1000000
, and you could end up with a million records loading and the same OutOfMemoryError
and thread blocking issues we mentioned earlier. Ouch!
A better solution is to ensure that you constrain the value of the max
parameter passed to a query to not exceed a specific value. Listing 14-8 shows an example implementation that ensures the max
parameter can only ever reach 100.
Listing 14-8. Constraining the Maximum Value for Pagination
def list = {
params.max = Math.min( params.max?.toInteger() ?: 0, 100)
[albumList: Album.list(params)]
}
As you can see from the code in Listing 14-8, you can use the Math.min
method to get a safe maximum value to use when paginating data. We're not done with potential vulnerabilities just yet, though. In the next section, you'll look at one that affects data binding.
Many web frameworks, including Grails, allow you to bind the data of incoming request parameters to objects. In the case of Grails, these are typically domain instances. Data binding was covered in depth in Chapter 4, but just as a reminder, with Grails it can be done with the following constructor:
def album = new Album(params)
or alternatively using the properties
property of an existing domain instance:
def album = Album.get(params.id)
album.properties = params
In many scenarios, this is not a problem, because a trusted source may be performing the update. However, in some cases, using this technique can be undesirable. Consider, for example, a scenario where you used a simple flag on a User
domain class to signify whether the User
is an administrator:
class User {
...
boolean administrator
}
Administrators have far-reaching powers over the system that only a select few are allowed to have. To set the scene further, say you had a profile page where a user can change her password, phone number, and various personal details. Listing 14-9 shows the server-side code to update the User
instance.
Listing 14-9. Vulnerable Controller Action
def update = {
def user = User.get(params.id)
user.properties = params
if(user.save()) {
redirect(action:"profile", id:user.id)
}
...
}
The form that sends the request to the update
action in Listing 14-9 has fields that only the User
is allowed to edit. However, a particularly malicious individual could spoof a request so that it sent a parameter called administrator
with a value of true
. The result would be the User
gaining newfound powers and, potentially, compromising your system.
In this scenario, you should make sure you are explicit about what properties can be updated. Listing 14-10 shows a corrected version of the code in Listing 14-9 that uses the subscript operator on the properties
property to specify which properties are subject to data binding.
Listing 14-10. Correcting the Data Binding Vulnerability
def update = {
def user = User.get(params.id)
user.properties['firstName', 'lastName', 'phoneNumber','password'] = params
if(user.save()) {
redirect(action:"profile", id:user.id)
}
...
}
The key message with all these attacks is to make sure that when you accept input from the user, you are aware of the risks of doing so. Grails provides you with all the tools necessary to avoid attacks but will not magically save you from writing vulnerable code. So far in this chapter, you've seen the use of encodeAsURL()
and encodeAsHTML()
; in the next section, we'll cover how these methods came about and how you can add your own custom versions.
Throughout the course of the chapter so far, you've seen examples of the encodeAsHTML()
and encodeAsURL()
methods. These methods didn't magically appear out of nowhere; codec classes that ship with Grails provide them. For example, the encodeAsHTML()
method is implemented in Grails as shown in Listing 14-11.
Listing 14-11. An Example Codec Class
import org.springframework.web.util.HtmlUtils
class HTMLCodec {
static encode( theTarget ) {
HtmlUtils.htmlEscape(theTarget?.toString())
}
static decode( theTarget ) {
HtmlUtils.htmlUnescape(theTarget?.toString())
}
}
Essentially, a codec class is one that ends with the convention Codec
and includes encode
and/or decode
methods. Grails will automatically create encodeAsHTML()
and decodeHTML()
methods that delegate to the HTMLCodec
class in Listing 14-11 at runtime. The interesting thing is that you can provide your own custom codecs. For example, say you wanted to provide the ability to encrypt data using the Blowfish encryption algorithm that is part of the Java Cryptography Extension (JCE) provided by Sun at http://java.sun.com/javase/technologies/security/. Thanks to custom codecs, this is pretty easy: all you need to do is create a new codec class in the grails-app/utils
directory called BlowfishCodec.groovy
and populate it with the code in Listing 14-12.
Listing 14-12. A Blowfish Encryption Codec Class
import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH
import java.security.*;
import javax.crypto.*;
import javax.crypto.spec.*;
class BlowfishCodec {
static encode(target) {
def cipher = getCipher(Cipher.ENCRYPT_MODE)
return cipher.doFinal(target.bytes).encodeBase64()
}
static decode(target) {
def cipher = getCipher(Cipher.DECRYPT_MODE)
return new String(cipher.doFinal(target.decodeBase64()))
}
private static getCipher(mode) {
def keySpec = new PBEKeySpec(getPassword())
def cipher = Cipher.getInstance("Blowfish")
def keyFactory = SecretKeyFactory.getInstance("Blowfish")
cipher.init(mode, keyFactory.generateSecret(keySpec))
}
private static getPassword() { CH.config.secret.key.toCharArray() }
}
The BlowfishCodec
implementation shown in Listing 14-12 uses the Java cryptography APIs to construct a Cipher
using a password set in grails-app/conf/Config.groovy
. The method getPassword()
inspects the config
object provided by importing the org.codehaus.groovy.grails.commons.ConfigurationHolder
class:
private static getPassword() { CH.config.secret.key.toCharArray() }
The getCipher(mode)
then uses the getPassword()
method to construct an instance of the javax.crypto.spec.PBEKeySpec
class that is used for password-based encryption. A javax.crypto.Cipher
instance is then obtained using the Blowfish algorithm and initialized using the appropriate mode:
private static getCipher(mode) {
def keySpec = new PBEKeySpec(getPassword())
def cipher = Cipher.getInstance("Blowfish")
def keyFactory = SecretKeyFactory.getInstance("Blowfish")
cipher.init(mode, keyFactory.generateSecret(keySpec))
}
Finally, the encode
and decode
closures then use the cipher
to encrypt and decrypt the necessary bytes. Notice how this codec is actually using the Base64Codec
built into Grails to return the byte[]
as a Base-64 encoded String
. Now to encrypt data, you can simply call the encodeAsBlowfish()
method:
def encrypted = "This is some secret info".encodeAsBlowfish()
And to perform the associated decryption, you can call the decodeBlowfish()
method:
def unencrypted = encrypted.decodeBlowfish()
We'll leave to your imagination what else might be possible with codec classes. They're certainly a pretty powerful way to provide common encoding and decoding methods across your application and yet another example of the use of conventions in Grails to enhance behavior. In the next section, we'll take a diversion into the topic of authentication and authorization, including coverage of the available security plugins for Grails.
Application-layer security, which consists of authenticating users at login and authorizing authenticated users to perform certain functions, is used in most nontrivial applications. In Chapter 4, you saw how to roll your own authentication mechanism with the UserController
class, a trivial implementation that simply checks that a user exists in the database. Until now, however, we have not explained how authorization works through roles and permissions.
As simple as it is to implement your own login mechanism, as your application grows you'll feel the need for more complex security rules. You could use roles to distinguish access to parts of the system—for example, is the user an administrator or a regular user? You may also want fine-grained permission access to individual resources. Typically, but not always, a role consists of multiple permissions.
Rolling your own solution for all of these, potentially complex, security scenarios is rather wasteful given the abundance of security frameworks available for Grails. Currently, three widely used plugins offer security features to Grails:
Acegi (Spring Security) plugin (http://www.grails.org/AcegiSecurity+Plugin): This integrates Grails with Spring Security (http://static.springframework.org/spring-security/site/, formerly Acegi), a security framework that is part of the Spring portfolio of products.
Authentication plugin (http://www.grails.org/Authentication+Plugin): The Authentication plugin is a simple security plugin that provides login and registration out of the box. Designed to use sensible defaults to configure most aspects authentication automatically, it lets you customize the behavior of the plugin via events.
JSecurity plugin (http://www.grails.org/JSecurity+Plugin): The JSecurity plugin integrates the JSecurity framework for Java (http://www.jsecurity.org/) with Grails. It provides helpers to automatically generate login and registration functionality.
In the next section, we'll cover filters, a feature of Grails that underpins all of these frameworks. After that, we'll dive headfirst into integrating the JSecurity plugin into the gTunes application.
Security is one of those problems that Aspect-Oriented Programming (AOP) advocates often point to as a prime example of a crosscutting concern. In other words, security rules often apply to multiple URIs, classes, and even methods across an application. Getting your security logic mixed in with your business logic is definitely undesirable. Typically, you need to authorize a user to execute certain methods, which can result in security logic being mixed with application logic.
In Grails, you can use filters to execute code before and after a controller action. To add a set of filters in Grails, you need to create a class that ends with the convention Filters in your application. A typical place to do this is in the grails-app/conf
directory. For example, Listing 14-13 shows a LoggingFilters
implementation that logs request information before and after each request.
Listing 14-13. An Example Filters Class
class LoggingFilters {
static filters = {
all(controller:"*", action:"*") {
before = {
log.debug "Parameters: ${params.inspect()}"
}
after = { model ->
log.debug "Model: ${model?.inspect()}"
}
}
}
}
As you can see from Listing 14-13, within the LoggingFilters
definition you define a single static property called filters
that is assigned a block of code. Then, within the body of this block of code, you can define one or more filters. The example in Listing 14-13 defines a single filter called all
that applies to all
actions within all controllers:
all(controller:"*", action:"*") {
Notice the usage of the wildcard (*
) character to signify that this filter applies to all actions and controllers. Instead of a wildcard, you can also define a specific controller and/or action:
secure(controller:"admin", action:"*") {
Alternatively, if you prefer URI-based filters, then you can use the uri
argument:
secure(uri:"/admin/**") {
In addition, the values you pass to any of the arguments, such as controller
and action
, are actually just regular expressions. Hence, if you need to apply a filter to multiple controllers, you can use regex:
secure(controller:"(admin|secure)", action:"*") {
The last argument of each filter definition is a block of code that you can use to define a before
filter:
before = {
log.debug "Parameters: ${params.inspect()}"
}
A before
filter can also return false
, which signifies that the intercepted action should not be executed, something that is critical for security plugins. As well as the before
filter, there is also an after
filter:
after = { model ->
log.debug "Model: ${model?.inspect()}"
}
As you can see, the after
filter is a little special because it gets passed the model
that the view will use to render. Note also that the after
filter gets executed before view rendering. If you want to execute a filter after the view has rendered, you can use the afterView
filter, as shown in Listing 14-14.
Listing 14-14. Using the afterView Filter
after = {
request.currentTime = System.currentTimeMillis()
}
afterView = {
log.debug "View took ${System.currentTimeMillis()-request.currentTime}ms"
}
Listing 14-14 shows an example that profiles how long it takes for view rendering to complete. As you can see, filters provide an excellent mechanism for implementing crosscutting concerns, because they can be applied across multiple controllers and/or actions. For example, Listing 14-15 shows a very trivial security filter that checks whether a user is logged in.
Listing 14-15. A Security Filter
class SecurityFilters {
def filters = {
loginCheck(controller:'*', action:'*') {
before = {
if(!session.user && actionName != 'login') {
redirect(action:'login')
return false
}
}
} } }
The security plugins available for Grails make extensive usage of its filters mechanism. In the next section, we'll talk about the JSecurity plugin as an example.
The JSecurity plugin builds on the excellent JSecurity library (http://www.jsecurity.org/) to provide authentication and authorization to a Grails application. The JSecurity plugin works by combining a set of one or more security filters with a security realm. The realm is the bridge between JSecurity and Grails, and it provides methods that you can implement to facilitate authentication and authorization. To get started with JSecurity, you have to install the plugin by running the install-plugin
command, as shown in Listing 14-16.
Listing 14-16. Running the install-plugin command
$ grails install-plugin jsecurity
...
Plugin jsecurity-0.2.1 installed
Plug-in provides the following new scripts:
------------------------------------------
grails create-auth-controller
grails create-db-realm
grails create-ldap-realm
grails quick-start
As you can see from the output in Listing 14-16, the JSecurity plugin provides various additional commands that help you integrate it with Grails, the details of which are listed here:
create-auth-controller
: This creates a controller that implements logging in and logging out using JSecurity APIs.create-db-realm:
If you don't already have a domain model that represents users and roles, this command will create one that uses GORM to store user information to the database.create-ldap-realm
: This creates a realm that authenticates users against a configured LDAP server.quick-start
: This combines the create-db-realm
and create-auth-controller
commands to set up JSecurity in a single command.Both the create-db-realm
and create-ldap-realm
classes set up a realm class that deals with rights management. In other words, the realms dictate who can access your system, as well as what roles and permissions they have once the user has authenticated. A realm class is a class that lives in the grails-app/realms
directory and that ends with the convention Realm. Although there are no further requirements, for realm classes to be useful they should implement some or all of the methods shown in Listing 14-17.
Listing 14-17. Methods of a Realm
def authenticate(authToken)
def hasRole(principal, roleName)
def isPermitted(principal, permission)
The authenticate
method is called when a user tries to sign in to your application. The argument passed to the authenticate
method is an instance of the org.jsecurity.authc.AuthenticationToken
interface. The default implementation assumed by JSecurity is org.jsecurity.authc.UsernamePasswordToken
, which uses username/password-based authentication. However, you can change the authentication token mechanism used by setting the authTokenClass
static property of the realm class:
static authTokenClass = org.jsecurity.authc.UsernamePasswordToken
The hasRole
and isPermitted
methods both accept a principal
, which is the unique value used to identify the user returned by the authenticate
method. We'll be returning to roles and permissions in a moment; first we'll address the notion of subjects and principals.
A subject, in JSecurity terms, is a person or entity who is currently accessing your application. Modeled by the class org.jsecurity.subject.Subject
, a subject does not have to be logged in and can be in one of three states:
Unknown
: The application doesn't know who the user is.Remembered
: The application remembers the user from a previous session.Authenticated
: The user has successfully logged in, by entering their credentials, and the application knows who they are.You can obtain the current Subject
instance at any time using the org.jsecurity.SecurityUtils
class, which has a static getSubject()
method, as shown in Listing 14-18.
Listing 14-18. Using SecurityUtils to Obtain the Subject
def subject = org.jsecurity.SecurityUtils.getSubject()
println "User ${subject.principal} is authenticated? ${subject.authenticated}"
As shown in Listing 14-18, the Subject
has a getPrincipal()
method that returns the principal. As mentioned, the principal is the unique identity of a user such as a login name, email address, or Social Security number.
In a role-based system, a role represents a function or set of responsibilities a user may have. If you were developing a content management system (CMS), you might have roles such as Administrators, Editors, and Users. Although an Administrator would have overall access to all parts of the system, lesser beings such as simple Users would be able to access only a limited set of functions. Permissions, on the other hand, represent a much finer-grained level of control. In fact, a role typically consists of a collection of permissions. For example, considering the gTunes application permissions might include asking the following questions:
Album
X?In JSecurity, a permission is modeled by the org.jsecurity.authz.Permission
interface shown in Listing 14-19.
Listing 14-19. The org.jsecurity.authz.Permission Interface
package org.jsecurity.authz
interface Permission {
boolean implies(Permission p)
}
As you can see from Listing 14-19, the Permission
interface defines a single method called implies(Permission)
. The idea here is that typically a user gets assigned a collection of permissions. If one of the permissions assigned to that user allows access to the Permission
passed as an argument to the implies(Permission)
method, then true
will be returned. As an example, to create an Administrator user, you could use the org.jsecurity.authz.permission.AllPermission
permission, which implies access to all other permissions by always returning true
from the implies(Permission)
method.
The validation of roles and permissions can and should occur at multiple levels, from the view to the controller layer. In the next section, we'll show how to apply these ideas to the gTunes application by using JSecurity to secure access to various parts of the application.
As mentioned previously, JSecurity comes with built-in commands that allow you to generate a domain model and authentication realm. However, since you already have a domain model and because it will help you understand the intricacies of JSecurity, you're going to build a custom JSecurity realm class.
To get started, you need to create new realm class in the grails-app/realms
directory. In Figure 14-3 you can see we've created a new realm class called AuthRealm
in the com.g2one.gtunes
package.
Figure 14-3. The AuthRealm class
The next step is to implement the authenticate
method for the AuthRealm
class, as shown in Listing 14-20.
Listing 14-20. The authenticate Method of the AuthRealm
class AuthRealm {
static authTokenClass = org.jsecurity.authc.UsernamePasswordToken
CredentialsMatcher credentialMatcher
def authenticate(authToken) {
...
}
}
As you can see, the code uses the org.jsecurity.authc.UsernamePasswordToken
class for the authToken
instance that will be passed to the authenticate
method. Since there is already an existing com.g2one.gtunes.User
domain class that contains login
and password
properties, the UsernamePasswordToken
class is a logical choice here.
Another thing you'll note from the code in Listing 14-20 is the credentialMatcher
property. This property is injected by Spring at runtime. The default implementation used is the org.jsecurity.authc.credential.Sha1CredentialsMatcher
, which expects that credentials that are stored are SHA hashed, a pretty common practice.
You can, however, replace the CredentialsMatcher
implementation simply by defining a new Spring bean in the grails-app/conf/spring/resources.groovy
file called credentialMatcher
. For example, the following code defines a CredentialsMatcher
that uses MD5 hashing instead:
credentialMatcher(org.jsecurity.authc.credential.Md5CredentialsMatcher)
Returning to the authenticate
method, the UsernamePasswordToken
instance defines a username
property that you can use to obtain the username of the user, as shown in Listing 14-21.
Listing 14-21. Obtaining the Username of a User
def username = authToken.username
// Null username is invalid
if (username == null) {
throw new AccountException('Null usernames are not allowed by this realm.')
}
As you can see from Listing 14-21, if the username
is null
, an org.jsecurity.authc.AccountException
is thrown. JSecurity provides a number of built-in exception types within the org.jsecurity.authc
package, the examples of which are listed here:
AccountException
: Thrown because of a problem with the account under which an authentication attempt is being executedConcurrentAccessException
: Thrown when an authentication attempt has been received for an account that has already been authenticatedDisabledAccountException
: Thrown when attempting to authenticate and the corresponding account has been disabled for some reasonExcessiveAttemptsException
: Thrown when a system is configured to allow only a certain number of authentication attempts over a period of time and the current session has failed to authenticate successfully within that numberExpiredCredentialsException
: Thrown during the authentication process when the system determines the submitted credentials has expired and will not allow a loginIncorrectCredentialsException
: Thrown when attempting to authenticate with credential(s) that do not match the actual credentials associated with the account principalUnknownAccountException
: Thrown when attempting to authenticate with a principal that doesn't exist in the system (for example, by specifying a username that doesn't relate to a user account)It is up to your implementation of the authenticate
method to throw the appropriate exceptions for each reason, but as you can see, JSecurity provides exception types for most common cases. For example, Listing 14-22 shows the next step needed to implement authenticate
appropriately by throwing an UnknownAccountException
if the User
instance is not found for the specified username
.
Listing 14-22. Throwing an UnknownAccountException If a User Is Not Found
def user = User.findByLogin(username)
if (!user) {
throw new UnknownAccountException("No account found for user $username")
}
As you can see from Listing 14-22, it is at this point that you are able to connect JSecurity with your existing domain model. However, if a User
instance is found, you want to make sure that said user's password is correct. Listing 14-23 shows an example of how to achieve this.
Listing 14-23. Validating User Credentials
def account = new SimpleAccount(username, user.password, "gTunesRealm")
if (!credentialMatcher.doCredentialsMatch(authToken, account)) {
throw new IncorrectCredentialsException("Invalid password for $username")
}
Notice how in Listing 14-23 you need to construct an instance of the org.jsecurity.authc.SimpleAccount
class, which takes the principal (in this case the username
), the credentials, and the name of the realm. Once constructed, you can then use the credentialMatcher
instance's doCredentialsMatch
method to validate the user's authentication token. If the token is not valid, an IncorrectCredentialsException
is thrown. If all is well, the final thing to do is to return the user's principal:
return username
And with that, you've completed the implementation of the authenticate
method. Listing 14-24 shows the full code listing from the authenticate
method.
Listing 14-24. The authenticate Method
def authenticate(authToken) {
def username = authToken.username
// Null username is invalid
if (username == null) {
throw new AccountException('Null usernames are not allowed by this realm.')
}
// Get the user with the given username. If the user is not
// found, then they don't have an account and we throw an
// exception.
def user = User.findByLogin(username)
if (!user) {
throw new UnknownAccountException("No account found for $username")
}
// Now check the user's password against the hashed value stored
// in the database.
def account = new SimpleAccount(username, user.password, "gTunesRealm")
if (!credentialMatcher.doCredentialsMatch(authToken, account)) {
throw new IncorrectCredentialsException("Invalid password for $username")
}
return username
}
Now all that is left to do is implement a controller that can take advantage of the realm. A simple way to do this is to run the create-auth-controller
command, which will generate a controller that uses JSecurity to authenticate. However, since the gTunes application already has a UserController
, you're going to modify that instead and at the same time get a chance to explore JSecurity's APIs.
To authenticate with JSecurity, you need a reference to the org.jsecurity.mgt.SecurityManager
instance, the interface for which is shown in Listing 14-25.
Listing 14-25. The org.jsecurity.mgt.SecurityManager Interface
interface SecurityManager {
Subject getSubject()
Subject login(AuthenticationToken authenticationToken)
void logout(PrincipalCollection subjectIdentifier)
}
To obtain a reference to the SecurityManager
, you need to use dependency injection via Spring using a bean called jsecSecurityManager
. If you recall, the current UserController
uses a command object, called LoginCommand
, to handle login processing. Command objects can participate in dependency injection using Spring by simply declaring a property within the command class that matches the bean name:
def jsecSecurityManager
Using the SecurityManager
instance's login(AuthenticationToken)
method, you can then authenticate users based on the parameters bound to the LoginCommand
. Listing 14-26 shows the updated LoginCommand
class that uses the jsecSecurityManager
for authentication.
Listing 14-26. A LoginCommand Definition That Uses JSecurity for Authentication
class LoginCommand {
String login
String password
def jsecSecurityManager
boolean authenticate() {
def authToken = new UsernamePasswordToken(login, password)
try{
this.jsecSecurityManager.login(authToken)
return true
}
catch (AuthenticationException ex){
return false
}
}
static constraints = {
login blank:false, validator:{ val, cmd ->
if(!cmd.authenticate())
return "user.invalid.login"
}
password blank:false
}
}
You can see the guts of the logic in the authenticate()
method of the LoginCommand
in Listing 14-26. Initially, a new UsernamePasswordToken
instance is constructed and passed to the jsecSecurityManager
bean's login(AuthenticationToken)
method. If the login(AuthenticationToken)
method completes without an exception being thrown, the authenticate()
method returns true
, signaling a successful login. Otherwise, if an exception is thrown, false
is returned. The other major change to the LoginCommand
is that the login
constraint now calls the command's authenticate()
method and returns a code called user.invalid.login
if authentication failed.
You could write code to handle specific AuthenticationException
instances, such as UnknownAccountException
, and return different error codes based on each exception. Nevertheless, the code serves to demonstrate how to use a command object to authenticate via JSecurity. As for the login
action of the UserController
, it doesn't need any changes since the command object itself encapsulates the logic of logging in.
However, what does need a change is the register
action. This currently stores passwords in plain-text form, but JSecurity is expecting an SHA1 hash of the password by default. Listing 14-27 shows the changes made to the register
action to provide an SHA1 hash of the password.
Listing 14-27. Hashing a Password with SHA1
import org.jsecurity.crypto.hash.Sha1Hash
class UserController {
...
def register = {
if(request.method == 'POST') {
...
if(u.validate()) {
u.password = new Sha1Hash(u.password).toHex()
u.save()
...
}
...
}
}
...
}
Adding the ability to authenticate users wouldn't be of much use if you didn't have the ability to secure areas of a Grails application that require authentication. The JSecurity plugin for Grails uses the filters mechanism discussed earlier in the chapter in order to authenticate users. To begin with, you need to define a filters class. For example, you could create an AuthFilters
class, as shown in Figure 14-4.
Figure 14-4. The grails-app/conf/com/g2one/tunes/AuthFilters.groovy file
With the filters class in place, you need to define a static filters
property that is assigned a block of code, the body of which will contain the filter definitions. Listing 14-28 shows the AuthFilters
class with the filters
static property in place.
Listing 14-28. The AuthFilters Class
package com.g2one.gtunes
class AuthFilters {
static filters = {
...
}
}
By default JSecurity allows all requests through without authentication, so you need to define which controllers, actions, and/or URIs require authentication. You can do so by calling the accessControl
method within the definition of a before
filter. For example, users are required to log in to purchase music, so you need to secure the buy
action of the StoreController
, as shown in Listing 14-29.
Listing 14-29. Securing an Action
static filters = {
purchasing(controller:"store", action:"buy") {
before = {
accessControl()
}
}
...
}
To deal with authentication failures that arise from a filters class, you need to implement the onNotAuthenticated(Subject, controller)
method. Listing 14-30 shows the implementation used by the gTunes application.
Listing 14-30. Implementing the onNotAuthenticated Method
def onNotAuthenticated(subject, d) {
if (d.request.xhr) {
d.render(template:"/user/loginForm", model:[message:"user.not.logged.in"])
}
else {
// Redirect to login page.
d.flash.message = "user.not.logged.in"
if(d.actionName == 'buy') {
d.redirect(controller:"album", action:"display", id:d.params.id)
}
else {
d.redirect(controller:"store", action:"shop")
}
}
}
The logic here is a bit more convoluted because it deals both with Ajax requests, by checking the request.xhr
property, and with regular requests. Additionally, if the actionName
is the buy
action, then there is some logic in there to take the user back to the Album
they were trying to buy. You could, of course, redirect to the original URI using the forwardURI
property, but since there isn't a use case yet for this in the gTunes application, the implementation in Listing 14-30 will do fine.
If you need to secure access for a specific role, you can pass a block to the accessControl
method that contains a call to the method role(name)
that defines the role. For example, the blogging feature you added via a plugin in Chapter 13 can be secured so that only administrators can access the feature using the following syntax:
blogEditing(controller:"blog", action:"(create|save)") {
before = {
accessControl {
role('ADMINISTRATOR')
}
}
}
Notice how the previous filter applies to both the create
and save
actions of the BlogController
. Currently the role(name)
method is being called to allow access to a role named ADMINISTRATOR
, but if you wanted to allow access to more than one role, you can use the |
operator:
accessControl {
role('ADMINISTRATOR') | role('EDITOR')
}
Here users who are in either the ADMINISTRATOR
role or the EDITOR
role can create blog posts. Of course, you have not yet implemented the hasRole
method in the AuthRealm
class, so no one at this point has access to these areas of the gTunes site. In the next section, you'll rectify that by implementing role-based security.
Currently, the gTunes domain model does not define the concept of a role. To correct this, you need to create a new domain class called Role
using the create-domain-class
command:
$ grails create-domain-class com.g2one.gtunes.Role
Once complete, you'll end up with a new domain class in the grails-app/domain/com/g2one/gtunes
directory called Role.groovy
. A good way to implement a role is using a type-safe enum
. Listing 14-31 shows the code for the Role
class that uses an enum
called RoleName
containing the different role names.
Listing 14-31. The Role Domain Class
package com.g2one.gtunes
class Role implements Serializable {
RoleName name
}
enum RoleName {
USER, EDITOR, ADMINISTRATOR
}
The next step is to update the com.g2one.gtunes.User
domain class to associate a user with a set of roles. Listing 14-32 shows the changes to the User
domain class with the addition of a roles
association.
Listing 14-32. Adding a roles Association to the User Domain Class
class User implements Serializable{
...
static hasMany = [ purchasedAlbums:Album,
purchasedSongs:Song,
roles:Role ]
}
In addition, users who register with the gTunes site should be assigned the default role of USER
. To achieve this, you can update the register
action, as shown in Listing 14-33, to call the addToRoles
method, passing the RoleName
as an argument.
Listing 14-33. Updating the register Action to Include Roles
def register = {
...
if(u.validate()) {
u.password = new Sha1Hash(u.password).toHex()
u.addToRoles(name:RoleName.USER)
u.save()
...
}
...
}
Now it is time to consider the AuthRealm
, which currently does not implement the hasRole
method. Listing 14-34 shows a simple implementation that inspects the roles
association of the User
domain class.
Listing 14-34. Using Criteria to Query User Roles
def hasRole(principal, roleName) {
def user = User.findByLogin(principal, [fetch:[roles:'join']])
return user.roles.any { it.name == RoleName.valueOf(roleName) }
}
Notice how in Listing 14-34 you use the principal
argument passed to the hasRole
method to look up the User
based on their unique login
name. If the User
doesn't have a Role
within its roles
association that matches the specified RoleName
, then the hasRole
method will return false
. With this in place, the controller actions secured with the accessControl
method in the AuthFilters
class will not allow users to access those controller actions unless the User
has the specified role.
In addition to securing via the AuthFilters
class, you can also secure the view layer using a variety of tags provided by the JSecurity plugin. You may recall that previously the gTunes application checked whether a User
existed within the session
object to control the state of the view. As a refresher, the code in question can be found within the grails-app/views/layouts/main.gsp
layout, as shown in Listing 14-35.
Listing 14-35. The Old Way of Securing the View
<div id="loginBox" class="loginBox">
<g:if test="${session?.user}">
<g:render template="/user/welcomeMessage"></g:render>
</g:if>
<g:else>
<g:render template="/user/loginForm"></g:render>
</g:else>
</div>
In the example in Listing 14-35, the loginBox <div>
displays different content depending on whether the user is logged in. Using JSecurity, there are two equivalents tags to achieve this: <jsec:isLoggedIn>
and <jsec:isNotLoggedIn>
. Listing 14-36 shows the code updated to use the JSecurity model.
Listing 14-36. Checking Whether a User Is Authenticated with JSecurity
<div id="loginBox" class="loginBox">
<jsec:isLoggedIn>
<g:render template="/user/welcomeMessage"></g:render>
</jsec:isLoggedIn>
<jsec:isNotLoggedIn>
<g:render template="/user/loginForm"></g:render>
</jsec:isNotLoggedIn>
</div>
Tip If you're not keen on the naming of the <jsec:isLoggedIn>
and <jsec:isNotLoggedIn>
tags, you may want to use <jsec:authenticated>
and <jsec:notAuthenticated>
, which mean the same thing.
Additionally, the grails-app/views/user/_welcomeMessage.gsp
template was particularly reliant on the existence of a user
object within the session
with the following snippet of code:
Welcome back <span id="userFirstName">${session?.user?.firstName}!</span><br><br>
If you merely want to output the currently logged in user's login name, then you could use the <jsec:principal />
tag instead:
Welcome back <span id="userFirstName"><jsec:principal />!</span><br><br>
However, in this case, you really want to print the user's first name. To facilitate this, you may want to add another filter that makes the actual User
instance available to the request
, as shown in Listing 14-37.
Listing 14-37. Making the User Object Available in the Request
userInRequest(controller:"*", action:"*") {
before = {
def subject = SecurityUtils.getSubject()
if(subject && subject?.principal) {
request.user = User.findByLogin(subject.principal)
}
}
}
As you can see from Listing 14-37, you can use the SecurityUtils
class to get a reference to the Subject and
then, using the principal
, look up the User
instance and place it within the request
. As you saw in Chapter 10, with a good caching policy in place, you can avoid hitting the database in most cases. Now within the _welcomeMessage.gsp
template you can use the user
object held in the request
to output the User
instance's firstName
property:
Welcome back <span id="userFirstName">${request?.user?.firstName}!</span><br><br>
Returning to roles, as well as the <jsec:isLoggedIn>
tag, you can also check whether a User
has a particular Role
within the view using the <jsec:hasRole>
or <jsec:hasAllRoles>
tag. Listing 14-38 shows an example of using the <jsec:hasRole>
tag.
Listing 14-38. Restricting Access Based on Role
<jsec:hasRole name="ADMINISTRATOR">
<g:link controller="blog" action="create">Create Blog Entry</g:link>
</jsec:hasRole>
<jsec:hasRole in="['ADMINISTRATOR', 'USER']">
<g:link controller="blog" action="list">Show Blog Entries</g:link>
</jsec:hasRole>
Now it is time to try something a little more fun. In the next section, you're going to implement the "My Music" section of the gTunes application that will allow you to play the music you have purchased. Of course, this has to be dealt with in a secure manner, because users should be able to play only the music they have actually purchased. Luckily, JSecurity has great support for implementing permission-based security, which will help solve this problem.
As it stands at the moment, the gTunes application is capable of allowing users to purchase albums. However, there is currently no way to play the music the User
has purchased, which is not particularly useful. To fix this problem, you're going to implement the "My Music" section, which will show the currently logged in user's collection of music and allow them to play individual songs.
To do so, first create a link to the "My Music" section by editing the grails-app/views/layouts/main.gsp
layout and modifying the navButtons <div>
, as shown in Listing 14-39.
Listing 14-39. Adding the "My Music" Link
<div id="navPane">
<div id="navButtons" style="display:${request.user? 'block' :'none'}">
<ul>
<li><g:link controller="user" action="music">My Music</g:link></li>
<li><g:link controller="store" action="shop">The Store</g:link></a></li>
</ul>
</div>
...
</div>
The added <g:link>
tag links to a new music
action of the UserController
. The music
action is responsible for building up a model representing the user's library of music. Listing 14-40 shows an example implementation.
Listing 14-40. Obtaining Information About the User's Music Collection
def music = {
def albumList = AlbumPayment.withCriteria {
projections {
property "album"
}
eq("user", request.user)
}
def artistList = albumList.collect { it.artist }.unique()
return [artists:artistList, albums:albumList ]
}
As you can see, you can obtain information about the user's purchases using the AlbumPayment
class. In Listing 14-40, a projection is used to select the album
property of each AlbumPayment
instance in the criteria query. Projections were discussed in more detail in Chapter 10. With a list of albums and artists in hand, you can then use a view to render this information appropriately. Listing 14-41 shows the grails-app/views/user/music.gsp
view that goes through each Artist
instance and displays an album art link to each album.
Listing 14-41. The music.gsp View
<g:applyLayout name="libraryLayout">
<div id="musicLibrary" class="musicLibrary">
<g:if test="${!artists}">
You haven't purchased any music just yet.
Why don't you take a <g:link controller="store"
action="shop">look at the store</g:link>
to see what's available.
</g:if>
<g:each var="artist" in="${artists}">
<div id="artist${artist.id}" class="artist">
<h2>${artist.name}</h2>
<g:each var="album"
in="${albums.findAll { it.artist.name == artist.name}}">
<span class="purchasedAlbum">
<g:remoteLink update="musicLibrary"
controller="album"
action="display"
id="${album.id}">
<music:albumArt artist="${artist.name}"
album="${album.title}"
alt="${album.title}"/>
</g:remoteLink>
</span>
</g:each>
</div>
</g:each>
</div>
</g:applyLayout>
Notice that in Listing 14-41 the music.gsp
view is using a new layout called libraryLayout
. This makes sense, since typically you don't want the same information about the store within your music library. You can see the grails-app/views/layouts/libraryLayout.gsp
file in Listing 14-42.
Listing 14-42. The libraryLayout.gsp View
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="layout" content="main">
<title>gTunes Store</title>
</head>
<body id="body">
<h1>Your Music</h1>
<div id="musicPanel">
<g:layoutBody />
</div>
</body>
</html>
Currently, the libraryLayout.gsp
view in Listing 14-42 is pretty simple, but you could easily augment it with additional functionality such as recommendations based on the user's current collection of music, and so on. All in all, after applying a few CSS tweaks, the new "My Music" section looks like Figure 14-5.
Figure 14-5. The "My Music" section of the gTunes application
Next, since this section of the gTunes application relates specifically to personal data of individual users, you need to ensure that said users are logged in before accessing the music
action of the SongController
. To do this, add a new filter definition in the AuthFilters
class that secures the music
action, as shown in Listing 14-43.
Listing 14-43. Securing the music Action
library(controller:"user", action:"music") {
before = {
accessControl()
}
}
If you refer to the code in Listing 14-41, you'll notice that the <g:remoteLink>
tag used links to the display
action of the AlbumController
. Currently, this will just render an Album
exactly as shown in the store. Figure 14-6 shows an example of the current behavior.
Figure 14-6. The current presentation of Album information
As you can see, even if you purchased the Album
, the gTunes application is still showing the price, the "Buy" button, and so on. Somehow you need to give the user permission to access this Album
. It is in use cases like this that JSecurity's permissions mechanism comes in handy. To model permissions, you're going to need to create a new com.g2one.gtunes.Permission
class using the create-domain-class
command:
$ grails create-domain-class com.g2one.gtunes.Permission
The Permission
domain class is going to implement the org.jsecurity.authz.Permission
interface, providing some default behavior. Listing 14-44 shows the code for the Permission
domain class.
Listing 14-44. The Permission Domain Class
package com.g2one.gtunes
class Permission implements org.jsecurity.authz.Permission, Serializable{
static belongsTo = [user:User]
boolean implies(org.jsecurity.authz.Permission p) { false }
}
As you can see from Listing 14-44, the Permission
domain class is also associated with an individual User
using a belongsTo
static property. To make this a bidirectional relationship, you can add a hasMany
definition on the User
side of the association, as shown in Listing 14-45.
Listing 14-45. Updating the User Class with the Permissions Association
class User implements Serializable{
...
static hasMany = [ purchasedAlbums:Album,
purchasedSongs:Song,
roles:Role,
permissions:Permission]
}
Returning to Listing 14-44, the default behavior is to return false
from the implies(Permission)
method, granting the User
no additional permissions. To provide additional behavior, you can subclass the Permission
domain class. As an example, currently you need to restrict the access users have to Album
instances they have purchased. To do this, you can implement an AlbumPermission
by extending the Permission
class. Simply run the create-domain-class
command again to create the AlbumPermission
class:
grails create-domain-class com.g2one.gtunes.AlbumPermission
With that done, you need to extend the com.g2one.gtunes.Permission
class and add the necessary behavior to restrict access to individual Album
instances. Listing 14-46 shows a sample implementation.
Listing 14-46. The AlbumPermission Class
package com.g2one.gtunes
class AlbumPermission extends Permission {
Album album
boolean implies(org.jsecurity.authz.Permission p) {
if(p instanceof AlbumPermission) {
if(album.id == p.album?.id) {
return true
}
}
return false
}
String toString() { "Album Permission: ${album}"}
}
As you can see from Listing 14-46, each AlbumPermission
is associated with an Album
instance. If the Permission
supplied to the implies(Permission)
method contains the same Album
instance, then the User
has permission to access the Album
and true
is returned; otherwise, false
is returned.
To finalize permission handling, you need to add code to the purchaseAlbum
method of the StoreService
you created in Chapter 11 to associate an AlbumPermission
with a User
when they purchase an Album
. Listing 14-47 shows how you can use the addToPermissions
method to achieve this in the StoreService
class.
Listing 14-47. Assigning an AlbumPermission to a User
class StoreService {
static transactional = true
Payment purchaseAlbums(User user, creditCard, List albumPayments) {
// Once payment taken update user profile
for(ap in albumPayments) {
...
user.addToPurchasedAlbums(ap.album)
user.addToPermissions(new AlbumPermission(album:ap.album))
}
...
}
At this point, you need to consider the AuthRealm
, which currently implements the hasRole
method, but not the isPermitted
method that is necessary for permission handling. Listing 14-48 shows an example implementation that obtains a list of Permission
instances for a User
and verifies them against the supplied Permission
instance.
Listing 14-48. Implementing isPermitted in the AuthRealm Class
def isPermitted(principal, requiredPermission) {
if(requiredPermission instanceof com.g2one.gtunes.Permission) {
def permissions = Permission.withCriteria {
user {
eq('login', principal)
}
}
return permissions.any { permission ->
permission.implies(requiredPermission)
}
}
else {
return true
}
}
Notice, in Listing 14-48, the usage of the any
method on the list of permissions
. The any
method will return true
if any of the expressions within the passed closure evaluate to true
. The result is that if any of the Permission
instances return true
from the implies(Permission)
method, then the any
method will return true
.
Now it is time to return to the grails-app/views/album/_album.gsp
template that is currently showing the price and insisting users purchase the Album
again, even if they have already purchased it. To resolve this situation, you can create a new permission
variable using the AlbumPermission
class with the <g:set>
tag:
<g:set var="permission"
value="${new com.g2one.gtunes.AlbumPermission(album:album)}" />
Then you can use the <jsec:lacksPermission>
tag to display information only if the user doesn't have permission to access the Album
. For example, the price shouldn't be shown if the user has already purchased the Album
, as shown in Listing 14-49.
Listing 14-49. Using the <jsec:lacksPermission> Tag to Restrict Access
<div class="albumInfo">
Genre: ${album.genre ?: 'Other'}<br>
Year: ${album.year}<br>
<jsec:lacksPermission permission="${permission}">
<strong>Price: $ ${album.price}</strong>
</jsec:lacksPermission>
</div>
You can also at this point supply a link that allows the user to play an individual Song
if they have purchased the Album
using a combination of the <jsec:lacksPermission>
and <jsec:hasPermission>
tags, as shown in Listing 14-50.
Listing 14-50. Using the <jsec:hasPermission> Tag to Allow Access
<g:each in="${album.songs}" var="song">
<li>
<jsec:lacksPermission permission="${permission}">
${song.title}
</jsec:lacksPermission>
<jsec:hasPermission permission="${permission}">
<g:link controller="song" action="play"
id="${song.id}">${song.title}</g:link>
</jsec:hasPermission>
</li>
</g:each>
As you can see from Listing 14-50, the code links to the play
action of the SongController
. We'll talk about implementing this action in a moment; for now, the last step in updating the _album.gsp
template is to disable the "Buy" button if the user has already purchased the Album
, as shown in Listing 14-51.
Listing 14-51. Disabling the "Buy" Button
<div id="buttons" style="float:right;">
<jsec:hasPermission permission="${permission}">
<g:link controller="user" action="music">Back to My Music</g:link>
</jsec:hasPermission>
<jsec:lacksPermission permission="${permission}">
<g:link controller="store" action="buy" id="${album.id}">
<img src="${createLinkTo(dir:'images',file:'buy-button.gif')}"
border="0">
</g:link>
</jsec:lacksPermission>
</div>
You'll notice from the GSP code in Listing 14-51 that if the User
does have access to the Album
, then a "Back to My Music" link is displayed instead, allowing the user to navigate easily back to their music. Figure 14-7 shows the updated interface in place with the AlbumPermission
class having the desired effect.
Figure 14-7. The updated _album.gsp template with permissions working
At this point, you need to consider how to enable users to play music they have purchased. You could leverage various technologies that allow the streaming of media. From Windows Media Player to Flash, each has its own advantages and disadvantages. For the gTunes application, the powers that be have decided on QuickTime (http://www.apple.com/quicktime/) as the preferred technology, since it works well on most mainstream platforms (even on Unix-flavors via WINE; see http://appdb.winehq.org/appview.php?appId=1029) and is simple to use.
To allow embedding of QuickTime audio easily, it would be good to wrap the functionality of QuickTime into a tag library. To do so, run the grails create-tag-lib
command as follows:
$ grails create-tag-lib com.g2one.gtunes.Streaming
This will create a new tag library called StreamingTagLib.groovy
at the location grails-app/com/g2one/gtunes
, as shown in Figure 14-8.
Figure 14-8. The StreamingTagLib.groovy file
We chose a generic name on purpose, just in case the requirement to add support for other media players, such as Flash, arises. Nevertheless, Listing 14-52 shows the code that embeds a QuickTime movie using the StreamingTagLib
.
Listing 14-52. The StreamingTagLib Implementation
package com.g2one.gtunes
class StreamingTagLib {
static namespace = "media"
def player = { attrs, body ->
def userAgent = request.getHeader('User-Agent')
def src = attrs.src
def width = attrs.width ?: 100
def height = attrs.height ?: 100
def autoplay = attrs.autoplay ?: false
out.write """
<OBJECT CLASSID="clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B"
WIDTH="${width}"
HEIGHT="${height}"
CODEBASE="http://www.apple.com/qtactivex/qtplugin.cab">
<PARAM name="SRC" VALUE="${src}">
<PARAM name="AUTOPLAY" VALUE="${autoplay}">
<EMBED SRC="${src}"
WIDTH="${width}"
HEIGHT="${height}"
AUTOPLAY="${autoplay}"
CONTROLLER="true"
LOOP="false"
PLUGINSPAGE="http://www.apple.com/quicktime/download/">
</EMBED>
</OBJECT>"""
}
}
There are a couple of interesting things to note about the code in Listing 14-52. First, as you can see, the media
namespace is used for this tag library, making the name of the tag <media:player>
.
Second, notice the usage of Groovy multiline String
s to easily write out a bunch of markup. If you wanted, you could refactor this out to a separate GSP template, but for now this simple solution will do. Now let's take advantage of the <media:player>
tag by implementing the play
action of the SongController
.
Of course, you don't want users who don't have permission to be able to play a Song
from an Album
they have purchased. Luckily, using the ability to call tags as methods in Grails, you can use the same <jsec:hasPermission>
and <jsec:lacksPermission>
tags in a controller. Listing 14-53 shows this in action.
Listing 14-53. The play Action of the SongController
def play = {
def song = Song.get(params.id)
if(song) {
def albumPermission = new AlbumPermission(album:song.album)
jsec.hasPermission(permission:albumPermission) {
render(view:"play", model:[song:song])
}
jsec.lacksPermission(permission:albumPermission) {
response.sendError 401
}
}
else {
response.sendError 404
}
}
As demonstrated by Listing 14-53, you can construct an instance of the AlbumPermission
class and then use it as an argument to the jsec.hasPermission
method. The closure, which is equivalent to the tag body in GSP, will be invoked only if the user has permission. In the case where the User
lacks permission, an HTTP 401 error is sent back signaling that the User
is forbidden from accessing this resource.
Otherwise, if all is well, a new view called grails-app/views/song/play.gsp
is rendered. Listing 14-54 shows the GSP markup for the play.gsp
view, which takes advantage of the <media:player>
tag you developed earlier.
Listing 14-54. The play.gsp View
<g:applyLayout name="libraryLayout">
<div id="musicLibrary" class="musicLibrary">
<div class="songPlayer">
<h2>${song.artist.name} - ${song.title}</h2>
<div class="albumArt">
<music:albumArt artist="${song.artist.name}"
album="${song.album.title}" />
</div>
<div class="player">
<media:player src="${createLink(controller:'song',
action:'stream',
id:song.id)}"
autoplay="true"
height="20"
width="200" />
</div>
<div class="links" style="float:right;">
<g:remoteLink controller="album"
action="display"
id="${song.album.id}"
update="musicLibrary">
Back to Album
</g:remoteLink><br>
<g:link controller="user"
action="music">
Back to My Music<
</g:link><br>
</div>
</div>
</div>
</g:applyLayout>
You'll notice from the code in Listing 14-54 that the src
attribute of the <media:player>
tag is another action called stream
. The stream
action is responsible for sending back the music file. Of course, at the moment, there isn't any music! To rectify that, add a new file
property to the Song
domain class, as shown in Listing 14-55.
Listing 14-55. Adding a file Property to the Song Class
class Song implements Serializable {
String file
...
static constraints = {
...
file validator:{ val ->
if(!new File(val).exists())
return "song.does.not.exist"
}
}
}
As you can see, the file
property uses a custom validator that ensures you can't add a Song
that doesn't exist on the file system. Now all you need to do is stream the data from the file back to the User
. Listing 14-56 shows an example implementation that uses Java I/O techniques.
Listing 14-56. Implementing the stream Action
1 static final BUFFER_SIZE = 2048
2 def stream = {
3 def song = Song.get(params.id)
4 if(song) {
5 def albumPermission = new AlbumPermission(album:song.album)
6 jsec.hasPermission(permission:albumPermission) {
7 try {
8 def file = new File(song.file)
9 def type = file.name[-3..-1]
10 response.contentType = "audio/x-${type}"
11 def out = response.outputStream
12 def bytes = new byte[BUFFER_SIZE]
13 file.withInputStream { inp ->
14 while( inp.read(bytes) != −1) {
15 out.write(bytes)
16 out.flush()
17 }
18 }
19 }
20 catch(Exception e) {
21 log.error "Error streaming song $file: $e.message", e
22 response.sendError 500
23 }
24
25 }
26 jsec.lacksPermission(permission:albumPermission) {
27 response.sendError 401
28 }
29 }
30 else {
31 response.sendError 404
32 }
33 }
Notice that in Listing 14-56, the code once again secures access to the Song
using the AlbumPermission
you created earlier and the <jsec:hasPermission>
and <jsec:lacksPermission>
tags. If the User
does have permission, then a new java.io.File
is created, and the response contentType
is set based on the file extension on lines 8 to 10:
8 def file = new File(song.file)
9 def type = file.name[-3..-1]
10 response.contentType = "audio/x-${type}"
Note The technique of using the file extension to produce the MIME type for the contentType
works for MP3 and M4A formats but may be a little naive if the application later needs to support other formats like WMA and so on.
With that done, the next step is to obtain the java.io.OutputStream
to write to and from the response
and create a buffer to read bytes from the file with the following:
11 def out = response.outputStream
12 def bytes = new byte[BUFFER_SIZE]
A trivial way to read the bytes of the File
would be to call the readBytes()
method. However, this reads the entire contents of the File
into memory and, since audio files are quite large, may not scale too well.
Note Speaking of scaling, a better solution may be to use something like Amazon's Simple Storage Service (S3) to serve the files from the cloud instead. You can find an Amazon S3 plugin for Grails that can help simplify this task at http://grails.org/Amazon+S3+Plugin.
Instead, the code in Listing 14-56 uses a 2KB buffer to read and stream parts of the file back to the User
on lines 13 to 18:
13 file.withInputStream { inp ->
14 while( inp.read(bytes) != −1) {
15 out.write(bytes)
16 out.flush()
17 }
18 }
And with that, you've completed the "My Music" section of the gTunes application and allowed users to securely stream the music they have purchased! Figure 14-9 shows the interface that allows users to play their music.
Figure 14-9. Streaming music with QuickTime
In the next section, you'll learn how having a better understanding of your URL mappings will enable you to keep an eye on how users can access your application.
A good technique to adopt when considering securing your application is to have greater control over the way URLs map onto controllers. The default URL mapping scheme that Grails uses is dynamic in that the parameters in the URI dictate what action is executed (see Listing 14-57).
Listing 14-57. The Default URL Mapping Scheme
"/$controller/$action?/$id?"()
It is easy with a URL mapping like the one in Listing 14-57 to accidentally expose an action that should be secured. If security is of a high priority, we recommend you take control of your URL mappings and create mapping rules for each URL that is exposed. Listing 14-58 shows an example grails-app/conf/UrlMappings.groovy
file for the gTunes application that provides mappings for each exposed controller.
Listing 14-58. Fine-Grained URL Mapping Configuration
// User access
"/your/music"(controller:"user", action:"music")
"/login"(controller:"user",action:"login")
"/logout"(controller:"user", action:"logout")
"/register"(controller:"user", action:"register")
"/stream/$id"(controller:"song", action:"stream")
"/play/$id"(controller:"song", action:"play")
"/buy/$id"(controller:"store", action:"buy")
// Anonymous browsing
"/"(controller:"store")
"/album/$id"(controller:"album", action:"display")
"/song/$id"(controller:"song", action:"display")
"/artist/$id"(controller:"artist", action:"display")
"/store"(controller:"store", action:"shop")
"/search"(controller:"store", action:"search")
"/genre/$name"(controller:"store", action:"genre")
"/blog"(controller:"blog", action:"list")
Another advantage of this approach is that you can then configure a dynamic URL mapping purely for administrator access, as shown in Listing 14-59.
Listing 14-59. Administrator URL Mappings
// Administrator access
"/admin/$controller/$action?/$id?"()
As you can see from Listing 14-59, all URIs that start with /admin
can now be used for administrator access. If you then secure this URI within the AuthFilters
class, as shown in Listing 14-60, you have created an area of the site that is accessible only to administrators.
Listing 14-60. Securing the /admin URI
admin(uri:'/admin/*') {
before = {
accessControl {
role("ADMINISTRATOR")
}
}
}
If you want to add some quick administrative features, then you could take advantage of dynamic scaffolding, a topic covered in Chapter 2. As an example, try adding the following line to the AlbumController
class:
def scaffold = Album
Now if you go to the URL http://localhost:8080/gTunes/admin/album/create, you can create new Album
instances using the CRUD interface provided. You can also go to the URL http://localhost:8080/gTunes/admin/album/list to get a list of existing Album
instances in case you need to modify any of them. Thanks to Grails' scaffolding feature, you have managed to add a basic admin facility to the gTunes application that is secured with JSecurity in only a few lines of code!
In this chapter, you explored the importance of security when developing a Grails application. From preventing malicious users from penetrating your application to cross-site scripting and DoS attacks to implementing authentication and authorization using a framework such as JSecurity, we covered a lot of ground. Security is, however, a domain-specific topic, and a variety of options are available to you when developing a Grails application. If JSecurity doesn't fulfill your requirements, then you have the Acegi plugin or the Authentication plugin to try. Alternatively, you could continue the "home-grown" security model. It is really up to you.
In the next chapter, we'll cover how to implement web services in Grails. Using key technologies such as SOAP, REST, and RSS/Atom, there is a lot to cover in this particularly interesting area of web development. Don't go away!