Using Authorization Tags and Annotations - Securing Your Application with Spring Security - PROFESSIONAL JAVA FOR WEB APPLICATIONS (2014)

PROFESSIONAL JAVA FOR WEB APPLICATIONS (2014)

Part IV Securing Your Application with Spring Security

Chapter 27 Using Authorization Tags and Annotations

IN THIS CHAPTER

· Checking authorization rules in code

· Declaring URL and method security

· Using common and Spring Security annotations

· Understanding authorization decisions

· Creating access control lists for object security

· Using Spring Security’s tag library

WROX.COM CODE DOWNLOADS FOR THIS CHAPTER

You can find the wrox.com code downloads for this chapter at http://www.wrox.com/go/projavaforwebapps on the Download Code tab. The code for this chapter is included in the following example:

· Customer-Support-v20 Project

NEW MAVEN DEPENDENCIES FOR THIS CHAPTER

In addition to the Maven dependencies introduced in previous chapters, you also need the following Maven dependency:

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-taglibs</artifactId>

<version>3.2.0.RELEASE</version>

<scope>runtime</scope>

</dependency>

AUTHORIZING BY DECLARATION

In Chapter 25, you explored some of the different approaches you can take to authorization. There are a lot of techniques and technologies you can use, and it would not be correct to say that any one approach is better than the others. A lot depends on your individual needs, the architecture of your application, and the approach you take to authentication. The instinctual approach might be to simply place authorization code within your code. A very common technique, and one often considered a best practice, is to authorize by declaration. In this technique, your code or its configuration declares rules that determine who can do what, and a security mechanism intercepts access to your code to enforce those rules on your behalf. This pattern is similar to the transactional pattern you explored in Part III of this book. In this section, you explore various ways to implement both approaches, and learn about the conventions and tools available in Spring Framework.

Checking Permissions in Method Code

Normally, your code would need to check that the user is authorized to perform an action using code like this:

public void doSomeAction(...)

{

if(security.userCanPerformAction("ACTION_1"))

{

// code that performs the action

}

else

throw new AccessDeniedException("Not authorized to perform action.");

}

However, this can quickly litter your services with duplicate code. Consider a service that manages forum postings and replies:

public Post getPost(long id)

{

if(security.userCanPerformAction("READ_POST")) {

// code that returns post

} else

throw new AccessDeniedException("Not authorized to perform action.");

}

public Page<Post> listPosts(long forumId, Pageable pageable)

{

if(security.userCanPerformAction("LIST_POSTS")) {

// code that lists posts

} else

throw new AccessDeniedException("Not authorized to perform action.");

}

public void savePost(Post post)

{

if(security.userCanPerformAction("SAVE_POST")) {

// code that saves post

} else

throw new AccessDeniedException("Not authorized to perform action.");

}

public void deletePost(Post post)

{

if(security.userCanPerformAction("DELETE_POST")) {

// code that deletes post

} else

throw new AccessDeniedException("Not authorized to perform action.");

}

public Reply getReply(long id)

{

if(security.userCanPerformAction("READ_REPLY")) {

// code that returns reply

} else

throw new AccessDeniedException("Not authorized to perform action.");

}

public Page<Reply> listReplies(long postId, Pageable pageable)

{

if(security.userCanPerformAction("READ_REPLIES")) {

// code that lists replies

} else

throw new AccessDeniedException("Not authorized to perform action.");

}

public void saveReply(Reply reply)

{

if(security.userCanPerformAction("SAVE_REPLY")) {

// code that saves reply

} else

throw new AccessDeniedException("Not authorized to perform action.");

}

public void deleteReply(Reply reply)

{

if(security.userCanPerformAction("DELETE_REPLY")) {

// code that deletes reply

} else

throw new AccessDeniedException("Not authorized to perform action.");

}

Of course, this code is simplistic. It ignores the fact that users might be permitted to view only certain posts and replies, and it doesn’t account for the difference between moderators deleting other users’ posts and users deleting their own posts. Even so, at least 24 lines of code are dedicated just to checking authorization rules in this one service, and the code is quite repetitive. You could remove some duplication using callbacks and lambda expressions, like so:

public Post getPost(long id)

{

return security.doSecured("READ_POST", () -> {

// code that returns post

});

}

...

Although this is certainly an improvement, you still end up duplicating some code. It also isn’t the cleanest approach because all your service code ends up being placed within callback lambdas. In addition, think about how you would unit test any of the previous code examples. It wouldn’t be easy! Each test would have to set up an Authentication with the correct GrantedAuthoritys to check both the positive case (code executes because the user is authorized) and the negative case (code does not execute because the user is not authorized). You would have to do this for all your code, re-testing authorization checks wherever you go. Should you really be coupling this code?

Generally speaking, you don’t want to take any of these approaches. In fact, Spring Security has only limited support for this first technique and no support for the second. (However, you can implement your own service to support the second approach.) Spring Security’s filter chain wraps all HttpServletRequest objects and implements the getUserPrincipal, getRemoteUser, and isUserInRole methods, which return the Authentication object, authenticated username, and authorization check results, respectively. Within a Spring Web MVC controller, you can ask for a Principal as a handler method parameter, cast it to an Authentication, and check its GrantedAuthoritys to determine access. However, this is a bit clunky. You can improve this technique slightly by implementing aHandlerMethodArgumentResolver that provides Authentication or List<GrantedAuthority> handler method parameters, but this still has its problems.

As of Spring Security 3.2, another approach involves annotating a controller method parameter with @org.springframework.security.web.bind.annotation.AuthenticationPrincipal. As long as you use the @EnableWebMvcSecurity configuration annotation, Spring Security registers a HandlerMethodArgumentResolver that can provide values for @AuthenticationPrincipal arguments. An @AuthenticationPrincipal argument can be of any type — the key is that its type must match the type returned by the getPrincipal method of the Authenticationyour application uses. In most cases this will be a UserDetails implementation, as in the following example:

@RequestMapping(value = "addMessage", method = RequestMethod.POST)

public View addMessage(MessageForm form,

@AuthenticationPrincipal MyUserDetails user)

{

...

}

All these techniques work for controllers; however, if you intend to enforce authorization in your services (as you should), you have to pass this information as arguments to your services, which is not ideal. Another option is to use the SecurityContextHolder to obtain the current Authentication and check its GrantedAuthoritys. This is better suited for enforcing authorization in your services, but it still involves a lot of code in each method and makes unit testing difficult. If you want to take this approach, you should create some helper classes that do most of the repetitive work.

So what should you do? Why does Spring Security not provide better tools for this? Spring Security emphasizes a declarative approach to authorization, so much so that it provides no helpers for programmatic checking. Declarative authorization decouples your business logic from your authorization logic, which in the end is going to save you a lot of trouble. You can implement declarative authorization a few different ways. The rest of this section provides an overview of these methods and how they work in Spring Security.

Employing URL Security

You already explored one approach to declarative security in Chapter 26: URL security. This involves declaring URL patterns and defining rules for who can access those URL patterns. You have already seen the permitAll, authenticated, and hasAuthority rules. Although you can avoid using URL security, it does make some things very simple. In the Customer Support application, for example, you used URL security to declare that all URLs require a user to be logged in, but excluded the login and logout URLs from this rule. permitAll, authenticated, and hasAuthority are authorization expression functions that largely mean exactly what they say. There are several expression functions that you can choose from:

· denyAll prevents everyone from accessing the given resource. (It always evaluates to false.) This is handy if you need to temporarily block access to a particular URL, but it has little long-term value.

· permitAll is the opposite of denyAll. It enables anyone to access the resource, whether they have been authenticated. (It always evaluates to true.)

· hasAuthority(String) evaluates to true if the user has the specified GrantedAuthority passed as an argument to the function.

· hasAnyAuthority(String...) enables you to specify multiple GrantedAuthoritys and evaluates to true if the user has at least one of these authorities.

· hasRole(String) is a synonym for hasAuthority and does the same thing. Which you use is just a matter of preference. If you want to avoid the appearance of role-based authorization, you’ll probably choose hasAuthority.

· hasAnyRole(String...) is a synonym for hasAnyAuthority.

· hasPermission is a special expression function that checks a user’s permission to access a particular resource based on more than just that user’s roles or authorities. For example, it can check that a user can access a specific forum, which may differ from one forum to the next. Using hasPermission is a complex subject that’s detailed in the section on access control lists.

· isAnonymous evaluates to true if the user is anonymously authenticated (in other words, they are not “logged in”).

· isRememberMe evaluates to true if the user is authenticated using remember-me authentication.

· isAuthenticated is the opposite of isAnonymous — it evaluates to true if the user is not anonymous (in other words, they are “logged in”).

· isFullyAuthenticated evaluates to true if isAuthenticated is true and isRememberMe is false. You can use this to mitigate some of the dangers of remember-me authentication. You can permit limited access to nonsensitive resources whenever isAuthenticated but protect more sensitive resources by allowing access only if isFullyAuthenticated.

· hasIpAddress(String) evaluates to true if the user’s request originates from the given IP address. You can also specify a Classless Inter-Domain Routing (CIDR) block (such as 65.128.76.0/24), and hasIpAddress evaluates to true if the user’s IP address belongs to that block of IP addresses.

· getAuthentication returns the Authentication object, which you can use to perform more complex or specialized comparisons that evaluate to a Boolean value.

Spring Framework Expression Language (Spring EL or SpEL) is the force that powers use of these authorization functions. This book has not covered SpEL, but it’s really quite simple. It’s very similar to the Java Unified Expression Language, and you can learn much of what you need to learn about it using Spring Security expressions. If you want more information, however, you can find it in the Spring Framework Reference Documentation.

