Introduction to Spring Security Expressions

1. Introduction

In this tutorial we’ll focus on Spring Security Expressions, and of course on practical examples with these expressions.

Before looking at more complex implementations (such as ACL), it’s important to have a solid grasp on security expressions – as they can be quite flexible and powerful if used correctly.

2. Maven Dependencies

In order to use Spring Security, you need to include the following section in your pom.xml file:

<dependencies>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>5.2.3.RELEASE</version>
    </dependency>
</dependencies>

Latest version can be found here.

And a quick note – this dependency only covers Spring Security; don’t forget to add spring-core and spring-context for a full web application.

3. Configuration

First, let’s take a look at a Java configuration.

We’ll extend WebSecurityConfigurerAdapter – so that we have the option to hook into any of the extension points that base class offers:

@Configuration
@EnableAutoConfiguration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
    ...
}

We can of course do an XML configuration as well:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans ...>
    <global-method-security pre-post-annotations="enabled"/>
</beans:beans>

4. Web Security Expressions

Now, let’s start looking at the security expressions:

  • hasRolehasAnyRole
  • hasAuthorityhasAnyAuthority
  • permitAlldenyAll
  • isAnonymousisRememberMeisAuthenticatedisFullyAuthenticated
  • principalauthentication
  • hasPermission

And let’s now go over each of these in detail.

4.1. hasRole, hasAnyRole

These expressions are responsible for defining the access control or authorization to specific URLs or methods in your application.

Let’s look at the example:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    ...
    .antMatchers("/auth/admin/*").hasRole("ADMIN")
    .antMatchers("/auth/*").hasAnyRole("ADMIN","USER")
    ...
}

In this example we specify access to all links starting with /auth/ restricted to users that are logged in with role USER or role ADMIN. Moreover, to access links starting with /auth/admin/ we need to have ADMIN role in the system.

The same configuration might be achieved in XML file, by writing:

<http>
    <intercept-url pattern="/auth/admin/*" access="hasRole('ADMIN')"/>
    <intercept-url pattern="/auth/*" access="hasAnyRole('ADMIN','USER')"/>
</http>

4.2. hasAuthority, hasAnyAuthority

Roles and authorities are similar in Spring.

The main difference is that, roles have special semantics – starting with Spring Security 4, the ‘ROLE_‘ prefix is automatically added (if it’s not already there) by any role related method.

So hasAuthority(‘ROLE_ADMIN’) is similar to hasRole(‘ADMIN’) because the ‘ROLE_‘ prefix gets added automatically.

But the good thing about using authorities is that we don’t have to use the ROLE_ prefix at all.

Here’s a quick example where we’re defining users with specific authorities:

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
      .withUser("user1").password(encoder().encode("user1Pass"))
      .authorities("USER")
      .and().withUser("admin").password(encoder().encode("adminPass"))
      .authorities("ADMIN");
}

We can then of course use these authorities expressions:

@Override
protected void configure(final HttpSecurity http) throws Exception {
    ...
    .antMatchers("/auth/admin/*").hasAuthority("ADMIN")
    .antMatchers("/auth/*").hasAnyAuthority("ADMIN", "USER")
    ...
}

As we can see – we’re not mentioning roles at all here. Additionally, starting Spring 5, we need a PasswordEncoder bean:

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

Finally – we can of course achieve the same functionality using XML configuration as well:

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="user1" password="user1Pass" authorities="ROLE_USER"/>
            <user name="admin" password="adminPass" authorities="ROLE_ADMIN"/>
        </user-service>
    </authentication-provider>
</authentication-manager>
<bean name="passwordEncoder" 
  class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>

And:

<http>
    <intercept-url pattern="/auth/admin/*" access="hasAuthority('ADMIN')"/>
    <intercept-url pattern="/auth/*" access="hasAnyAuthority('ADMIN','USER')"/>
</http>

4.3. permitAll, denyAll

Those two annotations are also quite straightforward. We either may permit access to some URL in our service or we may deny access.

Let’s have a look at the example:

...
.antMatchers("/*").permitAll()
...

With this config, we will authorize all users (both anonymous and logged in) to access the page starting with ‘/’ (for example, our homepage).

We can also deny access to our entire URL space:

...
.antMatchers("/*").denyAll()
...

And again, the same config can be done with an XML config as well:

<http auto-config="true" use-expressions="true">
    <intercept-url access="permitAll" pattern="/*" /> <!-- Choose only one -->
    <intercept-url access="denyAll" pattern="/*" /> <!-- Choose only one -->
</http>

4.4. isAnonymous, isRememberMe, isAuthenticated, isFullyAuthenticated

In this subsection we focus on expressions related to login status of the user. Let’s start with user that didn’t log in to our page. By specifying following in Java config, we enable all unauthorized users to access our main page:

...
.antMatchers("/*").anonymous()
...

The same in XML config:

