Spring Security Expressions provides a powerful expression language to define fine-grained access control rules based on various factors such as user roles, authorities, authentication details, and more. In this article, we’ll explore practical examples of using Spring Security expressions, configurations, and working code examples.
NOTE: WebSecurityConfigurerAdapter is not available in Spring Security 6.x. Read the article: Spring Security without the WebSecurityConfigurerAdapter
1. Setting up Spring Security Dependencies
Before we dive into the examples, let’s set up the required dependencies for Spring Security. If you’re using Spring Boot, you can simply add the spring-boot-starter-security
dependency to your pom.xml
file:
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
...
If you’re using the Spring Security Web module directly, you’ll need to include the spring-security-web
dependency:
...
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>6.2.4</version> <!-- latest version -->
</dependency>
...
2. Configuring Spring Security
Next, let’s configure Spring Security in our application. Here’s an example SecurityConfig
class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableMethodSecurity(prePostEnabled = true)
@EnableWebSecurity //if required
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("password")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin(Customizer.withDefaults());
return http.build();
}
}
In this configuration, we’re defining two in-memory users (user
and admin
) with different roles. We’re also enabling method-level security with @EnableMethodSecurity(prePostEnabled = true)
, which allows us to use Spring Security expressions in our method annotations.
XML configuration: if your project is still using XML based Configurations
<?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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<global-method-security pre-post-annotations="enabled" />
<!-- Other security configurations -->
</beans:beans>
3. Web Security Expressions
Spring Security provides a set of built-in expressions that you can use to define access control rules in your web application. Here are some practical examples of Spring Security Expressions:
3.1. Role-based Access Control – hasRole
and hasAnyRole
You can use the hasRole
and hasAnyRole
expressions to control access based on user roles.
Role-based access control is a widely used approach in which permissions and access rights are assigned to roles, and users are assigned one or more roles. The roles determine the set of actions and resources a user is allowed to access or perform within the application.
In Spring Security, roles are typically prefixed with the string “ROLE_” (e.g., “ROLE_ADMIN”, “ROLE_USER”). This is a convention, not a requirement, but it helps distinguish roles from other types of authorities.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
.requestMatchers("/auth/admin/**").hasRole("ADMIN")
.requestMatchers("/auth/**").hasAnyRole("ADMIN", "USER")
.and()
.formLogin(Customizer.withDefaults())
.build();
}
In the example above:
- URLs starting with
/auth/admin/
require theADMIN
role. - URLs starting with
/auth/
are accessible to users with either theADMIN
orUSER
role.
XML Config: The equivalent 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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
<!-- Define security rules -->
<http>
<intercept-url pattern="/auth/admin/**" access="hasRole('ADMIN')" />
<intercept-url pattern="/auth/**" access="hasAnyRole('ADMIN','USER')" />
<!-- Other URL patterns and access rules -->
</http>
<!-- Authentication configuration -->
<authentication-manager>
<authentication-provider>
<user-service>
<user name="Namo" password="namo123" authorities="ROLE_ADMIN" />
<user name="Amit" password="amit123" authorities="ROLE_USER" />
<!-- Other users and roles -->
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
In the above XML configuration:
- The
<http>
section defines URL patterns and their corresponding access rules.- URLs starting with
/auth/admin/
require theADMIN
role. - URLs starting with
/auth/
are accessible to users with either theADMIN
orUSER
role.
- URLs starting with
- The
<authentication-manager>
section configures user authentication.- We define two users (
Namo
andAmit
) with their passwords and associated roles.
- We define two users (
3.2. Authority-based Access Control – hasAuthority
and hasAnyAuthority
Similar to roles, you can use the hasAuthority
and hasAnyAuthority
expressions to control access based on user authorities.
Authority-based access control is a more granular approach where permissions and access rights are assigned directly to authorities, rather than roles. Authorities represent specific privileges or capabilities within the application, such as “READ_PERMISSION”, “WRITE_PERMISSION”, or “DELETE_PERMISSION”.
You do not have to use ROLE_ when using Authority based Access Control.
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
//extra stuffs
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/read/**").hasAuthority("READ_PERMISSION")
.requestMatchers("/write/**").hasAnyAuthority("WRITE_PERMISSION", "ADMIN")
.anyRequest().authenticated()
.and()
.formLogin(form -> form.loginPage("/login").permitAll())
.logout(logout -> logout.logoutSuccessUrl("/my/success/endpoint").permitAll());
return http.build();
}
}
In this example, the /read/**
URLs are accessible to users with the READ_PERMISSION
authority, while the /write/**
URLs are accessible to users with either the WRITE_PERMISSION
or ADMIN
authority.
XML based config:
<beans:beans xmlns="http://www.springframework.org/schema/security"
......>
<http>
<intercept-url pattern="/read/**" access="hasAuthority('READ_PERMISSION')" />
<intercept-url pattern="/write/**" access="hasAnyAuthority('WRITE_PERMISSION', 'ADMIN')" />
<!-- Other URL patterns and access rules -->
<form-login login-page="/login" />
<logout />
</http>
<!-- Other security configurations (authentication, user details, etc.) -->
</beans:beans>
3.3. Authentication-based Access Control
Spring Security also provides expressions to control access based on the user’s authentication state .Authentication-based access control in Spring Security is used to control access based on the user’s authentication state. This type of access control is often used in combination with role-based or authority-based access control to provide an additional layer of security.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/public/**").permitAll()
.requestMatchers("/anonymous/**").anonymous()
.requestMatchers("/authenticated/**").authenticated()
.requestMatchers("/remember-me/**").rememberMe()
.anyRequest().fullyAuthenticated();
return http.build();
}
In this example:
- Public resource: The
/public/**
URLs are accessible to everyone (both authenticated and anonymous users) - Annonymus access: The
/anonymous/**
URLs are accessible only to anonymous users - Authenticated: The
/authenticated/**
URLs are accessible to both authenticated and fully authenticated users - Remember me: The
/remember-me/**
–isRememberMe
expression allows access to resources for users who are authenticated through the “remember-me” feature. - All other URLs require full authentication (e.g., not remember-me authentication)
3.4. Principal and AuthenticationPrincipal Expressions
Spring Security also provides expressions to access the current Principal
and Authentication
objects.
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.security.Principal;
@RequestMapping("/user")
@RestController
public class UserController {
@GetMapping("/me")
public String getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
return "Hello, " + userDetails.getUsername();
}
@GetMapping(value = "/username")
public String currentUserName(Principal principal) {
return principal.getName();
}
}
In this example, we’re using the @AuthenticationPrincipal
annotation to inject the current UserDetails
object into the controller method. We can then access the username and other user details directly. For all posible ways to inject and access the User information in Spring, please refer the related article provided below.
Reference: Retrieving User Information in Spring Security
3.5. PermitAll and denyAll expressions
PermitAll: The permitAll
expression allows unrestricted access to specific endpoints or resources without requiring authentication or authorization. It is typically used for defining public endpoints that should be accessible to all users, including those who are not authenticated. Suppose we want to allow unrestricted access to the homepage (/
) and a public API endpoint (/api/public
). We can configure it as follows:
DenyAll: The denyAll
expression denies access to specific endpoints or resources for all users, regardless of their authentication status. It is rarely used but can be helpful in specific scenarios. Suppose we want to deny access to a sensitive admin panel (/admin
). We can configure it as follows:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/", "/api/public").permitAll()
.requestMatchers("/admin").denyAll()
.anyRequest().authenticated();
//add login and logout from above
return http.build();
}
}
<http>
<intercept-url pattern="/" access="permitAll" />
<intercept-url pattern="/api/public" access="permitAll" />
<intercept-url pattern="/admin" access="denyAll" />
<!-- Other URL patterns and access rules -->
<form-login />
<logout />
</http>
3.6. isAnonymous, isRememberMe, isAuthenticated, isFullyAuthenticated
1. isAnonymous
Expression: The isAnonymous()
expression checks whether the current principal is an anonymous user (i.e., not authenticated). It’s useful for allowing unrestricted access to certain endpoints or resources.
2. isRememberMe
Expression: The isRememberMe()
expression checks whether the current principal is a remember-me user. Remember-me authentication allows users to be automatically logged in based on a persistent token (e.g., a cookie).
3. isAuthenticated and isFullyAuthenticated Expressions: These expressions check the login status of the user:
isAuthenticated()
returns true if the user is not anonymous (logged in).isFullyAuthenticated()
returns true if the user is not anonymous or a remember-me user.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.requestMatchers("/public/**").anonymous()
.requestMatchers("/dashboard/**").rememberMe()
.requestMatchers("/user/**").authenticated()
.requestMatchers("/admin/**").fullyAuthenticated()
.anyRequest().authenticated()
.and()
.formLogin(form -> form.loginPage("/login").permitAll())
.logout(logout -> logout.logoutSuccessUrl("/my/success/endpoint").permitAll());
return http.build();
}
}
- The
antMatchers("/public/**").anonymous()
line allows access to URLs starting with/public/
for anonymous users. - The
antMatchers("/dashboard/**").rememberMe()
line allows access to URLs starting with/dashboard/
for remember-me users. - The
antMatchers("/user/**").authenticated()
line allows access to URLs starting with/user/
for authenticated users (not anonymous). - The
antMatchers("/admin/**").fullyAuthenticated()
line requires fully authenticated users (not anonymous or remember-me) for URLs starting with/admin/
. - All other requests require authentication.
Alternatively, use can also use Java based config as shown below.
<http>
<intercept-url pattern="/public/**" access="isAnonymous()" />
<intercept-url pattern="/dashboard/**" access="isRememberMe()" />
<intercept-url pattern="/user/**" access="isAuthenticated()" />
<intercept-url pattern="/admin/**" access="isFullyAuthenticated()" />
<!-- Other URL patterns and access rules -->
<form-login />
<logout />
</http>
4. hasPermission Expression in Spring Security
In Spring Security, the hasPermission
expression is a powerful mechanism that allows for fine-grained access control based on permissions associated with domain objects. This expression is commonly used in conjunction with Access Control Lists (ACLs) to enforce access policies at the object level.
Working Principle:
The hasPermission
expression evaluates whether the authenticated user has a specific permission on a given domain object. It takes two arguments:
- Target Domain Object: The object for which permission is being checked.
- Permission Expression: A string representing the permission being checked.
Usage:
The hasPermission
expression is typically used within method-level security annotations, such as @PreAuthorize
or @PostAuthorize
, to restrict access to methods based on the permissions associated with domain objects. Here’s an example:
@PreAuthorize("hasPermission(#book, 'read')")
public BookDto getBookDetails(Book book) {
// Method implementation
}
In this example, the getBookDetails
method is annotated with @PreAuthorize
, and the hasPermission
expression is used to ensure that the authenticated user has the ‘read’ permission on the specified Book
object. If the user doesn’t have the required permission, access to the method will be denied.
Configuration:
To use the hasPermission
expression with ACLs, you need to configure Spring Security to integrate with your data source and define the necessary permissions for your domain objects. This typically involves setting up a PermissionEvaluator
bean that evaluates permissions based on your application’s business logic.
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private PermissionEvaluator permissionEvaluator;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
}
Custom Permission Evaluators:
You can also implement custom PermissionEvaluator
classes to define how permissions are evaluated for specific domain objects and permissions. This allows you to customize the access control logic based on your application’s requirements.
@Component
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
// Custom permission evaluation logic
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// Custom permission evaluation logic
}
}
The hasPermission
expression in Spring Security provides a flexible and extensible mechanism for implementing object-level access control based on permissions associated with domain objects. By using hasPermission
, you can enforce fine-grained access policies in your application, ensuring that users have appropriate permissions to perform specific actions on specific objects.
Conclusion
Spring Security expressions provide a powerful and flexible way to define access control rules in your application. With role-based access control, authority-based access control, authentication-based access control, and Access Control Lists, you can implement fine-grained authorization logic based on various factors, such as user roles, authorities, authentication details, and domain objects.
Remember to always follow security best practices and regularly review and update your security configurations to ensure the ongoing protection of your application and its data.