When using XML configuration, you define URL access rules within the <intercept-url> inner element of <http>. By default, the access attribute acts like a hasAnyRole or hasAnyAuthorities. It simply accepts a comma-separated list of GrantedAuthoritys, and the user must have one of them to access matching URLs. Note that the order these <intercept-url> tags appear is important. Spring Security evaluates the first rule that a URL matches, so if you reverse the order of these two rules, anyone could access the /admin/ URLs.

<http>

<intercept-url pattern="/admin/**" access="ADMINISTRATOR" />

<intercept-url pattern="/**" access="USER" />

<form-login ...>

<logout ...>

</http>

To use the authorization expression functions within the access attribute, you must enable expressions within the <http> element. Enabling expressions is as simple as setting the use-expressions attribute to true. After you do this, you can no longer provide a simple comma-separated list of GrantedAuthoritys to the access attribute. You must use expressions. You use one or more of the security functions combined with the Boolean operators or and and, and parentheses if necessary, to form an entire security expression. With this syntax you can define very simple and very complex rules for protecting your application’s web resources. For example, to specify that accessing the administration panel requires being fully authenticated (not remember-me), having administrator privileges, and connecting from a range of internal IP addresses, you could configure it like this:

<http use-expressions="true">

<intercept-url pattern="/admin/**" access="isFullyAuthenticated

and hasAuthority('ADMINISTRATOR') and hasIpAddress('192.168.0.0/24')" />

<intercept-url pattern="/**" access="hasAuthority('USER')" />

<form-login ...>

<logout ...>

</http>

In this case, if the user is authenticated using remember-me authentication, he is asked to fully authenticate (“log in”) before accessing the administration panel. If the user does not have the ADMINISTRATOR GrantedAuthority or is connecting from a prohibited IP address, he receives a 403 Forbidden HTTP error. Of course, you could use or instead of and, but that wouldn’t make sense. Then only one of the three conditions would have to be met to permit access to the administration panel.

Java configuration is a little different in that it supports only security expressions. This is natural considering how different Java configuration is from XML configuration already, and how the defaults of many settings differ between these types. When you select a URL pattern in Java configuration (either with anyRequest, antMatchers, or regexMatchers), the object returned is an org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer.AuthorizedUrl. With this object you can either call the access method to create a complex SpEL expression identical to what you’d create in an XML configuration, or you can use one of the convenience methods named after the security functions to create simple expressions. The following Java configuration is identical to the previous XML configuration. (Note, again, that the order of the URL matchers is important.)

@Override

protected void configure(HttpSecurity security) throws Exception

{

security

.authorizeRequests()

.antMatchers("/admin/**").access("isFullyAuthenticated and " +

"hasAuthority('ADMINISTRATOR') and " +

"hasIpAddress('192.168.0.0/24')")

.antMatchers("/**").hasAuthority("USER")

.and().formLogin()

...

}

WARNING Although the hasRole and hasAnyRole security functions are identical to the hasAuthority and hasAnyAuthority security functions, this is not the case for the Java configuration convenience methods. The hasRole and hasAnyRole convenience methods prepend the authorities passed in to them with ROLE_. This is fine if the authorities you use start with ROLE_. In this case, just leave off ROLE_ and it is added for you. However, if your authorities do not start with ROLE_, you need to stick withhasAuthority and hasAnyAuthority.

Using declarative URL security certainly has its advantages. It’s easy to configure and apply to broad sets of URLs, the expressions are very flexible, and if you use an XML configuration, you can alter the authorization rules without recompiling the application. It’s definitely an improvement over evaluating security restrictions in all your application methods. However, two glaring disadvantages often discourage use of this feature:

· It ties authorization to the web tier — If another user interface is created that performs the same task, forgetting to define duplicate security rules for that user interface compromises the security of that feature.

· It makes refactoring more risky — If your user interface changes and your authorization rules don’t, your security could again be compromised.

And, of course, this completely ignores the possibility that you might not even have a web tier to begin with! The previous code that evaluated authorization rules within your methods had one thing going for it — the rules were applied within your services. This meant that the rules were always applied uniformly across all user interfaces. You’ll find that it makes the most sense to apply a few simple rules at the UI for convenience while applying more precise and complex rules within the services themselves.

Using Annotations to Declare Permissions

So how do you evaluate authorization rules within your services without actually writing authorization code in your services? You approached a similar need for database transactions in Part III using annotations — by annotating your service methods with@Transactional, the Spring Framework transaction proxy ensured that a transaction started before your methods executed and that it committed or rolled back after your methods completed. You can apply the same pattern to add security to your service methods — annotate them with one or more security-related annotations to declare the GrantedAuthoritys the user must have or permission expressions that must evaluate successfully before the user is permitted to invoke the requested action.

Common Annotations for Authorization

The Common Annotations API, part of Java EE, specifies several authorization annotations that you can use for just this purpose. They are all found in the javax.annotation.security package.

· @DeclareRoles, which isn’t any use in a Spring Security environment, provides a way to list all of the roles (or permissions, or actions, and so on) your application uses.

· @DenyAll prohibits access to everyone. Of course, this annotation has limited use as well. Annotating a method with this means that external actors (another class) can never execute it. An object can normally execute its own @DenyAll methods because proxies aren’t applied in this situation. However, if you use bytecode weaving instead of interface or CGLIB proxies (which you learned about in Chapter 24), even that won’t work. As such, few situations require you to use this annotation. If you mark an entire class or interface with this annotation, it prevents execution of all that class’s methods.

· @PermitAll is, naturally, the opposite of @DenyAll. It declares that everyone may execute this method. Although this may seem equally useless, it can actually be quite useful. To understand why, you must first understand the next annotation.

· @RolesAllowed can be deceiving because its name suggests it works only for role-based authorization. Remember what you learned in Chapter 25: It’s not what you call it; it’s how you use it. @RolesAllowed specifies one or more roles (or permissions orGrantedAuthoritys) that a user must have to execute the method. The user is not required to have all these roles, just one of them. If specified on a class or interface, this annotation applies to all the methods in that class. If specified on both a class and a method, the restrictions for the method typically add to the restrictions for the class. However, if the method annotation specifies roles also specified in the class annotation, the method restrictions completely override the class restrictions. Furthermore, if a class is marked with @RolesAllowed and one of its methods is marked @PermitAll, anyone may execute that method.

· @RunAs instructs the container to run the method as a different user. However, this annotation is only useful in a full Java EE container when you use container-supplied authorization. Spring Security does not provide support for this annotation.

Using the @RolesAllowed annotation is straightforward. Just add it to any method or class (of a Spring-managed bean) and specify one or more permissions in the annotation value.

@RolesAllowed("ADMINISTRATOR")

public interface SettingsMutatorService

{

@RolesAllowed({"ADD_GENERAL_SETTING", "ALTER_GENERAL_SETTING"})

void saveGeneralSettings(Map<String, Object> settings);

@RolesAllowed({"ALTER_MEMBERSHIP_SETTING"})

void saveMembershipSettings(Map<String, Object> settings);

...

}

If a user attempts to invoke a method without sufficient permissions, an org.springframework.security.access.AccessDeniedException is thrown. You don’t normally need to worry about catching this exception. As a RuntimeException, it causes any related transaction to roll back and propagate without any efforts on your part. Spring Security’s filter chain then catches the exception and returns a 403 Forbidden error to the user. In a few scenarios, you might need to catch this exception, but usually you need to do so only for non-web applications.

As you can see, the @RolesAllowed and @PermitAll annotations can be useful, but that’s about it. There’s little point in using the other three annotations when you use Spring Security. So is that all there is? These annotations are useful, but they are completely ignorant of what the user is requesting a method to do. You can’t use @RolesAllowed to restrict access to some forum posts but not all forum posts if they all come through the same method. Fortunately, Spring Security provides a more robust set of annotations to meet your needs.

The Secured Annotation

The @org.springframework.security.access.annotation.Secured annotation was Spring Security’s first pass at declarative security annotations. It came before Java EE 5 was released and thus before the Common Annotations API was available. Practically speaking,@Secured is identical to @RolesAllowed. It accepts one or more GrantedAuthoritys, and a user may execute an annotated method only if that user has at least one of those GrantedAuthoritys.

There is one minor difference, however. @RolesAllowed works with any values, but @Secured requires the values you specify to start with ROLE_. Any GrantedAuthoritys you specify that do not start with ROLE_ are ignored. This is due to the varying members in the access decision voter pattern, which you learn more about in the next section. If you do not want all your permission names to start with ROLE_, you should avoid @Secured and use @RolesAllowed instead. As with @RolesAllowed, you can specify @Secured on a class or interface to affect all the methods in that class or interface, or you can specify it on individual methods, or both. The same precedence and overriding rules apply in the third case.

@Secured("ADMINISTRATOR")

public interface SettingsMutatorService

{

@Secured({"ADD_GENERAL_SETTING", "ALTER_GENERAL_SETTING"})

void saveGeneralSettings(Map<String, Object> settings);

@Secured({"ALTER_MEMBERSHIP_SETTING"})

void saveMembershipSettings(Map<String, Object> settings);

...

}

Pre- and Post- Annotations for Authorization

