1. 简介
在本教程中,我们将重点关注 Spring Security 表达式和使用这些表达式的实际示例。
在研究更复杂的实现(例如 ACL)之前,牢牢掌握安全表达式很重要,因为如果使用得当,它们会非常灵活和强大。
2. Maven依赖
为了使用 Spring Security,我们需要在pom.xml文件中包含以下部分:
<dependencies>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.6.0</version>
</dependency>
</dependencies>
最新版本可以在这里找到。
请注意,此依赖项仅涵盖 Spring Security;我们需要为完整的 Web 应用程序添加spring-core和spring-context。
3. 配置
首先我们来看一个Java配置:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ComponentScan("com.baeldung.security")
public class SecurityJavaConfig {
...
}
当然,我们也可以进行 XML 配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans ...>
<global-method-security pre-post-annotations="enabled"/>
</beans:beans>
4. Web安全表达式
现在让我们探索安全表达式:
- hasRole, hasAnyRole
- hasAuthority, hasAnyAuthority
- permitAll, denyAll
- isAnonymous, isRememberMe, isAuthenticated, isFullyAuthenticated
- principal, authentication
- hasPermission
4.1. hasRole, hasAnyRole
这些表达式负责定义对我们应用程序中特定 URL 和方法的访问控制或授权:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
.antMatchers("/auth/admin/*").hasRole("ADMIN")
.antMatchers("/auth/*").hasAnyRole("ADMIN","USER")
...
}
在上面的示例中,我们指定了对所有以/auth/*
开头的链接的访问权限,将它们限制为以角色USER或角色ADMIN 登录的用户。此外,要访问以/auth/admin/*
开头的链接,我们需要在系统中具有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中的角色和权限是类似的。
主要的区别是角色有特殊的语义。从Spring Security 4开始,ROLE_
前缀被任何与角色相关的方法自动添加(如果它还不存在的话)。
因此hasAuthority('ROLE_ADMIN')
与 hasRole('ADMIN')
类似,因为会自动添加ROLE_
前缀。
使用权限的好处是我们根本不必使用ROLE_
前缀。
这是一个定义具有特定权限的用户的简单示例:
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails admin = User.withUsername("admin")
.password(encoder().encode("adminPass"))
.roles("ADMIN")
.build();
UserDetails user = User.withUsername("user")
.password(encoder().encode("userPass"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin, user);
}
然后我们可以使用这些权限表达式:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
...
.antMatchers("/auth/admin/*").hasAuthority("ADMIN")
.antMatchers("/auth/*").hasAnyAuthority("ADMIN", "USER")
...
}
正如我们所看到的,我们在这里根本没有提到角色。
此外,从 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
在本小节中,我们将重点关注与用户登录状态相关的表达式。让我们从一个没有登录我们页面的用户开始。通过在 Java 配置中指定以下内容,我们将允许所有未经授权的用户访问我们的主页:
...
.antMatchers("/*").anonymous()
...
这与 XML 配置中的相同:
<http>
<intercept-url pattern="/*" access="isAnonymous()"/>
</http>
如果我们想保护网站的安全,让每个使用它的人都需要登录,我们需要使用*isAuthenticated()*方法:
...
.antMatchers("/*").authenticated()
...
或者我们可以使用 XML 版本:
<http>
<intercept-url pattern="/*" access="isAuthenticated()"/>
</http>
我们还有两个额外的表达式,isRememberMe()
和 isFullyAuthenticated()
。通过使用 cookie,Spring 启用了记住我的功能,因此无需每次都登录系统。我们可以在这里阅读更多关于记住我的信息。
为了向通过记住我功能登录的用户授予访问权限,我们可以使用:
...
.antMatchers("/*").rememberMe()
...
我们也可以使用 XML 版本:
<http>
<intercept-url pattern="*" access="isRememberMe()"/>
</http>
最后,我们服务的某些部分要求用户再次进行身份验证,即使用户已经登录也是如此。例如,假设用户想要更改设置或支付信息;在系统的更敏感区域要求手动身份验证是一种很好的做法。
为此,我们可以指定isFullyAuthenticated() ,如果用户不是匿名用户或记住我的用户,它会返回true :
...
.antMatchers("/*").fullyAuthenticated()
...
这是 XML 版本:
<http>
<intercept-url pattern="*" access="isFullyAuthenticated()"/>
</http>
4.5. principal, authentication
这些表达式允许我们分别从SecurityContext访问表示当前授权(或匿名)用户的主体对象和当前Authentication对象。
例如,我们可以使用principal加载用户的电子邮件、头像或登录用户可访问的任何其他数据。
身份验证提供有关完整身份验证对象及其授予权限的信息。
这两个表达式在文章Retrieve User Information in Spring Security中有更详细的描述。
4.6. hasPermission API
这个表达式是文档化的,旨在成为表达式系统和 Spring Security 的 ACL 系统之间的桥梁,允许我们基于抽象权限指定对各个域对象的授权约束。
让我们看一个例子。想象一下,我们有一个允许合作文章写作的服务,主编辑决定应该发表作者提议的哪些文章。
为了允许使用这样的服务,我们可以创建以下具有访问控制方法的方法:
@PreAuthorize("hasPermission(#article, 'isEditor')")
public void acceptArticle(Article article) {
…
}
只有授权用户才能调用此方法,并且他们需要在服务中具有isEditor权限。
我们还需要记住在我们的应用程序上下文中显式配置PermissionEvaluator,其中customInterfaceImplementation将是实现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>
当然,我们也可以使用 Java 配置来做到这一点:
@Override
protected MethodSecurityExpressionHandler expressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new CustomInterfaceImplementation());
return expressionHandler;
}
5.结论
本文是对 Spring Security Expressions 的全面介绍和指南。
GitHub 项目上提供了此处讨论的所有示例。