Expression-based Access with FreeMarker

05 April 2015

Spring Security 3.0 introduced the ability to use Spring EL expressions as an authorisation mechanism. Expression-based access control is built on the same architecture but allows complicated boolean logic to be encapsulated in a single expression. This comes handy when you want to secure a Controller method or even the entire Controller. JSP has native support for this feature using

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

FreeMarker don't so you need to do more than just importing a tag library.

I followed this article here tech talk, very simple yet precise!

Add JspTagLibs

Consider the first line of the snippet.

<#assign security=JspTaglibs["/WEB-INF/security.tld"]/>
<#include "/user/logout.ftl"/>
<#macro navigation>
	<div class="masthead clearfix">
    	<div class="inner">
      		<h3 class="masthead-brand"><@spring.message "prototype"/></h3>
      		<nav>
        		<ul class="nav masthead-nav">
					<#if Session["SPRING_SECURITY_CONTEXT"]?exists>
						<li><@logout/></li>
          				<li><a href="home"><@spring.message "home"/></a></li> 				
						<#assign authorities = Session["SPRING_SECURITY_CONTEXT"].authentication.authorities />
						<#if authorities?seq_contains("ROLE_ADMIN")>
							<li><a href="listUsers"><@spring.message "users"/></a></li>
						</#if>
	          		</#if>
        		</ul>
      		</nav>
    	</div>
  	</div>
</#macro>  

It's a shame I cannot use <security:authorize access="hasRole('ROLE_ADMIN')"> but the end result is the same, you will gain authorisation to specified pages.

Copy security.tld

Check your POM

Make sure you have spring-security-taglibs dependency.

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
	<version>${spring.security.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>${spring.security.version}</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
	<version>${spring.security.version}</version>
</dependency>

Check your Spring Security @Configuration

If you are extending WebSecurityConfigurerAdapter and overriding configure to create your own AuthenticationManagerBuilder, you will have BeanCreationException.

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'methodSecurityInterceptor' defined in class path resource [org/springframework/security/config/annotation/method/configuration/GlobalMethodSecurityConfiguration.class]: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: An AuthenticationManager is required

The solution is documented in Spring Security docs. You can check my own configuration on Github.

public AuthenticationManager authenticationManagerBean() throws Exception

Override this method to expose the AuthenticationManager from configure(AuthenticationManagerBuilder) to be exposed as a Bean. For example:

 @Bean(name name="myAuthenticationManager")
 @Override
 public AuthenticationManager authenticationManagerBean() throws Exception {
     return super.authenticationManagerBean();
 }

Returns:
    the AuthenticationManager
Throws:
    Exception

Finally, annotate your @Controller

@PreAuthorize("hasRole('ROLE_ADMIN')")
@RequestMapping(value = "listUsers", method = RequestMethod.GET)
public ModelAndView listUsers() {

	ModelAndView mav = new ModelAndView();
	mav.addObject("users", userService.list());
	mav.setViewName(ControllerConstant.USER_LIST);

	return mav;
}

Spring Security 3.2.5 Guide
Spring EL Documentation