Spring Security’s pre- and post-execution authorization annotations are the core of Spring Security’s modern declarative authorization power. This group of four annotations, all in the org.springframework.security.access.prepost package, enables you to use the expressions you read about previously either before or after (or before and after) a method executes. As with @RolesAllowed and @Secured, you can specify these annotations on the class or interface, on individual methods, or both. If you specify the same pre- or post- annotation on a method and its class, the method annotation completely overrides the class annotation.

@PreAuthorize is the annotation you’ll likely use the most of any annotations Spring Security supports. A security expression you specify in @PreAuthorize is evaluated before the method executes. The expression may reference any of the method arguments using the parameter name preceded by a #. For example, if the method has a parameter named employee, you could access the value of this argument using #employee in the expression. The exposed SpEL variables have the same types as the parameters they correspond to, and you can call methods on and access properties of the arguments.

@PreAuthorize("isFullyAuthenticated and hasAuthority('USER')")

public interface UserService

{

@PreAuthorize("#userId == authentication.principal.userId or " +

"hasAuthority('CHANGE_OTHER_USER_PASSWORD')")

void changePassword(long userId, String oldPassword, String newPassword);

@PreAuthorize("#user.userId == authentication.principal.userId or " +

"hasAuthority('CHANGE_OTHER_USER_DETAILS')");

void updateDetails(User user);

@PreAuthorize("#user.userId == 0 and ( isAnonymous or " +

"hasAuthority('CREATE_NEW_USER') )")

void addUser(User user);

...

}

If the expression evaluates to true, method execution continues. If the expression evaluates to false, the method is not invoked and an AccessDeniedException is thrown. Here, you have to be careful with the resolution of parameter names. Prior to Java 8, SpEL could not discover parameter names unless your classes were compiled with debug information. This meant putting classes compiled with debug symbols into production, which some developers prefer to avoid. In addition to this, interfaces don’t contain debug information, so Spring Security cannot discover the parameter names of interface methods using debug symbols.

With Java 8’s new parameter name reflection feature and as of Spring Security 3.2, parameter name discovery is more reliable and is also available for interfaces. You must compile your code with the –parameters compiler option to enable parameter name reflection. However, there are still circumstances under which it cannot work, such as if bean proxies interfere with the process. You can use parameter names if you carefully control your environment, but a better option (also new to Spring Security 3.2) is to annotate your method parameters to specify their names within Spring Security expressions. You can do this using @org.springframework.security.access.method.P or Spring Data’s @Param (if you are using Spring Data).

@PreAuthorize("#u.userId == 0 and ( isAnonymous or " +

"hasAuthority('CREATE_NEW_USER') )")

void addUser(@P("u") User user);

Spring Security 3.2’s default configuration determines expression parameter variable names using the following checks, in order of precedence. You learn how to customize this later in the section.

1. If the parameter is annotated with @P, the name specified in that annotation is used. This is true even if @Param is also present.

2. If @Param is on the classpath and the parameter is annotated with @Param but not @P, the name specified in @Param is used.

3. If Java 8 parameter name reflection information is available, the name of the method parameter is used.

4. If debug symbols are available, the name of the method parameter is used.

WARNING When you enable pre- and post- annotations for authorization, Spring Security applies an implicit @PreAuthorize("permitAll") restriction to all applicable methods that do not explicitly specify @PreAuthorize. This means that any user can invoke methods not annotated with @PreAuthorize. To be clear, this isn’t really any different from the behavior of @Secured and @RolesAllowed — in the absence of these annotations, access to a method is unrestricted. (Of course, URL security is still applied regardless of the status of your authorization annotations.)

@PostAuthorize is essentially the same as @PreAuthorize, except that it evaluates after a method completes execution. The expression in this annotation cannot access the method arguments, but it can access the value the method returned using the EL variablereturnObject, which has the same type as the method return type.

@PostAuthorize("returnObject.userId == authentication.principal.userId");

User getUser(long id);

Very few scenarios require you to use this annotation (you can replace this simple example with @PreAuthorize) because the method executes even if the user doesn’t have permission. It can result in a rolled back transaction (assuming you order your proxies correctly), and it can prevent the user from obtaining the return value of the method execution, but if it’s important to you that the method invocation has no side effects without permission, you should use @PreAuthorize instead.

@PostFilter is a particularly powerful annotation that allows you to filter the value returned from the method. You can use this annotation only on methods that return a Collection or array type. An EL variable named filterObject is available to the SpEL expression specified in @PostFilter. This variable has the type of the elements stored in the Collection. The expression is evaluated once for every value in the Collection or array, and values for which the expression evaluates to false are removed from the Collection or array before continuing. If the returned value is not a Collection or array, it results in an IllegalArgumentException. You can use this annotation for checks as simple as ensuring that the logged in user sees only objects that they “own” or “manage” (for example, the objects’ theoretical userId property values match the principal’s userId property). Or you can perform more advanced filtering using access control lists, which you learn about in the “Creating Access Control Lists for Object Security” section.

@PostAuthorize("returnObject.userId == authentication.principal.userId or " +

"hasPermission(returnObject, 'read') or " +

"hasPermission(returnObject, 'admin')")

User getUser(long id);

@PostFilter("hasPermission(filterObject, 'read') or " +

"hasPermission(filterObject, 'admin')")

List<User> getManagedUsers();

@PreFilter is similar to @PostFilter, except that it acts on a method argument instead of a method’s return value. The method parameter must be a Collection of elements (arrays are not supported for this annotation), and a filterObject variable will be exposed to the SpEL expression with the same type as the elements in the Collection, just like with @PostFilter. If the method has only one parameter that is a Collection, Spring Security can detect it automatically. If you have multiple Collection parameters, you must specify the name of the parameter you want to filter using the annotation’s filterTarget attribute. That parameter name is matched to the method parameter using the process previously described for @PreAuthorize. There are very few uses for this annotation, and you will likely find that you never use it.

WARNING Although filtering method arguments and return values is useful, it cannot completely replace in-method security checks. More important, this technique is incompatible with pagination; it works only for methods that normally return an unpaged collection of a particular object. If you try to combine it with pagination, pages would possibly not have the correct number of results, and also may not have the same number of results from page to page. Also, filtering after the fact can often be a detriment to performance. To restrain lists based on user permissions, you still usually want to alter the logic that retrieves the data (for example, changes the SQL query to exclude results the user can’t see). As such, you can never completely eliminate authorization code within your services. You can, however, almost completely replace it with declarative authorization.

As you can see, Spring Security’s pre- and post-execution authorization annotations are significantly more powerful than the Common Annotations API or @Secured. There is very little you can’t do with these annotations given SpEL as the expression engine. However, you may have been confused by the sample use of the hasPermission function that you still haven’t learned about. hasPermission makes use of Spring Security’s access control list, which you learn about in the “Creating Access Control Lists for Object Security” section.

NOTE If you don’t want to use SpEL expressions and prefer @Secured or @RolesAllowed, but you still want the advantage of the isAnonymous, isRememberMe, and isFullyAuthenticated security functions, just use the special-case permissions/rolesIS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_REMEMBERED, and IS_AUTHENTICATED_FULLY. They have the same effect as these functions.

Understanding the Benefits of Using Annotations

After seeing how you can essentially write code within some of these annotations, you might wonder how this is an improvement over writing the code in the service method. Didn’t you read earlier that separating the authorization logic from the business logic was a key goal? Doesn’t this just relocate the problem? Remember what you read earlier about unit testing your services: It becomes much harder to do this when you must create a SecurityContext and test positive and negative cases for every unit test. Using annotations in this matter eliminates this problem. Because the authorization check is merely declared in the annotation, it is never evaluated during your unit tests. This allows your tests to focus on testing the actual business logic that your code implements.

Another important point is that authorization rules are really a contract. When you create a UserService interface, you do so to establish a contract about what all UserService implementations must do, regardless of how they do it. Now imagine swapping out implementations of this UserService. Would those implementations suddenly have different authorization rules, too? No. The authorization rules are not an implementation detail. They are part of the contract, and as such they don’t belong in the implementation — they belong in the interface. Spring Security’s annotation support lets you (but does not require you to) declare these authorization rules in your interfaces so that they are part of the contract and apply to any implementation. This is a vast improvement over embedding the authorization checks in your application code.

Configuring Annotation Support

Annotation support is disabled in Spring Security by default. You must enable annotation support to use any of the annotations covered in this section; otherwise, they are ignored. To enable annotation support you first need to expose the AuthenticationManager as a bean. Because you can have multiple web security configurations (which you learn about in the next chapter), the WebSecurityConfigurerAdapter does not expose the AuthenticationManager bean by default. (Otherwise, you could have duplicate beans.) Overriding the configuration adapter’s authenticationManagerBean method allows the exposure of this bean.

Next, you need to add @org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity to your configuration class and configure its attributes. Three of these attributes allow you to specify which annotations you want to support.jsr250Enabled controls support for the Common Annotations API, whereas securedEnabled controls support for @Secured and prePostEnabled for the pre- and post-execution annotations. By default, support for all three types is disabled because the attributes default tofalse. The following configuration enables support for all three groups using the SecurityConfiguration class you created in Chapter 26:

@Configuration

@EnableWebMvcSecurity

@EnableGlobalMethodSecurity(

jsr250Enabled = true, securedEnabled = true, prePostEnabled = true

)

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Bean

@Override

public AuthenticationManager authenticationManagerBean() throws Exception

{

return super.authenticationManagerBean();

}

...

}

In most cases, however, you won’t want to enable all three groups of annotations. In fact, intermixing these groups can result in unspecified outcomes that might not be what you expect. You should usually pick one of the three annotation groups and stick with it throughout your application. In these last two chapters of the book, you work with the pre- and post- annotations exclusively.

