1. Overview
1.概述
We may wish to apply multiple security filters within the different paths of our Spring Boot applications.
我们可能希望在Spring Boot应用程序的不同路径中应用多个安全过滤器。
In this tutorial, we’ll take a look at two approaches to customizing our security – via the use of @EnableWebSecurity and @EnableGlobalMethodSecurity.
在本教程中,我们将看看两种定制安全的方法–通过使用@EnableWebSecurity和@EnableGlobalMethodSecurity。
To illustrate the differences, we’ll use a simple application that has some admin resources, authenticated user resources. We’ll also give it a section with public resources that we are happy for anyone to download.
为了说明这些区别,我们将使用一个简单的应用程序,它有一些管理资源,认证的用户资源。我们还将给它一个带有公共资源的部分,我们很乐意让任何人下载。
2. Spring Boot Security
2.Spring Boot的安全性
2.1. Maven Dependencies
2.1.Maven的依赖性
Whichever approach we take, we first need to add the spring boot starter for security:
无论我们采取哪种方法,我们首先需要添加spring boot starter以保证安全。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.2. Spring Boot Auto-Configuration
2.2.Spring Boot的自动配置
With Spring Security on the classpath, Spring Boot Security Auto-Configuration‘s WebSecurityEnablerConfiguration activates @EnableWebSecurity for us.
当Spring Security位于classpath上时,Spring Boot Security Auto-Configuration的WebSecurityEnablerConfiguration为我们激活了@EnableWebSecurity。
This applies Spring’s default security configuration to our application.
这将把Spring的默认安全配置应用于我们的应用程序。
Default security activates both HTTP security filters and the security filter chain and applies basic authentication to our endpoints.
默认安全激活HTTP安全过滤器和安全过滤器链,并对我们的端点应用基本认证。
3. Protecting Our Endpoints
3.保护我们的端点
For our first approach, let’s start by creating a MySecurityConfigurer class, making sure that we annotate it with @EnableWebSecurity.
对于我们的第一个方法,让我们开始创建一个MySecurityConfigurer类,确保我们用@EnableWebSecurity.来注释它。
@EnableWebSecurity
public class MySecurityConfigurer {
}
3.1. A Quick Look at SecurityFilterChain bean
3.1.对SecurityFilterChainBean的快速观察
First, let’s take a look at how to register a SecurityFilterChain bean:
首先,让我们看看如何注册一个SecurityFilterChainbean。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
http.formLogin();
http.httpBasic();
return http.build();
}
Here we see that any request we receive is authenticated, and we have a basic form login to prompt for credentials.
在这里,我们看到,我们收到的任何请求都是经过认证的,我们有一个基本的表单登录来提示凭证。
When we want to use the HttpSecurity DSL, we write this as:
当我们想使用HttpSecurity DSL时,我们这样写。
http.authorizeRequests().anyRequest().authenticated()
.and().formLogin()
.and().httpBasic()
3.2. Require Users to Have an Appropriate Role
3.2.要求用户有一个适当的角色
Now let’s configure our security to allow only users with an ADMIN role to access our /admin endpoint. We’ll also allow only users with a USER role to access our /protected endpoint.
现在让我们配置我们的安全,只允许具有ADMIN角色的用户访问我们的/admin端点。我们还将允许只有具有USER角色的用户访问我们的/protected端点。
We achieve this by creating a SecurityFilterChain bean:
我们通过创建一个SecurityFilterChainbean来实现这一目标。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/protected/**")
.hasRole("USER");
return http.build();
}
3.3. Relax Security for Public Resources
3.3.放松对公共资源的保障
We don’t need authentication for our public /hello resources, so we’ll configure WebSecurity to do nothing for them.
我们的公共/hello资源不需要认证,所以我们将配置WebSecurity对其不做任何处理。
Just like before, let’s register a WebSecurityCustomizer bean:
就像以前一样,让我们注册一个WebSecurityCustomizer Bean。
@Bean
public WebSecurityCustomizer ignoreResources() {
return (webSecurity) -> webSecurity
.ignoring()
.antMatchers("/hello/*");
}
4. Protect Our Endpoints Using Annotations
4.使用注解保护我们的端点
To apply security using an annotation-driven approach, we can use @EnableGlobalMethodSecurity.
为了使用注解驱动的方法应用安全,我们可以使用@EnableGlobalMethodSecurity.。
4.1. Require Users to Have an Appropriate Role Using Security Annotations
4.1.使用安全注释要求用户拥有适当的角色
Now let’s use method annotations to configure our security to allow only ADMIN users to access our /admin endpoint and our USER users to access our /protected endpoint.
现在让我们使用方法注解来配置我们的安全,只允许ADMIN用户访问我们的/admin端点,以及USER用户访问我们的/protected端点。
Let’s enable JSR-250 annotations by setting jsr250Enabled=true in our EnableGlobalMethodSecurity annotation:
让我们通过在EnableGlobalMethodSecurity注解中设置jsr250Enabled=true来启用JSR-250>注解。
@EnableGlobalMethodSecurity(jsr250Enabled = true)
@Controller
public class AnnotationSecuredController {
@RolesAllowed("ADMIN")
@RequestMapping("/admin")
public String adminHello() {
return "Hello Admin";
}
@RolesAllowed("USER")
@RequestMapping("/protected")
public String jsr250Hello() {
return "Hello Jsr250";
}
}
4.2. Enforce All Public Methods Have Security
4.2.强制执行所有公共方法的安全性
When we use annotations as our way of implementing security, we may forget to annotate a method. This would inadvertently create a security hole.
当我们使用注解作为我们实现安全的方式时,我们可能会忘记注解一个方法。这将在不经意间造成一个安全漏洞。
To safeguard against this, we should deny access to all methods that don’t have authorization annotations.
为了防止这种情况,我们应该拒绝对所有没有授权注释的方法的访问。
4.3. Allow Access to Public Resources
4.3.允许访问公共资源
Spring’s default security enforces authentication to all our endpoints, whether we add role-based security or not.
无论我们是否添加了基于角色的安全,Spring的默认安全都会对我们所有的端点实施认证。
Although our previous example applies security to our /admin and /protected endpoints, we still want to allow access to file-based resources in /hello.
尽管我们前面的例子将安全性应用于/admin和/protected端点,但我们仍然希望允许访问/hello中基于文件的资源。
Whilst we could extend WebSecurityAdapter again, Spring provides us a simpler alternative.
虽然我们可以再次扩展WebSecurityAdapter,但Spring为我们提供了一个更简单的选择。
Having protected our methods with annotations, we can now add the WebSecurityCustomizer to open the /hello/* resources:
在用注解保护了我们的方法后,我们现在可以添加WebSecurityCustomizer来打开/hello/*资源。
public class MyPublicPermitter implements WebSecurityCustomizer {
public void customize(WebSecurity webSecurity) {
webSecurity.ignoring()
.antMatchers("/hello/*");
}
}
Alternatively, we can simply create a bean that implements it inside our configuration class:
另外,我们可以简单地在我们的配置类中创建一个实现它的bean。
@Configuration
public class MyWebConfig {
@Bean
public WebSecurityCustomizer ignoreResources() {
return (webSecurity) -> webSecurity
.ignoring()
.antMatchers("/hello/*");
}
}
When Spring Security initializes, it invokes any WebSecurityCustomizers it finds, including ours.
当Spring Security初始化时,它会调用它发现的任何WebSecurityCustomizers,包括我们的。
5. Testing Our Security
5.测试我们的安全
Now that we have configured our security, we should check that it behaves as we intended.
现在我们已经配置了我们的安全,我们应该检查它的行为是否符合我们的意图。
Depending on the approach we chose for our security, we have one or two options for our automated tests. We can either send web requests to our application or invoke our controller methods directly.
根据我们为安全所选择的方法,我们的自动测试有一到两个选择。我们可以向我们的应用程序发送网络请求或直接调用我们的控制器方法。
5.1. Testing via Web Requests
5.1.通过网络请求进行测试
For the first option, we’ll create a @SpringBootTest test class with a @TestRestTemplate:
对于第一个选项,我们将创建一个带有@SpringBootTest测试类的@TestRestTemplate。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class WebSecuritySpringBootIntegrationTest {
@Autowired
private TestRestTemplate template;
}
Now, let’s add a test to make sure our public resources are available:
现在,让我们添加一个测试,以确保我们的公共资源是可用的。
@Test
public void givenPublicResource_whenGetViaWeb_thenOk() {
ResponseEntity<String> result = template.getForEntity("/hello/baeldung.txt", String.class);
assertEquals("Hello From Baeldung", result.getBody());
}
We can also see what happens when we try to access one of our protected resources:
我们还可以看到,当我们试图访问我们的一个受保护的资源时会发生什么。
@Test
public void whenGetProtectedViaWeb_thenForbidden() {
ResponseEntity<String> result = template.getForEntity("/protected", String.class);
assertEquals(HttpStatus.FORBIDDEN, result.getStatusCode());
}
Here we get a FORBIDDEN response, as our anonymous request does not have the required role.
这里我们得到一个FORBIDDEN响应,因为我们的匿名请求没有必要的角色。
So, we can use this method to test our secured application, whichever security approach we choose.
因此,我们可以使用这种方法来测试我们的安全应用程序,无论我们选择哪种安全方法。
5.2. Testing via Auto-Wiring and Annotations
5.2.通过自动布线和注解进行测试</span
Now let’s look at our second option. Let’s set up a @SpringBootTest and autowire our AnnotationSecuredController:
现在让我们看看我们的第二个选择。让我们设置一个@SpringBootTest并自动连接我们的AnnotationSecuredController:。
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = RANDOM_PORT)
public class GlobalMethodSpringBootIntegrationTest {
@Autowired
private AnnotationSecuredController api;
}
Let’s start by testing our publicly accessible method using @WithAnonymousUser:
让我们先用@WithAnonymousUser来测试我们的公开访问方法。
@Test
@WithAnonymousUser
public void givenAnonymousUser_whenPublic_thenOk() {
assertThat(api.publicHello()).isEqualTo(HELLO_PUBLIC);
}
Now that we’ve accessed our public resource, let’s use @WithMockUser annotations to access our protected methods.
现在我们已经访问了我们的公共资源,让我们使用@WithMockUser注解来访问我们的保护方法。
First, let’s test our JSR-250 protected method with a user that has the “USER” role:
首先,让我们用一个拥有 “USER “角色的用户来测试我们的JSR-250保护方法。
@WithMockUser(username="baeldung", roles = "USER")
@Test
public void givenUserWithRole_whenJsr250_thenOk() {
assertThat(api.jsr250Hello()).isEqualTo("Hello Jsr250");
}
And now, let’s attempt to access the same method when our user doesn’t have the right role:
现在,让我们尝试在我们的用户没有正确的角色时访问同一个方法。
@WithMockUser(username="baeldung", roles = "NOT-USER")
@Test(expected = AccessDeniedException.class)
public void givenWrongRole_whenJsr250_thenAccessDenied() {
api.jsr250Hello();
}
Our request was intercepted by Spring Security, and an AccessDeniedException was thrown.
我们的请求被Spring Security拦截,并抛出了一个AccessDeniedException。
We can only use this approach when we choose annotation-based security.
我们只有在选择基于注释的安全时才能使用这种方法。
6. Annotation Cautions
6.注释注意事项
When we choose the annotation-based approach, there are some important points to note.
当我们选择基于注释的方法时,有一些要点需要注意。
Our annotated security only gets applied when we enter a class via a public method.
当我们通过公共方法进入一个类时,我们的注释安全才会被应用。
6.1. Indirect Method Invocation
6.1.间接方法调用
Earlier, when we called an annotated method, we saw our security successfully applied. However, now let’s create a public method in the same class but without a security annotation. We’ll make it call our annotated jsr250Hello method:
早些时候,当我们调用一个有注解的方法时,我们看到我们的安全性被成功应用。然而,现在让我们在同一个类中创建一个公共方法,但没有安全注释。我们将让它调用我们的注解jsr250Hello方法。
@GetMapping("/indirect")
public String indirectHello() {
return jsr250Hello();
}
Now let’s invoke our “/indirect” endpoint just using anonymous access:
现在让我们使用匿名访问来调用我们的”/indirect “端点。
@Test
@WithAnonymousUser
public void givenAnonymousUser_whenIndirectCall_thenNoSecurity() {
assertThat(api.indirectHello()).isEqualTo(HELLO_JSR_250);
}
Our test passes as our ‘secured’ method was invoked without triggering any security. In other words, no security is applied to the internal calls within the same class.
我们的测试通过了,因为我们的 “安全 “方法被调用而没有触发任何安全。换句话说,没有对同一类中的内部调用进行安全处理。
6.2. Indirect Method Invocation to a Different Class
6.2.对不同类的间接方法调用
Now let’s see what happens when our unprotected method invokes an annotated method on a different class.
现在让我们看看当我们的非保护方法调用不同类上的注解方法时会发生什么。
First, let’s create a DifferentClass with an annotated method, differentJsr250Hello:
首先,让我们创建一个DifferentClass,它有一个注解的方法,differentJsr250Hello。
@Component
public class DifferentClass {
@RolesAllowed("USER")
public String differentJsr250Hello() {
return "Hello Jsr250";
}
}
Now, let’s autowire DifferentClass into our controller and add an unprotected differentClassHello public method to call it.
现在,让我们把DifferentClass自动连接到控制器中,并添加一个不受保护的differentClassHello公共方法来调用它。
@Autowired
DifferentClass differentClass;
@GetMapping("/differentclass")
public String differentClassHello() {
return differentClass.differentJsr250Hello();
}
And finally, let’s test the invocation and see that our security is enforced:
最后,让我们测试一下这个调用,看看我们的安全是否得到了执行。
@Test(expected = AccessDeniedException.class)
@WithAnonymousUser
public void givenAnonymousUser_whenIndirectToDifferentClass_thenAccessDenied() {
api.differentClassHello();
}
So, we see that although our security annotations won’t be respected when we call another method in the same class when we call an annotated method in a different class, then they are respected.
因此,我们看到,虽然我们的安全注释不会被尊重,当我们调用同一个类中的另一个方法时,当我们调用不同类中的注释方法时,那么它们会被尊重。
6.3. A Final Note of Caution
6.3.最后的谨慎提示
We should make sure that we configure our @EnableGlobalMethodSecurity correctly. If we don’t, then despite all of our security annotations, they could have no effect at all.
我们应该确保正确配置我们的@EnableGlobalMethodSecurity。如果我们不这样做,那么尽管我们有所有的安全注释,它们可能完全没有效果。
For example, if we’re using JSR-250 annotations but instead of jsr250Enabled=true we specify prePostEnabled=true, then our JSR-250 annotations will do nothing!
例如,如果我们使用JSR-250注解,但我们没有指定jsr250Enabled=true,而是指定prePostEnabled=true,那么我们的JSR-250注解就不会做任何事情
@EnableGlobalMethodSecurity(prePostEnabled = true)
We can, of course, declare that we will use more than one annotation type by adding them both to our @EnableGlobalMethodSecurity annotation:
当然,我们可以声明我们将使用一个以上的注解类型,把它们都加到我们的@EnableGlobalMethodSecurity注解中。
@EnableGlobalMethodSecurity(jsr250Enabled = true, prePostEnabled = true)
7. When We Need More
7.当我们需要更多的时候
Compared to JSR-250, we can also use Spring Method Security. This includes using the more powerful Spring Security Expression Language (SpEL) for more advanced authorization scenarios. We can enable SpEL on our EnableGlobalMethodSecurity annotation by setting prePostEnabled=true:
与JSR-250相比,我们还可以使用Spring Method Security。这包括使用更强大的Spring安全表达式语言(SpEL)来实现更高级的授权场景。我们可以通过设置EnableGlobalMethodSecurity注解启用SpEL:prePostEnabled=true:。
@EnableGlobalMethodSecurity(prePostEnabled = true)
In addition, when we want to enforce security based on whether a domain object is owned by the user, we can use Spring Security Access Control Lists.
此外,当我们想根据用户是否拥有某个域对象来执行安全性时,我们可以使用Spring Security Access Control Lists。
We should also note that when we write reactive applications, we use @EnableWebFluxSecurity and @EnableReactiveMethodSecurity instead.
我们还应该注意,当我们编写反应式应用程序时,我们使用@EnableWebFluxSecurity和@EnableReactiveMethodSecurity代替。
8. Conclusion
8.结语
In this tutorial, we first looked at how to secure our application using a centralized security rules approach with @EnableWebSecurity.
在本教程中,我们首先研究了如何利用@EnableWebSecurity.的集中式安全规则方法来保护我们的应用程序。
Then, we built upon this by configuring our security to put those rules nearer to the code that they affect. We did this by using @EnableGlobalMethodSecurity and annotating the methods we wanted to secure.
然后,我们在此基础上配置我们的安全性,使这些规则更接近于它们所影响的代码。我们通过使用@EnableGlobalMethodSecurity和注释我们想要保护的方法来做到这一点。
Finally, we introduced an alternative way of relaxing security for public resources that don’t need it.
最后,我们介绍了一种为不需要安全的公共资源放松安全的替代方式。
As always, the example code is available over on GitHub.
像往常一样,示例代码可在GitHub上获得。