<http>
    <intercept-url pattern="/*" access="isAnonymous()"/>
</http>

If we want to secure the website that everybody who uses it will be required to login, we need to use isAuthenticated() method:

...
.antMatchers("/*").authenticated()
...

or XML version:

<http>
    <intercept-url pattern="/*" access="isAuthenticated()"/>
</http>

Moreover, we have two additional expressions, isRememberMe() and isFullyAuthenticated(). Through the use of cookies, Spring enables remember-me capabilities so there is no need to log into the system each time. You can read more about Remember Me here.

In order to give the access to users that were logged in only by remember me function, we may use this:

...
.antMatchers("/*").rememberMe()
...

or XML version:

<http>
    <intercept-url pattern="*" access="isRememberMe()"/>
</http>

Finally, some parts of our services require the user to be authenticated again even if the user is already logged in. For example, user wants to change settings or payment information; it’s of course good practice to ask for manual authentication in the more sensitive areas of the system.

In order to do that, we may specify isFullyAuthenticated(), which returns true if the user is not an anonymous or a remember-me user:

...
.antMatchers("/*").fullyAuthenticated()
...

or the XML version:

<http>
    <intercept-url pattern="*" access="isFullyAuthenticated()"/>
</http>

4.5. principal, authentication

These expressions allow accessing the principal object representing the current authorized (or anonymous) user and the current Authentication object from the SecurityContext, respectively.

We can, for example, use principal to load a user’s email, avatar, or any other data that is accessible in the logged in user.

And authentication provides information about the full Authentication object, along with its granted authorities.

Both are described in further detail in the following article: Retrieve User Information in Spring Security.

4.6. hasPermission APIs

This expression is documented and intended to bridge between the expression system and Spring Security’s ACL system, allowing us to specify authorization constraints on individual domain objects, based on abstract permissions.

Let’s have a look at an example. We have a service that allows cooperative writing articles, with a main editor, deciding which article proposed by other authors should be published.

In order to allow usage of such a service, we may create following methods with access control methods:

@PreAuthorize("hasPermission(#articleId, 'isEditor')")
public void acceptArticle(Article article) {
   …
}

Only authorized user can call this method, and also user needs to have permission isEditor in the service.

We also need to remember to explicitly configure a PermissionEvaluator in our application context:

<global-method-security pre-post-annotations="enabled">
    <expression-handler ref="expressionHandler"/>
</global-method-security>

<bean id="expressionHandler"
    class="org.springframework.security.access.expression
      .method.DefaultMethodSecurityExpressionHandler">
    <property name="permissionEvaluator" ref="customInterfaceImplementation"/>
</bean>

where customInterfaceImplementation will be the class that implements PermissionEvaluator.

Of course we can also do this with Java configuration as well:

@Override
protected MethodSecurityExpressionHandler expressionHandler() {
    DefaultMethodSecurityExpressionHandler expressionHandler = 
      new DefaultMethodSecurityExpressionHandler();
    expressionHandler.setPermissionEvaluator(new CustomInterfaceImplementation());
    return expressionHandler;
}

5. Conclusion

This tutorial is a comprehensive introduction and guide to Spring Security Expressions.

All examples discussed here are available on the GitHub project.

Related posts:

Apache Commons Collections BidiMap
Giới thiệu về Stream API trong Java 8
Mảng (Array) trong Java
Getting Started with Stream Processing with Spring Cloud Data Flow
Java Program to Implement Solovay Strassen Primality Test Algorithm
Tạo ứng dụng Java RESTful Client với thư viện Retrofit
StringBuilder vs StringBuffer in Java
Java Program to Check if an UnDirected Graph is a Tree or Not Using DFS
Các nguyên lý thiết kế hướng đối tượng – SOLID
Explain about URL and HTTPS protocol
Mix plain text and HTML content in a mail
A Guide to Spring Cloud Netflix – Hystrix
Spring Boot - Enabling Swagger2
Comparing Long Values in Java
Spring Security Login Page with React
Convert String to Byte Array and Reverse in Java
Java Program to Implement Sorted Vector
Java Program to Implement Red Black Tree
Java Program to Find the Mode in a Data Set
Spring Boot - Quick Start
Tìm hiểu cơ chế Lazy Evaluation của Stream trong Java 8
OAuth2 for a Spring REST API – Handle the Refresh Token in Angular
Java Program to Implement Suffix Tree
Java Program to Find Minimum Number of Edges to Cut to make the Graph Disconnected
Java Program to Implement Sieve Of Atkin
Returning Custom Status Codes from Spring Controllers
Java Web Services – Jersey JAX-RS – REST và sử dụng REST API testing tools với Postman
Java Program to Implement LinkedBlockingDeque API
Most commonly used String methods in Java
Introduction to Java Serialization
Java Program to Implement Hash Tables with Quadratic Probing
Introduction to Using FreeMarker in Spring MVC