You also need to pay attention to proxy ordering. You should recall from Part III (when you added @EnableTransactionManagement to your RootContextConfiguration) that all Spring Framework features that wrap beans with proxies must be configured the same way and should be ordered appropriately. If you use AdviceMode.PROXY on one, you should use it on all of them. If you set proxyTargetClass to false on one, you should do the same on all of them. Like @EnableAsync and @EnableTransactionManagement, @EnableGlobalMethodSecurityprovides mode, proxyTargetClass, and order attributes to control these features because it configures a proxy that wraps your beans. You should use the same mode and proxyTargetClass values that you use for @EnableAsync and @EnableTransactionManagement. As for order, generally speaking the Spring Security proxy should execute before any other proxies on the same method. More important, it should always execute before the asynchronous execution and transaction management proxies.

@Configuration

@EnableWebMvcSecurity

@EnableGlobalMethodSecurity(

prePostEnabled = true, order = 0,

mode = AdviceMode.PROXY, proxyTargetClass = false

)

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

}

The previous code is comparable to the following XML configuration:

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"

xmlns:beans="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/security

http://www.springframework.org/schema/security/spring-security-3.2.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

<global-method-security pre-post-annotations="enabled"

proxy-target-class="false" order="0" />

...

</beans:beans>

You have many ways to further customize global method security. You can create custom decision managers, expression handlers, invocation managers, metadata sources, and method advices. In an XML configuration you do this using other <global-method-security> attributes or sub-elements. Using Java configuration you must extend org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration and override one or more of its methods to customize the configuration defaults further.

For example, the hasPermission security function is handled using an implementation of org.springframework.security.access.PermissionEvaluator. Normally you would configure an org.springframework.security.acls.AclPermissionEvaluator (which uses Spring Security’s access control list features to evaluate object permissions) for this purpose. If you want to use a custom implementation, you just need to override the createExpressionHandler method and customize the PermissionEvaluator it uses. You could also call the handler’ssetParameterNameDiscoverer method to configure an alternative parameter name discovery protocol.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Configuration

@EnableGlobalMethodSecurity(

prePostEnabled = true, order = 0,

mode = AdviceMode.PROXY, proxyTargetClass = false

)

public static class AuthorizationConfiguration

extends GlobalMethodSecurityConfiguration

{

@Override

public MethodSecurityExpressionHandler createExpressionHandler()

{

DefaultMethodSecurityExpressionHandler handler =

new DefaultMethodSecurityExpressionHandler();

handler.setPermissionEvaluator(new CustomPermissionEvaluator());

return handler;

}

}

}

This code uses a new configuration syntax you haven’t seen before. SecurityConfiguration already extends WebSecurityConfigurerAdapter, so it can’t extend another class. To get around this, you just need to define a separate configuration class that extendsGlobalMethodSecurityConfiguration. Using an inner class allows you to still group the entire security configuration in this file and also eliminates the need to @Import the AuthorizationConfiguration. (Spring Framework automatically imports static inner @Configurationclasses of other @Configuration classes.) If you create a configuration class that extends GlobalMethodSecurityConfiguration, you must annotate it with @EnableGlobalMethodSecurity. You may have only one configuration class marked with this annotation, and only one configuration class may extend GlobalMethodSecurityConfiguration.

Defining Method Pointcut Rules

As you might understand, some development teams prefer not to decorate their interfaces and classes with more and more annotations. There are arguably some down sides to using security annotations. For example, changing your authorization rules means recompiling your interfaces or their implementations. It may be easier to recompile your configuration classes or, more important, you might keep your configuration in XML files so that you can change the configuration without any recompilation. Within the<global-method-security> XML tag, you can define one or more AspectJ pointcut expressions and corresponding permissions or roles (comma-separated for or-ing multiple permissions for a single pointcut). Spring Security applies those restrictions to any bean methods matching the AspectJ pointcut, for example:

<global-method-security>

<protect-pointcut expression="execution(* com.wrox.site.admin.*(*))"

access="ADMIN" />

</global-method-security>

Unfortunately, this feature does not support SpEL security expressions, which limits its usefulness. (Spring Security JIRA issue SEC-1663 documents a feature request to add expression support if you are truly interested.) It also requires you to learn about the very complex AspectJ pointcut expression syntax, which is beyond the scope of this discussion. Although method pointcut rules are useful for applying very simple restrictions to large numbers of methods with minimal code, this book recommends you stick to using the pre- and post-execution annotations for method security. Method security pointcuts are not explored further in this book.

UNDERSTANDING AUTHORIZATION DECISIONS

To a large extent, you can configure Spring Security to handle most authorization scenarios without customizing its implementation. However, you may occasionally need to customize something, and understanding how Spring Security determines authorization decisions makes this process much simpler. Understanding this decision-making process also helps you troubleshoot behavior that doesn’t match your intentions or desires. Finally, grasping Spring Security’s access decision making is critical if you decide to switch from Spring Security’s default decision-making settings to alternative settings that Spring Security provides. This section covers the decision-making process and the interfaces and implementations that power it.

Using Access Decision Voters

Access Decision Voters are the key actors in the decision-making process. A typical Spring Security configuration contains multiple voters, which are implementations of org.springframework.security.access.AccessDecisionVoter. Just like members of a governmental body vote on whether to pass legislation, AccessDecisionVoters vote on whether to permit access to some part of the application. When a voter is polled for its opinion on whether access should be granted, it casts its vote based on the specific knowledge that voter is given. If it isn’t given enough information to make an informed vote (for example, some other feature it doesn’t know about is the only feature protecting a resource), it returns AccessDecisionVoter.ACCESS_ABSTAIN from its vote method to abstain from voting. If it does have enough information and believes access should be granted, it returns AccessDecisionVoter.ACCESS_GRANTED. Likewise, it returns AccessDecisionVoter.ACCESS_DENIED if it believes access should not be granted.

Several AccessDecisionVoter implementations are available, and Spring Security configures the appropriate one for you based on the security features you enable and disable. Your application may use just one or it may use many AccessDecisionVoters. The existing implementations are detailed in the following list.

· org.springframework.security.acls.AclEntryVoter casts its vote based on Spring Security’s access control lists. If the resource accessed is not protected with the hasPermission expression function, AclEntryVoter abstains from voting. This voter isn’t automatically configured, and in fact you will probably never use it. You have better ways to configure access control lists, which you explore in the next section.

· org.springframework.security.access.vote.AuthenticatedVoter acts on the special-case roles IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_REMEMBERED, and IS_AUTHENTICATED_FULLY. If the resource is not protected with one of these roles, AuthenticatedVoter abstains.

· The org.springframework.security.access.annotation.Jsr250Voter votes for any methods protected using the Common Annotations API (JSR 250). For example, if a method is annotated with @RolesAllowed, the Jsr250Voter votes based on the permissions specified in that annotation. This voter is enabled only if you enable support for JSR 250 annotations. When enabled, it votes only if a JSR 250 annotation is present for the method (or its class or interface). If one is not present or the access decision is for a non-method resource (such as a URL), this voter abstains.

· org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter casts a vote based on the @PreAuthorize and @PreFilter annotations. It is enabled only if pre- and post-execution annotation support is enabled. It abstains for non-method resources or when the annotations aren’t present for a method (or its class or interface). There is no voter for @PostFilter and @PostAuthorize — Spring Security handles these annotations as a special case because voters are used only before resource access.

· The org.springframework.security.access.vote.RoleHierarchyVoter uses Spring Security’s role hierarchy system to make access decisions. You must create it manually if you enable the role hierarchy system, and it abstains if role hierarchy restrictions are not present on the protected resource being accessed. This book does not detail the role hierarchy system because use of role-based authorization is generally discouraged (see Chapter 25).

· org.springframework.security.access.vote.RoleVoter votes for resources protected with non-expression URL restrictions, method pointcut restrictions, or the @Secured annotation, and abstains for all other cases. It votes only if one or more listed “roles” start with the ROLE_ prefix (which is why you can use only ROLE_-prefixed permissions with @Secured). This voter is enabled if you have @Secured annotations enabled, if you have use-expressions set to false in an XML <http> configuration element, or if you use method pointcut restrictions.

· The org.springframework.security.web.access.expression.WebExpressionVoter makes its decisions based on expressions protecting URL resources. It abstains for method protection decisions and in the absence of an expression protecting a particular URL. It is enabled if you use Java configuration or have use-expressions set to true in an XML <http> configuration element.

This pattern of voting “yes,” “no,” or “abstain” is very common in technologies that control access. For example, nearly all firewall systems have rules that evaluate to “accept,” “deny,” or “neutral.” If you recall Log4j 2 filters from Chapter 11, you know that they, too, use a similar pattern. It’s an extremely flexible architecture that essentially supports every possible rule.

Using Access Decision Managers

As you can see, there are several built-in voters, and it’s possible that multiple voters could be enabled simultaneously. In fact, multiple voters could have a non-abstaining opinion on a particular access decision. So how does Spring Security reconcile these differences? It uses Access Decision Managers to coordinate the votes of one or more voters and turn them into a final decision on the fate of an access request.

To continue the analogy of a governing body, a decision manager, which implements org.springframework.security.access.AccessDecisionManager, acts like a prime minister or speaker of the governing body. It tallies up the votes and determines whether the appropriate rules are met to grant access. Ultimately, the decision managers are the components that decide whether to throw an AccessDeniedException.

