分类
Spring Security

详解Spring Security表达式

Intro to Spring Security Expressions

1. 简介

本文我们将与大家交流Spring Security表达式并给出相应的使用示例。

在学习如ACL等复杂的表达式以前,打下良好的Spring Security表达式的基础是非常有必要的!

本文是对Spring Security 表达式 – hasRole 简介的补充与延伸。

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 Boot中使用时,可以忽略指定版本号。

你可以点击此处获取最新的版本。

如果你没有使用Spring Boot,那么还需要手动添加以下spring-core 以及 spring-context 两个依赖;如果你使用了Spring Boot,Spring Boot将自动我们处理好这一切。

3. 配置

新建一个类并继承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 安全表达式(Web Security Expressions)

Sprring Security内置了以下安全表达式:

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

在正式介绍上述表达式以前,让我们建立两个测试用户:user以及admin:

public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password(passwordEncoder().encode("user"))
            .authorities("ROLE_USER")
            .and().withUser("admin").password(passwordEncoder().encode("admin"))
            .authorities("ROLE_ADMIN");
    }
}

当然了,你也可以使用XML来配置以上信息:

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

需要注意的是自Spring 5开始,需要为Spring Seuirty提从一个 PasswordEncoder 类型的bean(用于密码加密以及验证密码是否正确)。

接下来,依次对各个表达式进行介绍。

4.1 hasRole, hasAnyRole

我们可以使用上述表达式来对访问某些符合一定规则的URL授权。

比如:

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

以上代码实现了:当前登录用户访问任意以/auth/admin打头的地址时,必须拥有ADMIN角色;当当登录用户访问任意以/auth/ 打头的地址时,最少拥有USER或ADMIN角色的其中一个。

使用XML配置如下:

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

4.2 hasAuthority, hasAnyAuthority

Spring Security中的Roles和authorities其实差不多。

它们两个唯一的不同便是:使用Roles的时候,系统会自动为其添加ROLE_前缀(自Spring Security 4版本以后)。

所以hasAuthority(‘ROLE_ADMIN') 与 hasRole(‘ADMIN') 是等价的。

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

上述两处代码中,我们完全忽略了ROLE_前缀,这么写是完全没有问题的。

以及:

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

同时authorities表达式与roles表达式的区别也仅限于是否自动添加ROLE_前缀,所以初始测试用户的代码还可以这样写:

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("user").password(passwordEncoder().encode("user"))
            .authorities("ROLE_USER")
            .and().withUser("admin").password(passwordEncoder().encode("admin"))
            .authorities("ROLE_ADMIN");
    }

4.3. permitAll, denyAll

这两个表达式也非常的容易理解。它们可以规定禁止/允许任何用户访问某些URL。

比如:

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

上述代码的设置了允许任意用户(无论是登录用户还是未登录的匿名用户)访问以/打头的地址(比如首页)

同样的还可以设置禁止任何人访问系统设置(systemConfig):

...
.antMatchers("/systemConfig").denyAll()
...

使用XML配置的话,示例配置如下:

<http auto-config="true" use-expressions="true">
    <intercept-url access="permitAll" pattern="/*" /> 
    <intercept-url access="denyAll" pattern="/systemConfig" /> 
</http>

4.4. isAnonymous, isRememberMe, isAuthenticated, isFullyAuthenticated

本小节将围绕用户的登录状态展开。当我们想设置:匿名用户(使用匿名信息登录,比如游客1234)的可以访问关于我们(aboutMe)时,使用以下代码:

...
.antMatchers("/aboutMe").anonymous()
...

使用XML的话,配置如下:

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

如果我们想规定只有登录的用户才能够访问个人中心(personalCenter),则可以使用isAuthenticated() 方法:

...
.antMatchers("/personalCenter").authenticated()
...

使用XML的话,配置如下:

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

用户登录时可以通过记住我(RememberMe)以用使用用户名、密码登录两种方式。记住我的登录依赖为cookies,使用这种登录方式避免了每次登录都要输入的用户名、密码。如果你想了解更多关于记往我的登录方式,请点击这里

如果我们想规定允许通过记住我的方式登录站点的用户访问我的信息(message)时,则可以如下用如下代码:

...
.antMatchers("/message").rememberMe()
...

使用XML的话,配置如下:

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

还有一种场景:当用户使用一些特殊敏感的服务时,即使用户当前的登录状态是已登录(使用记住我的方式),我们仍然规定用户必须重新输入用户名、密码等登录信息重新登录一次。

 isFullyAuthenticated()方法便是为解决上述需要而存在的:

...
.antMatchers("/balance").fullyAuthenticated()
...

上述代码实现了只有通过用户名、密码的形式登录的用户才能够访问当前余额界面(balance) ---- 禁止未登录用户以及通过记住我的方式登录的用户。

使用XML的话,配置如下:

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

4.5. principal, authentication

principal、authentication表达式可以获取当前认证(匿名)用户的认证主体信息,还可以获取当前上下文中的认证对象。

比如可以使用principal来获取当前登录用户的email、头像等信息(只有登录用户提供的,都可以获取到)。

可以使用authentication获取完整的认证对象,包含该对象被赋予的授权信息。

Spring Security中获取用户的基本信息一文中对上述表达式进行了更详细的介绍。

4.6 hasPermission 接口

表达式旨在为Spring Security表达式与Spring Security的ACL系统架起一座桥梁,通过hasPermission我们可以自定义一些认证的逻辑。

比如在系统中设置一个查看系统信息的后门,仅仅当输入的token值符合一定的算法时才允许访问,否则不允许访问,则示例代码如下:

    @RequestMapping("systemInfo/{token}")
    @PreAuthorize("hasPermission(#token, 'isCorrect')")
    public String systemInfo(@PathVariable String token) {
        ...
    }

上述代码实现了只有当前用户拥有isEditor权限时,才可以执行此方法。

若使上述代码正常工作,还可以在应用上下文中配置一个PermissionEvaluator

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

上述配置信息中customInterfaceImplementation需要实现PermissionEvaluator接口。

使用JAVA配置的话,如下:

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

更多详情请参考GIHHUB的code demo。如果你想获取关于自定义表达式的更多内容的话,我们还为你准备了如何自定义安全表达式一文。

5. 总结

本文中我们对Spring Security中的表达式进行了全面的介绍,结合Spring Boot的使用方法以及相关的测试代码请参考github同步代码。希望能对你有所帮助。