1. 概述

本教程我们将通过实例学习 Spring Security 表达式用法。

在研究更复杂的实现之前(比如ACL),对security表达式有一个扎实的掌握很重要,使用得当,它们可以非常灵活和强大。

2. Maven 依赖

为了使用Spring Security,我们首先需要添加其依赖:

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

最新版本可以从 这里 找到。

注:此依赖项仅涉及 Spring Security; 一个完整的 web 应用还需要添加 spring-corespring-context

3. 配置

Java 配置方式。

我们继承 WebSecurityConfigurerAdapter,后面设置都是基于此类进行配置:

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

XML配置方式:

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

4. Web Security 表达式

security 提供了如下几种表达式:

  • hasRole, hasAnyRole
  • hasAuthority, hasAnyAuthority
  • permitAll, denyAll
  • isAnonymous, isRememberMe, isAuthenticated, isFullyAuthenticated
  • principal, authentication
  • hasPermission

下面我们进行一一讲解。

4.1. hasRole, hasAnyRole

这两个表达式负责定义某个URL或方法的访问控制权限。

示例:

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

本例中,我们指定具有 USERADDMIN 角色的用户可以访问 /auth/ 开头的所有链接。只有ADMIN 角色的用户可以访问 /auth/admin/ 开头的链接。

XML配置文件方式:

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

4.2. hasAuthority, hasAnyAuthority

Security 中 RoleAuthority 用法是类似的。

主要区别是,从 Spring Security 4 开始,与 role 相关的方法会自动加上 ‘*ROLE_*‘ 前缀。

所以 hasAuthority(‘ROLE_ADMIN')hasRole(‘ADMIN') 作用是一样的,因为后者会自动添加 ‘*ROLE_*‘ 前缀。

但使用Authority 的好处是我们完全不需要加ROLE_前缀

下面示例中,我们定义具有指定 Authority 的用户:

@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");
}

然后我们可以这样使用这些 Authority 表达式:

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

可以看到,这里我们一点儿都没有用到 Role,完全使用 Authority 实现。

另外从 Spring 5 开始,我们需要定义一个  PasswordEncoder bean:

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

最后,当然我们也可以使用XML配置实现同样的功能:

<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"/>

以及:

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

4.3. permitAll, denyAll

这两个注解用法也非常简单,用来允许或拒绝某个URL的访问。

示例:

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

使用此配置,我们将允许所有用户(包括匿名的和已登录)访问 ‘/' 开头的页面。

同样,我们也可以拒绝所有URL的访问:

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

上面的配置,也可以使用XML方式实现:

<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

本小节,我们关注与用户登录状态相关的表达式。

首先我们来实现,允许匿名用户访问:

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

对应的XML配置:

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

如果想要保护我们的网站,仅允许已登录的用户访问,则使用 isAuthenticated() 方法

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

XML 版本:

<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()
...

XML 版本:

<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()
...

XML 版本:

<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. 总结

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

本文所有示例源码存放在 GitHub 上。