Your application could have multiple decision managers configured simultaneously. If you enable global method security, this creates a decision manager to manage access to protected methods. This decision manager can use most of the aforementioned voters. Likewise, configuring URL restrictions in your HTTP configuration creates another decision manager for controlling access to your application URLs. The WebExpressionVoter exists solely to service this manager because it has no purpose elsewhere in your application.

Spring Security ships with three standard AccessDecisionManager implementations; however, you can easily create your own. You can use these implementations for method security, URL security, or anything else that calls for a decision manager. By default, access is denied when all voters abstain, but each standard implementation provides a setting to determine whether to grant access in this scenario.

Deciding by Affirmation

The org.springframework.security.access.vote.AffirmativeBased is the default and perhaps the simplest decision manager. This decision manager grants access if at least one non-abstaining voter approves. Even if other voters reject access, this decision manager still grants access if at least one voter returns ACCESS_GRANTED. You can probably see why this pattern is problematic in some cases, but in most cases it is perfectly sufficient. You typically won’t have two URL restrictions match a request, but if you do, it’s usually fine to allow access if at least one rule permits it. The same applies to method restrictions. Rarely do you use both @Secured and @PreAuthorize on the same method, for example, but if you did, it’s logical to conclude those conditions should be or-ed together, not and-ed.

Deciding by Consensus

Consensus decisions are probably the kind you are most familiar with. All republican and democratic forms of government operate on some principal of decisions made by consensus, whether it’s a simple majority or two-thirds of a governing body agreeing. Theorg.springframework.security.access.vote.ConsensusBased decision manager operates on the simple majority principle. If 50-percent-plus-1 non-abstaining voters approve, access is granted.

For example, if you have all 7 voters enabled, 4 voters abstain, 2 voters vote to grant access, and 1 votes to deny access, access is granted. However, if 1 votes to grant and 2 vote to deny, access is denied. Of course, you have the problem of ties, in which the same number of grant and deny votes are cast. For this case, the ConsensusBased decision manager provides a setting for determining the tiebreaker policy. By default, access is granted in the event of a tie.

Deciding by Unanimity

The natural conclusion to the set of standard decision managers is the one that requires all non-abstaining voters to vote yes — org.springframework.security.access.vote.UnanimousBased. All non-abstaining voters must vote to grant access; otherwise, this decision manager denies access. This is a very strict policy but, nevertheless, is appropriate in some cases.

Configuring a different AccessDecisionManager for global method security is really straightforward. If your @Configuration class doesn’t extend GlobalMethodSecurityConfiguration, make that change. Then simply override the accessDecisionManager method to return aConsesusBased, a UnanimousBased, or some custom implementation. Don’t forget to include the voters in whatever manager you decide to return. Configuring a different decision manager for web security is equally easy: just add it to the configure(HttpSecurity) method.

@Override

protected void configure(HttpSecurity security) throws Exception

{

security

.authorizeRequests()

.accessDecisionManager(new CustomDecisionManager())

...

}

CREATING ACCESS CONTROL LISTS FOR OBJECT SECURITY

So far this chapter has presented a relatively naïve approach to authorization, in which only a user’s permissions or whether he “owns” a resource or object determine his access to that resource. Sometimes this approach is perfectly sufficient. Simple applications, especially applications with minimal user input, usually operate with very few authorization rules. Even applications like user forums, which seem fairly complicated, are actually simple in terms of authorization. However, sometimes you need much more complicated solutions.

An access control list (or ACL) is a more complex approach to object security, which defines per-user, per-object permissions. For a given object type Foo, a user Bar might have completely different permissions for different instances of that object. This goes beyond the idea of user ownership: A user might administer some instances, write others, delete others, read others, and finally not access others at all. Operating system file permissions, such as those in Microsoft Windows and Apple Mac OS X, are based on ACL principles. Linux operating systems also support ACL permissions; however, by default, they are not enabled and many Linux users are unfamiliar with using them.

Understanding Spring Security ACLs

If you have ever managed file permissions in Windows or Mac OS X, or in Linux with ACL permissions enabled, you are very familiar with how ACLs work. Every file on your filesystem has a set of permissions, and for any given file, you can define different permissions for each user with access to that file system.

As you can imagine, a system that supports this is not trivial. In addition to containing complex permission management mechanisms, a system requires a significant set of backing data to hold these permissions. If you imagine an application with one million records of a given type in a system with one million users, you could potentially have one trillion pieces of data just to record those users’ permissions for that one object type! No matter how efficiently you store that data, you’re still looking at several terabytes of data. Understand that this is an extreme scenario (most users would have permissions to access only a small percentage of each data type), but undoubtedly you can see how important it is for you to design such a system well.

Spring Security comes with a mature, industry-tested ACL system that provides fine-grained control over your users’ permissions. The central player in this system is the org.springframework.security.acls.model.Acl interface. It represents the access control list for a single domain object, and every domain object has exactly one Acl. It generically encapsulates the ID of the domain object in the form of an org.springframework.security.acls.model.ObjectIdentity, the ID of the owner of the domain object (the user that created it) in the form of an org.springframework.security.acls.model.Sid, and a list of all the permissions assigned to every user allowed to use that object in some way in the form of a List of org.springframework.security.acls.model.AccessControlEntrys.

Each AccessControlEntry encapsulates the ID of the user that holds that entry and the org.springframework.security.acls.model.Permission assigned to the entry. Permissions are represented with bitmasks — a highly efficient storage mechanism where each bit of a number represents a single permission. For example, bit 1 might control permission to read, whereas bit 2 controls permission to write. An Acl can also contain a parent Acl, in which case it inherits the AccessControlEntrys of that parent.

NOTE Bit masking can be a scary term to even seasoned developers. This same technique is used for standard Unix filesystem permissions and many other application and operating system activities. Rest assured, Spring Security abstracts away all the bit masking horror; you should never have to deal with it directly. The standard Permission implementation, org.springframework.security.acls.domain.BasePermission, understands the permissions read, write, create, delete, and admin, which are sufficient for most use cases. If you need to support other permissions, you must implement your own Permission class and tell it how to convert the bitmasks for your implementation.

There are two standard implementations for Sid: org.springframework.security.acls.domain.PrincipalSid corresponds to an Authentication, whereas org.springframework.security.acls.domain.GrantedAuthoritySid corresponds to a GrantedAuthority. The latter is useful if you use GrantedAuthoritys to represent roles or groups and you want to grant permission to everyone in a particular role or group. With both these implementations, the Sid is ultimately represented with the String form of the Authentication or GrantedAuthority (the username or authority name). If you want to represent these with, say, a long user ID, you can easily implement your own Sid.

The ACL system is powered by an implementation of org.springframework.security.acls.model.AclService. This service can load ACLs by object identifier for use in permission evaluation. It is what enables you to use the hasPermission expression function to control access to your methods — calling this function loads the ACL for the given domain object using the AclService implementation.

If you want to, you can implement your own AclService, but this is not recommended. Spring Security’s org.springframework.security.acls.jdbc.JdbcAclService is designed to very efficiently store ACLs in a relational database and retrieve them in a high-performance manner. The JAR artifact for spring-security-acl contains SQL scripts that create the necessary tables for MySQL/MariaDB (createAclSchemaMySql.sql), PostgreSQL (createAclSchemaPostgresql.sql), and HSQLDB (createAclSchema.sql). If you decide to use a different database, you can easily translate one of these scripts. Because JdbcAclService uses standard ANSI SQL features for data manipulation, it should be compatible with nearly any popular relational database.

Four ACL tables serve the following functions:

· acl_sid uniquely defines each Sid in the system for referential integrity with other tables. Typically, you have one row in this table for every user in your system, assuming you use only PrincipalSid or similar.

· acl_class uniquely defines each domain object class in the application. Like acl_sid, this table is used for referential integrity.

· acl_object_identity corresponds to the Acl class and holds the ObjectIdentity, owner Sid, and other Acl properties. You’ll have one row in this table for every ACL-protected domain object in your system.

· acl_entry maps to the List of AccessControlEntrys in each Acl.

WARNING The acl_entry table has the potential to grow to many billions or even trillions of records, depending on the number of domain objects in your application, the number of persisted instances of those domain objects, and the number of users in your system. You should always monitor this table for performance problems and adjust indexing, system properties, or hardware as appropriate.

Configuring Access Control Lists

If you’re going to implement your own ACL system, how you configure it is largely determined by how you design it. You either need to implement AclService and modify the DefaultMethodSecurityExpressionHandler to provide it with an AclPermissionEvaluator that uses this service, or just define your own PermissionEvaluator implementation.

Configuring Spring Security’s ACL implementation is significantly more involved, but when set up correctly, it does most of the work for you. You would start by creating the Spring Security ACL methods in your application and defining an inner @Configuration class for global method security.

@Configuration

@EnableGlobalMethodSecurity(

prePostEnabled = true, order = 0,

mode = AdviceMode.PROXY, proxyTargetClass = false

)

public static class MethodAuthorizationConfiguration

extends GlobalMethodSecurityConfiguration

{

private static final Logger log = LogManager.getLogger();

...

}

NOTE The spring-security-acl Maven artifact is already a run-time dependency of other Spring Security artifacts you have included in your application. However, if you are going to use ACL you need to make this dependency a compile-time dependency.

<dependency>

<groupId>org.springframework.security</groupId>

<artifactId>spring-security-acl</artifactId>

<version>3.2.0.RELEASE</version>

<scope>compile</scope>

</dependency>

You need to configure an implementation of org.springframework.security.acls.domain.AclAuthorizationStrategy. The ACL system uses this to determine whether the current principal has permissions to actually access administrative functions of the ACL service (such as changing the ACL for a particular object). The standard implementation permits use of these administrative methods if the principal is the owner of the object, has the administrative permission for the object, or has any of the granted authorities listed in the strategy constructor.

@Bean

public AclAuthorizationStrategy aclAuthorizationStrategy()

{

return new AclAuthorizationStrategyImpl(

new SimpleGrantedAuthority("ADMINISTRATOR")

);

}

Next, you need to configure an org.springframework.security.acls.model.PermissionGrantingStrategy. This interface allows you to customize how permissions are evaluated, but the standard implementation is usually sufficient. It needs anorg.springframework.security.acls.domain.AuditLogger for logging ACL authorization events. Configured as shown in the following code, it uses a lambda expression to provide logging behavior. You could take many different approaches, such as using the defaultorg.springframework.security.acls.domain.ConsoleAuditLogger that logs to standard output.

@Bean

public PermissionGrantingStrategy permissionGrantingStrategy()

{

return new DefaultPermissionGrantingStrategy((granted, entry) -> {

if (!granted)

log.info("Access denied for [{}].", entry);

});

}

Now, you need to set up an org.springframework.security.acls.model.AclCache implementation. For optimized performance, the ACL system needs to cache entries until the next time they are changed. Spring Security comes with AclCache implementations capable of using either Ehcache (a popular open source distributed cache system for Java) or Spring Framework’s cache system. For demonstration purposes, Spring Framework’s cache based on java.util.concurrent.ConcurrentMap is sufficient. If you have a clustered application, you must provide an appropriate distributed cache implementation.

@Bean

public AclCache aclCache()

{

return new SpringCacheBasedAclCache(

new ConcurrentMapCache("Security-Acl"),

this.permissionGrantingStrategy(),

this.aclAuthorizationStrategy()

);

}

After configuring these base systems, you need to set up the JDBC ACL implementation and add the PermissionEvaluator. The JdbcAclService and org.springframework.security.acls.jdbc.BasicLookupStrategy work together to efficiently retrieve ACL entries using your application DataSource and cache them when appropriate. The AclPermissionEvaluator and org.springframework.security.acls.AclPermissionCacheOptimizer work together to evaluate hasPermission expressions.

@Inject DataSource dataSource;

@Bean

public LookupStrategy lookupStrategy()

{

return new BasicLookupStrategy(

this.dataSource,

this.aclCache(),

this.aclAuthorizationStrategy(),

this.permissionGrantingStrategy()

);

}

@Bean

public AclService aclService()

{

return new JdbcAclService(this.dataSource, this.lookupStrategy());

}

@Override

public MethodSecurityExpressionHandler createExpressionHandler()

{

DefaultMethodSecurityExpressionHandler handler =

new DefaultMethodSecurityExpressionHandler();

handler.setPermissionEvaluator(new AclPermissionEvaluator(

this.aclService()

));

handler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(

this.aclService()

));

return handler;

}

Finally, you can add hasPermission expressions to your methods to restrict access based on ACL permissions. For the most part, this entire configuration consists of simple Spring beans, and you should be very familiar with how to configure these components using XML. If you use an XML configuration, you need to configure the DefaultMethodSecurityExpressionHandler with its evaluator and optimizer as a standard bean and then use the Spring Security namespace to assign it to global method security:

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"

xmlns:beans="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.springframework.org/schema/security

http://www.springframework.org/schema/security/spring-security-3.2.xsd

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

<global-method-security pre-post-annotations="enabled" order="0"

proxy-target-class="false">

<expression-handler ref="expressionHandler" />

</global-method-security>

</beans:beans>

Populating ACLs for Your Entities

So far you have seen how to use ACLs, but you still need to populate ACLs for your entities. Spring Security does not do this for you automatically. Any time you create an entity, you must populate its owner, administrator, and other user permissions for that entity. Likewise, you may need service methods specifically for modifying the permissions for an entity, where user interfaces are provided for this purpose. You can manually populate the ACL database tables if you want, but Spring Security does provide a simpler approach to this task. You can create ObjectIdentity, Sid, Permission, and Acl instances and persist them to the database using an org.springframework.security.acls.model.MutableAclService, similar to the way you would use an Object-Relational Mapper.

To make this possible, you must first replace your JdbcAclService with a mutable version of it, org.springframework.security.acls.jdbc.JdbcMutableAclService. It also requires a reference to the caching bean because it must update the cache whenever an ACL changes.

@Bean

public MutableAclService aclService()

{

return new JdbcMutableAclService(

this.dataSource, this.lookupStrategy(), this.aclCache()

);

}

Using the mutable service is easy. In any of your services, you can obtain an @Injected MutableAclService and then use it to insert or update the ACL, where appropriate. The following code updates an existing ACL or inserts a new ACL if one does not already exist. The ObjectIdentityImpl constructor used expects the entity to have a getId method. Alternatively, you can use the constructor that accepts a Class<?> object type and a Serializable ID value. If the mutable ACL service has to create a new ACL, it assigns the current principal as the owner of the object. You can change this by calling the setOwner method on the MutableAcl. This code gives another user read and write permissions on the forum post.

ObjectIdentityImpl identity = new ObjectIdentityImpl(forumPost);

MutableAcl acl;

try

{

acl = (MutableAcl)this.mutableAclService.readAclById(identity);

}

catch(NotFoundException e)

{

acl = this.mutableAclService.createAcl(identity);

}

Authentication otherUser = this.userService.getUser("OtherUserName");

PrincipalSid sid = new PrincipalSid(otherUser);

acl.insertAce(acl.getEntries().length, BasePermission.READ, sid, true);

acl.insertAce(acl.getEntries().length, BasePermission.WRITE, sid, true);

this.mutableAclService.updateAcl(acl);

Although Spring Security’s ACL system is very powerful, it can be overkill. You don’t need ACLs in every application, and if you can create a useful, secure application without ACLs, you’re usually better off. More important, using ACLs unnecessarily can make performance suffer. Integrating Spring Security’s ACL system into your application is an exercise left up to the reader.

ADDING AUTHORIZATION TO CUSTOMER SUPPORT

In the last chapter, you created a simple application to demonstrate Spring Security authentication and then integrated authentication into the Customer Support application. In this chapter, you improve the security of the Customer Support application using Spring Security authorization. You can obtain the Customer-Support-v20 project from the wrox.com code download site. If you have not been following along with the Customer Support application from previous chapters, don’t worry. The downloaded project is ready to compile and start, so you don’t need to be familiar with previous work completed in the application. You should, however, take a look at the authentication added in Chapter 26.

Before you start, there’s one thing you should know: Spring Security’s error messages are already localized in several languages for your convenience. Just add the resource bundles to your RootContextConfiguration’s messageSource bean:

...

messageSource.setBasenames(

"/WEB-INF/i18n/titles", "/WEB-INF/i18n/messages",

"/WEB-INF/i18n/errors", "/WEB-INF/i18n/validation",

"classpath:org/springframework/security/messages"

);

...

Switching to Custom User Details

When you added authentication to the Customer Support application in Chapter 26, you created a custom Authentication object and AuthenticationProvider implementation. This demonstrated that you could do this, if you need to, but it really wasn’t necessary. In fact, it’s not the best practice, either, so you should avoid it if you can. Instead, the convention is to provide a UserDetailsService implementation and, if necessary, a custom UserDetails.

Deciding on a User Details Implementation

When you design your system, ultimately you need to decide whether you want to couple the user object for persistence purposes with the user object for authentication and authorization purposes. The advantage to separating them is that you really don’t need (or even want) a mutable user object carried around in the security context, and keeping them separate enforces this mantra. However, the advantage to making them the same object is that you don’t have to do as much work to embed the user entity in other entities. Ultimately, only you can decide the approach that works best for you.

For simplicity, the Customer Support application combines the UserDetails implementation and the entity in the same object. The changes to UserPrincipal are pretty simple. It now implements UserDetails instead of Authentication and also implementsorg.springframework.security.core.CredentialsContainer so that Spring Security clears the password when the authentication process is complete.

...

public class UserPrincipal implements UserDetails, CredentialsContainer, Cloneable

{

private static final long serialVersionUID = 1L;

private long id;

private String username;

private byte[] hashedPassword;

private Set<UserAuthority> authorities = new HashSet<>();

private boolean accountNonExpired;

private boolean accountNonLocked;

private boolean credentialsNonExpired;

private boolean enabled;

// userId and username properties

@Basic(fetch = FetchType.LAZY)

@Column(name = "HashedPassword")

public byte[] getHashedPassword() { ... }

public void setHashedPassword(byte[] password) { ... }

@Transient

@Override

public String getPassword()

{

return this.getHashedPassword() == null ? null :

new String(this.getHashedPassword(), StandardCharsets.UTF_8);

}

@Override

public void eraseCredentials()

{

this.hashedPassword = null;

}

@Override

@ElementCollection(fetch = FetchType.LAZY)

@CollectionTable(name = "UserPrincipal_Authority", joinColumns = {

@JoinColumn(name = "UserId", referencedColumnName = "UserId")

})

public Set<UserAuthority> getAuthorities() { ... }

public void setAuthorities(Set<UserAuthority> authorities) { ... }

@Override

@XmlElement @JsonProperty

public boolean isAccountNonExpired() { ... }

public void setAccountNonExpired(boolean accountNonExpired) { ... }

@Override

@XmlElement @JsonProperty

public boolean isAccountNonLocked() { ... }

public void setAccountNonLocked(boolean accountNonLocked) { ... }

@Override

public boolean isCredentialsNonExpired() { ... }

public void setCredentialsNonExpired(boolean credentialsNonExpired) { ... }

@Override

@XmlElement @JsonProperty

public boolean isEnabled() { ... }

public void setEnabled(boolean enabled) { ... }

...

}

This, of course, requires some additional columns in the UserPrincipal table. If this is the first time you’ve run the Customer Support application, just run the SQL script in create.sql in the application to create all the tables and indexes you need. Otherwise, use the following code to add the necessary columns.

USE CustomerSupport;

ALTER TABLE UserPrincipal

ADD COLUMN AccountNonExpired BOOLEAN NOT NULL DEFAULT TRUE,

ADD COLUMN AccountNonLocked BOOLEAN NOT NULL DEFAULT TRUE,

ADD COLUMN CredentialsNonExpired BOOLEAN NOT NULL DEFAULT TRUE,

ADD COLUMN Enabled BOOLEAN NOT NULL DEFAULT TRUE;

ALTER TABLE UserPrincipal

MODIFY COLUMN AccountNonExpired BOOLEAN NOT NULL,

MODIFY COLUMN AccountNonLocked BOOLEAN NOT NULL,

MODIFY COLUMN CredentialsNonExpired BOOLEAN NOT NULL,

MODIFY COLUMN Enabled BOOLEAN NOT NULL;

The UserAuthority class is simple: It’s just an embeddable POJO that implements GrantedAuthority. @CollectionTable in UserPrincipal maps UserAuthority to the UserPrincipal_Authority table.

@Embeddable

public class UserAuthority implements GrantedAuthority

{

private String authority;

public UserAuthority() { }

public UserAuthority(String authority)

{

this.authority = authority;

}

@Override

public String getAuthority() { ... }

public void setAuthority(String authority) { ... }

}

CREATE TABLE UserPrincipal_Authority (

UserId BIGINT UNSIGNED NOT NULL,

Authority VARCHAR(100) NOT NULL,

UNIQUE KEY UserPrincipal_Authority_User_Authority (UserId, Authority),

CONSTRAINT UserPrincipal_Authority_UserId FOREIGN KEY (UserId)

REFERENCES UserPrincipal (UserId) ON DELETE CASCADE

) ENGINE = InnoDB;

Using the New User Details Entity

The old AuthenticationService no longer needs to handle authentication. Instead, it extends UserDetailsService, so it makes sense to rename it to UserService (and its implementation to DefaultUserService).

@Validated

public interface UserService extends UserDetailsService

{

@Override

UserPrincipal loadUserByUsername(String username);

...

}

@Service

public class DefaultUserService implements UserService

{

...

@Override

@Transactional

public UserPrincipal loadUserByUsername(String username)

{

UserPrincipal principal = userRepository.getByUsername(username);

// make sure the authorities and password are loaded

principal.getAuthorities().size();

principal.getPassword();

return principal;

}

...

}

The configuration needs just a slight tweak to use the DaoConfigurationProvider backed by the UserService and tell Spring Security to erase credentials after authentication.

@Configuration

@EnableWebMvcSecurity

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

@Inject UserService userService;

...

@Override

protected void configure(AuthenticationManagerBuilder builder)

throws Exception

{

builder

.userDetailsService(this.userService)

.passwordEncoder(new BCryptPasswordEncoder())

.and()

.eraseCredentials(true);

}

...

}

When your users authenticate, the UserPrincipal object is stored in the resulting Authentication object. You can call the Authentication’s getPrincipal method to retrieve the corresponding UserPrincipal. In previous versions of Spring Security this would be quite cumbersome in your controllers because you would have to cast the Principal to an Authentication and then call getPrincipal and cast that to a UserPrincipal. Because of Spring Security 3.2’s new @AuthenticationPrincipal annotation you learned about earlier, you can simply add annotated UserPrincipal parameters to your controller methods. Note that this wouldn’t be possible if UserPrincipal still implemented Authentication as in Chapter 26.

@WebController

@RequestMapping("ticket")

public class TicketController

{

...

@RequestMapping(value = "create", method = RequestMethod.POST)

public ModelAndView create(@AuthenticationPrincipal UserPrincipal principal,

@Valid TicketForm form, Errors errors,

Map<String, Object> model)

throws IOException

{

...

}

...

}

Securing Your Service Methods

In Chapter 26, you added a single, simple authorization rule that protects all URLs by requiring authentication. Now you need to secure your service methods so that only certain users can perform certain tasks. First, you have to enable global method security. This is as simple as adding @EnableGlobalMethodSecurity to the SecurityConfiguration class — enabling pre- and post-execution annotations in the process — and exposing the AuthenticationManager as a bean. There is no need to create an inner class extendingGlobalMethodSecurityConfiguration in this case, because nothing in the method security configuration needs to be customized.

@Configuration

@EnableWebMvcSecurity

@EnableGlobalMethodSecurity(

prePostEnabled = true, order = 0, mode = AdviceMode.PROXY,

proxyTargetClass = false

)

public class SecurityConfiguration extends WebSecurityConfigurerAdapter

{

...

@Bean

@Override

public AuthenticationManager authenticationManagerBean() throws Exception

{

return super.authenticationManagerBean();

}

...

}

NOTE The order for the global method security proxy is set to 0 here. In addition, the proxy order for asynchronous method execution is 1, whereas the proxy order for transactional support is 2. This ensures that security always runs first, followed by asynchronous method execution, and finally transaction operations.

Protecting the TicketService is straightforward. You can protect all the methods using @PreAuthorize and fairly simple expressions. However, because you want users to edit only their own tickets and comments (unless they have an administrator-level privilege to edit anyone’s comment), you must split up the save methods for Tickets and TicketComments into create and update methods. The TicketController, TicketRestEndpoint, and TicketSoapEndpoint must then be updated with the changed method names (not shown here). Notice the use of @P to indicate the parameter names for Spring Security expressions.

...

public interface TicketService

{

@NotNull

@PreAuthorize("hasAuthority('VIEW_TICKETS')")

List<Ticket> getAllTickets();

@NotNull

@PreAuthorize("hasAuthority('VIEW_TICKETS')")

Page<SearchResult<Ticket>> search(...);

@PreAuthorize("hasAuthority('VIEW_TICKET')")

Ticket getTicket(...);

@PreAuthorize("#ticket.id == 0 and hasAuthority('CREATE_TICKET')")

void create(@NotNull(message = "{validate.ticketService.save.ticket}")

@Valid @P("ticket") Ticket ticket);

@PreAuthorize("(authentication.principal.equals(#ticket.customer) and " +

"hasAuthority('EDIT_OWN_TICKET')) or hasAuthority('EDIT_ANY_TICKET')")

void update(@NotNull(message = "{validate.ticketService.save.ticket}")

@Valid @P("ticket") Ticket ticket);

@PreAuthorize("hasAuthority('DELETE_TICKET')")

void deleteTicket(long id);

@NotNull

@PreAuthorize("hasAuthority('VIEW_COMMENTS')")

Page<TicketComment> getComments(...);

@PreAuthorize("#comment.id == 0 and hasAuthority('CREATE_COMMENT')")

void create(

@NotNull(message = "{validate.ticketService.save.comment}")

@Valid @P("comment") TicketComment comment,

@Min(value = 1L, message = "{validate.ticketService.saveComment.id}")

long ticketId

);

@PreAuthorize("(authentication.principal.equals(#comment.customer) and " +

"hasAuthority('EDIT_OWN_COMMENT')) or " +

"hasAuthority('EDIT_ANY_COMMENT')")

void update(@NotNull(message = "{validate.ticketService.save.comment}")

@Valid @P("comment") TicketComment comment);

@PreAuthorize("hasAuthority('DELETE_COMMENT')")

void deleteComment(long id);

@PreAuthorize("hasAuthority('VIEW_ATTACHMENT')")

Attachment getAttachment(long id);

}

You also need to secure chat requests, which introduces a few more complications. Here, you must not only check that users are authorized to perform the task, but also that they are actually members of the chat session when they send a message or leave the session.

public interface ChatService

{

@PreAuthorize("authentication.principal.username.equals(#user) and " +

"hasAuthority('CREATE_CHAT_REQUEST')")

CreateResult createSession(@P("user") String user);

@PreAuthorize("authentication.principal.username.equals(#user) and " +

"hasAuthority('START_CHAT')")

JoinResult joinSession(long id, @P("user") String user);

@PreAuthorize("authentication.principal.username.equals(#user) and " +

"(#user.equals(#session.customerUsername) or " +

"#user.equals(#session.representativeUsername)) and " +

"hasAuthority('CHAT')")

ChatMessage leaveSession(@P("session") ChatSession session,

@P("user") String user, ReasonForLeaving reason);

@PreAuthorize("authentication.principal.username.equals(#message.user) and " +

"(#message.user.equals(#session.customerUsername) or " +

"#message.user.equals(#session.representativeUsername)) and " +

"hasAuthority('CHAT')")

void logMessage(@P("session") ChatSession session,

@P("message") ChatMessage message);

@PreAuthorize("hasAuthority('VIEW_CHAT_REQUESTS')")

List<ChatSession> getPendingSessions();

...

}

You must also consider something else, however. Currently, Spring Security has no support for handling WebSocket sessions. This is a planned feature for Spring Security 4.0, which will hopefully release by the middle of 2014. That version should take care of many things for you, such as overriding the Principal in the Session and closing WebSocket sessions when HttpSessions are logged out. Indeed, it could get rid of a great deal of code in your application. For now, however, you must take some special steps on your own. When a WebSocket session is handed off and asynchronous communications begin, the Security Context is cleared. To compensate, you must create a method to handle secured operations.

private void doSecured(SecuredAction secureAction)

{

SecurityContextHolder.setContext(this.securityContext);

try

{

secureAction.execute();

}

finally

{

SecurityContextHolder.clearContext();

}

}

@FunctionalInterface

private static interface SecuredAction

{

void execute();

}

public static class EndpointConfigurator extends SpringConfigurator

{

...

@Override

public void modifyHandshake(ServerEndpointConfig config,

HandshakeRequest request,

HandshakeResponse response)

{

...

config.getUserProperties().put(SECURITY_CONTEXT_KEY,

SecurityContextHolder.getContext());

...

}

...

}

You then have to make sure that all @OnOpen, @OnClose, @OnMessage, and @OnError methods (and any other methods that the WebSocket container could invoke) execute within a secured action. For example, here is the new @OnError method:

@OnError

public void onError(Throwable e)

{

this.doSecured(() -> {

log.warn("Error received in WebSocket session.", e);

synchronized(this)

{

if(this.closed)

return;

this.close(ChatService.ReasonForLeaving.ERROR,

"error.chat.closed.exception");

}

});

}

The last thing you need to think about is the session list. This is an administrative function, but the service (Spring Security’s SessionRegistry) is not in your control, so you cannot add security annotations to the service. For this, you will need to fall back to standard URL security rules in your SecurityConfiguration.

@Override

protected void configure(HttpSecurity security) throws Exception

{

security

.authorizeRequests()

.antMatchers("/session/list")

.hasAuthority("VIEW_USER_SESSIONS")

.anyRequest().authenticated()

...

}

Now that you know what all the secured actions in your application are, you need to assign permissions to your users. If you just ran the create.sql script earlier in the section, this was already done for you. Otherwise, you must find the following four inserts in the script and run them to grant the authorities. The inserts assume you still have the pre-populated users (Nicholas, Sarah, Mike, and John) in your database, and they grant standard “customer” permissions to Nicholas, Sarah, and Mike, and “representative” permissions to John.

USE CustomerSupport;

INSERT INTO UserPrincipal_Authority (UserId, Authority)

VALUES (1, 'VIEW_TICKETS'), (1, 'VIEW_TICKET'), (1, 'CREATE_TICKET'),

(1, 'EDIT_OWN_TICKET'), (1, 'VIEW_COMMENTS'), (1, 'CREATE_COMMENT'),

(1, 'EDIT_OWN_COMMENT'), (1, 'VIEW_ATTACHMENT'), (1, 'CREATE_CHAT_REQUEST'),

(1, 'CHAT');

INSERT INTO UserPrincipal_Authority (UserId, Authority)

VALUES (2, 'VIEW_TICKETS'), (2, 'VIEW_TICKET'), (2, 'CREATE_TICKET'),

(2, 'EDIT_OWN_TICKET'), (2, 'VIEW_COMMENTS'), (2, 'CREATE_COMMENT'),

(2, 'EDIT_OWN_COMMENT'), (2, 'VIEW_ATTACHMENT'), (2, 'CREATE_CHAT_REQUEST'),

(2, 'CHAT');

INSERT INTO UserPrincipal_Authority (UserId, Authority)

VALUES (3, 'VIEW_TICKETS'), (3, 'VIEW_TICKET'), (3, 'CREATE_TICKET'),

(3, 'EDIT_OWN_TICKET'), (3, 'VIEW_COMMENTS'), (3, 'CREATE_COMMENT'),

(3, 'EDIT_OWN_COMMENT'), (3, 'VIEW_ATTACHMENT'), (3, 'CREATE_CHAT_REQUEST'),

(3, 'CHAT');

INSERT INTO UserPrincipal_Authority (UserId, Authority)

VALUES (4, 'VIEW_TICKETS'), (4, 'VIEW_TICKET'), (4, 'CREATE_TICKET'),

(4, 'EDIT_OWN_TICKET'), (4, 'VIEW_COMMENTS'), (4, 'CREATE_COMMENT'),

(4, 'EDIT_OWN_COMMENT'), (4, 'VIEW_ATTACHMENT'), (4, 'CREATE_CHAT_REQUEST'),

(4, 'CHAT'), (4, 'EDIT_ANY_TICKET'), (4, 'DELETE_TICKET'),

(4, 'EDIT_ANY_COMMENT'), (4, 'DELETE_COMMENT'), (4, 'VIEW_USER_SESSIONS'),

(4, 'VIEW_CHAT_REQUESTS'), (4, 'START_CHAT');

Using Spring Security’s Tag Library

Now that you’ve configured method security, your services are secured against unauthorized access. However, is it really a good user experience to click a link only to be greeted with an “Access Denied” message? You need to improve your views so that users can’t see actions they can’t perform. The creators of Spring Security thought about pretty much everything, so you have a tag library at your disposal to make this task easy.

<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>

The <security:accesscontrollist> tag, which you won’t use here, allows you to perform checks against the access control list for an object. In the domainObject attribute, you use an EL expression (not a SpEL expression) to provide a domain object exposed as an EL variable. In the hasPermission attribute, you specify one or more (comma-separated) object permissions that the user must have on that domain object. The code nested within the tag is not evaluated unless the user has those permissions for the given domain object. Spring Security 4.0 will include <security:csrfField> and <security:csrfMetaTags> tags that output, respectively, a CSRF form field and CSRF HTML <meta> elements. JavaScript code can use these HTML <meta> elements to reference the CSRF field and header name and CSRF token value.

The <security:authentication> tag is very useful. It allows you to output properties of the current Authentication. The WEB-INF/tags/template/basic.tag file demonstrates this by printing out the username for the current principal.

<br />Welcome, <security:authentication property="principal.username" />!

The tag you’ll use most often is <security:authorize>. Its nested contents are evaluated only if the authorization rule defined with its attributes evaluates to true. If you specify the access attribute, it evaluates the value as a security expression (so you can usehasAuthority and the other functions). The url attribute allows you to specify an application-relative URL, and Spring Security finds and evaluates the matching URL security rule, if any. If you use url, you can also specify method to restrict the permitted HTTP methods. You cannot specify access and url at the same time. basic.tag demonstrates using <security:authorize> to hide links that the user isn’t authorized to access.

<security:authorize access="hasAuthority('VIEW_TICKETS')">

<a href="<c:url value="/ticket/list" />">...</a><br />

<a href="<c:url value="/ticket/search" />">...</a><br />

</security:authorize>

<security:authorize access="hasAuthority('CREATE_TICKET')">

<a href="<c:url value="/ticket/create" />">...</a><br />

</security:authorize>

<security:authorize access="hasAuthority('CREATE_CHAT_REQUEST')">

<a href="javascript:void 0;" onclick="newChat();">...</a><br />

</security:authorize>

<security:authorize access="hasAuthority('VIEW_CHAT_REQUESTS')">

<a href="<c:url value="/chat/list" />">...</a><br />

</security:authorize>

<security:authorize access="hasAuthority('VIEW_USER_SESSIONS')">

<a href="<c:url value="/session/list" />">...</a><br />

</security:authorize>

All the Spring Security tags provide var attributes like so many other tags you have used. For the <security:authorize> and <security:accesscontrollist> tags, the specified variable contains the boolean result of evaluating the rule. The tag cannot have nested content if you use this attribute. For the other tags, the tag output is stored in the specified variable. WEB-INF/jsp/view/chat/list.jsp demonstrates using the var attribute with <security:authorize> to omit links from the list if the user doesn’t have the START_CHAT authority.

<security:authorize access="hasAuthority('START_CHAT')"

var="canJoin" />

<spring:message code="message.chatList.instruction" />:<br /><br />

<c:forEach items="${sessions}" var="s">

<c:choose>

<c:when test="${canJoin}">

<a...>${s.customerUsername}</a><br />

</c:when>

<c:otherwise>${s.customerUsername}</c:otherwise>

</c:choose>

...

With these changes to the views, the Customer Support application is ready to go. Go ahead and compile it, start Tomcat from your IDE, and go to http://localhost:8080/support. Log in as different users and test out that the authorization system works properly by listing and creating tickets, listing sessions, and taking part in a chat session. If you want, you can set the system property spring.security.disableUISecurity, which disables the <security:authorize> tags so that their contents are always rendered, making it easier to attempt unauthorized access to resources and ensure that method security is working properly.

NOTE If you have never run the Customer Support application before, or you do not remember the usernames and passwords for it, consult the create.sql script. Each user’s INSERT statement includes a SQL comment with the plain-text password for that user.

SUMMARY

You have learned a lot about authorizing users in Spring Security in this chapter. You explored URL security and global method security, and compared a number of different method security annotations and how to use them. You learned about how Spring Security makes access decisions and how voters can contribute to those decisions, and also learned about Spring Security’s powerful access control list (ACL) system. Finally, you added authorization to Multinational Widget Corporation’s Customer Support application. You converted the UserPrincipal from an Authentication to a UserDetails, configured method security, added user interface security with the security tag library, and took special steps to maintain the SecurityContext in a WebSocket endpoint.

Only one thing remains to finish off the Customer Support application: you still have web service endpoints that aren’t secured and aren’t fully functional. In the next and final chapter of the book, you learn about web service security and OAuth, and you integrate Spring Security OAuth into the Customer Support application.