禁用 @PreAuthorize 注解在运行配置文件时。

huangapple go评论120阅读模式
英文:

Disable @PreAuthorize annotation when running a profile

问题

我已为Spring Boot应用程序编写了使用Cucumber测试框架的API功能测试用例。这些测试在启动Spring Boot应用程序时以专用配置文件配置的配置文件中执行,使用以下命令执行:

  1. mvn spring-boot:start -Dspring-boot.run.profiles=test -Dspring-boot.run.arguments="--spring.config.name=application-test --spring.config.location=./src/test/resources/application-test.yaml" test spring-boot:stop -DskipTests=false

在Spring应用程序启动后,测试用例会发出API调用,并在测试中验证响应。

我已使用以下自定义WebSecurityConfigurerAdapter禁用了测试用例中的安全性:

  1. @TestConfiguration
  2. @Order(1)
  3. @Profile("test")
  4. public class TestSecurityConfiguration extends WebSecurityConfigurerAdapter {
  5. @Override
  6. protected void configure(HttpSecurity http) throws Exception {
  7. http.csrf().disable()
  8. .authorizeRequests()
  9. .anyRequest()
  10. .permitAll();
  11. }
  12. }

安全性已被禁用。但是,在某些控制器中,角色受到@PreAuthorize注释的检查。对带有@PreAuthorize注释的控制器的调用失败,并显示以下错误:

  1. {
  2. "timestamp": 1689838624242,
  3. "errorType": "UNKNOWN",
  4. "errors": [
  5. "Access is denied"
  6. ]
  7. }

已尝试多种选项来禁用特定配置文件或Cucumber测试用例中的@PreAuthorize检查,但没有一种方法有效。感谢任何有关如何在所选配置文件或Cucumber测试用例中禁用@PreAuthorize检查的想法。

已尝试的选项:

  1. 在测试中设置@EnableGlobalMethodSecurity(prePostEnabled = true)。但未奏效。
  2. 参考了这个SO答案。我的Spring Boot应用程序受到Oauth2保护,但我在测试中禁用了它,因此发送基本身份验证凭据将无法正常工作。
英文:

I have written API functional test cases for Spring boot Application using cucumber test framework. The tests are executed after starting the spring boot application in a profile with dedicated configurations for test cases, using the following command:

  1. mvn spring-boot:start -Dspring-boot.run.profiles=test -Dspring-boot.run.arguments="--spring.config.name=application-test --spring.config.location=./src/test/resources/application-test.yaml" test spring-boot:stop -DskipTests=false

The API call is made from test cases once the spring application is started and the responses are validated in the tests.

I have disabled the security in test cases using the custom WebSecurityConfigurerAdapter given below:

  1. @TestConfiguration
  2. @Order(1)
  3. @Profile("test")
  4. public class TestSecurityConfiguration extends WebSecurityConfigurerAdapter {
  5. @Override
  6. protected void configure(HttpSecurity http) throws Exception {
  7. http.csrf().disable()
  8. .authorizeRequests()
  9. .anyRequest()
  10. .permitAll();
  11. }
  12. }

The security is disabled. But there are some controllers where the role is checked with @PreAuthorize annotation. The calls made to the controller with @PreAuthorize annotation is failed with the following error:

  1. {
  2. "timestamp": 1689838624242,
  3. "errorType": "UNKNOWN",
  4. "errors": [
  5. "Access is denied"
  6. ]
  7. }

Tried several options to disabled to PreAuthorize field to specific profile but none of them worked. Appreciate any idea on how to disable the @PreAuthorize check for a selected profile or in test cases with Cucumber.

The options already tried:

  1. set @EnableGlobalMethodSecurity(prePostEnabled = true) in test. It didn't work
  2. Referred to this SO answer. My spring boot application is secured with Oauth2, which I disabled in tests. so sending basic auth credentials will not work.

答案1

得分: 2

@Given步骤中设置测试安全上下文,而不是禁用安全性。

示例摘自我写的此README(当时Spring的Keycloak适配器尚未弃用):

Gherkin功能:

  1. Feature: 测试安全的REST API
  2. 用户只有在经过身份验证后才能获取问候
  3. Scenario: 授权用户应该受到问候
  4. Given 具有以下用户角色:
  5. | ROLE_user |
  6. | ROLE_TESTER |
  7. When 发送GET请求到问候端点
  8. Then 返回一个问候
  9. Scenario: 未经授权的用户不应能够访问问候
  10. Given 用户未经身份验证
  11. When 发送GET请求到问候端点
  12. Then 返回401

以下代码使用了我发布在maven-central上的com.c4-soft.springaddons:spring-addons-oauth2-test库中的一些代码,以便更容易定义OpenID声明。如果您对声明的详细信息不感兴趣(如果权限和用户名足够),可以跳过此依赖项:

  1. public class JwtTestingBuilder {
  2. protected final OpenidClaimSetBuilder claimsBuilder;
  3. private final Set<String> authorities;
  4. private String bearerString = "machin.truc.chose";
  5. private Map<String, Object> headers = new HashMap<>(Map.of("machin", "truc"));
  6. public JwtTestingBuilder() {
  7. this.claimsBuilder = new OpenidClaimSetBuilder().subject(Defaults.SUBJECT).name(Defaults.AUTH_NAME);
  8. this.authorities = new HashSet<>(Defaults.AUTHORITIES);
  9. }
  10. public JwtAuthenticationToken build() {
  11. final var claims = claimsBuilder.build();
  12. final var iat = Instant.now();
  13. final var exp = iat.plusMillis(60000);
  14. final var jwt = new Jwt(bearerString, iat, exp, headers, claims);
  15. return new JwtAuthenticationToken(jwt, authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()), claims.getName());
  16. }
  17. public JwtTestingBuilder authorities(String... authorities) {
  18. return authorities(Stream.of(authorities));
  19. }
  20. public JwtTestingBuilder authorities(Stream<String> authorities) {
  21. this.authorities.clear();
  22. this.authorities.addAll(authorities.toList());
  23. return this;
  24. }
  25. public JwtTestingBuilder claims(Consumer<OpenidClaimSetBuilder> tokenBuilderConsumer) {
  26. tokenBuilderConsumer.accept(claimsBuilder);
  27. return this;
  28. }
  29. public JwtTestingBuilder bearerString(String bearerString) {
  30. this.bearerString = bearerString;
  31. return this;
  32. }
  33. }

步骤:

  1. public class GreetingControllerSuite {
  2. @Autowired
  3. MockMvc mockMvc;
  4. MvcResult result;
  5. @Given("user is not authenticated")
  6. public void unauthenticatedUser() {
  7. TestSecurityContextHolder.clearContext();
  8. }
  9. @Given("the following user roles:")
  10. public void authenticateAsUser(List<String> rolesTable) {
  11. TestSecurityContextHolder.clearContext();
  12. final Stream<String> roles = rolesTable.stream().map(String::trim);
  13. TestSecurityContextHolder.setAuthentication(new JwtTestingBuilder().authorities(roles).build());
  14. }
  15. @When("a get request is sent to greeting endpoint")
  16. public void getGreet() throws Exception {
  17. result = mockMvc.perform(get("/greet")).andReturn();
  18. }
  19. @Then("401 is returned")
  20. public void unauthorizedStatus() throws Exception {
  21. assertEquals(401, result.getResponse().getStatus());
  22. }
  23. @Then("a greeting is returned")
  24. public void greetingIsReturned() throws Exception {
  25. assertEquals(200, result.getResponse().getStatus());
  26. final var body = result.getResponse().getContentAsString();
  27. assertThat(body.contains("Hello user! You are granted with "));
  28. assertThat(body.contains("ROLE_user"));
  29. assertThat(body.contains("ROLE_TESTER"));
  30. }
  31. }

Cucumber JUnit 4适配器:

  1. import org.junit.runner.RunWith;
  2. import io.cucumber.junit.Cucumber;
  3. import io.cucumber.junit.CucumberOptions;
  4. @RunWith(Cucumber.class)
  5. @CucumberOptions(features = "classpath:cucumber-features", plugin = {
  6. "pretty",
  7. "html:target/cucumber" }, extraGlue = "com.c4_soft.springaddons.samples.webmvc.cucumber.extraglue")
  8. public class CucumberIntegrationTest {
  9. }

Spring "extraglue":

  1. package com.c4_soft.springaddons.samples.webmvc.cucumber.extraglue;
  2. ...
  3. @CucumberContextConfiguration
  4. @SpringBootTest(webEnvironment = WebEnvironment.MOCK)
  5. @ContextConfiguration(classes = { SpringBootSampleApp.class })
  6. @AutoConfigureMockMvc
  7. public static class CucumberSpringConfiguration {
  8. }
英文:

Setup test security context in a @Given step instead of disabling security.

Sample taken from this README I wrote (at a time Keycloak adapters for Spring where not deprecated):

Gherkin feature:

  1. Feature: Testing a secured REST API
  2. Users should be able to GET greetings only if authenticated
  3. Scenario: Authorized users should be greeted
  4. Given the following user roles:
  5. | ROLE_user |
  6. | ROLE_TESTER |
  7. When a get request is sent to greeting endpoint
  8. Then a greeting is returned
  9. Scenario: Unauthorized users should not be able to access greetings
  10. Given user is not authenticated
  11. When a get request is sent to greeting endpoint
  12. Then 401 is returned

The following uses some code from com.c4-soft.springaddons:spring-addons-oauth2-test, a lib of mine published on maven-central, to ease defining OpenID claims. You can skip this dependency if you are not interested in claims details (if authorities and user name are enough):

  1. public class JwtTestingBuilder {
  2. protected final OpenidClaimSetBuilder claimsBuilder;
  3. private final Set&lt;String&gt; authorities;
  4. private String bearerString = &quot;machin.truc.chose&quot;;
  5. private Map&lt;String, Object&gt; headers = new HashMap&lt;&gt;(Map.of(&quot;machin&quot;, &quot;truc&quot;));
  6. public JwtTestingBuilder() {
  7. this.claimsBuilder = new OpenidClaimSetBuilder().subject(Defaults.SUBJECT).name(Defaults.AUTH_NAME);
  8. this.authorities = new HashSet&lt;&gt;(Defaults.AUTHORITIES);
  9. }
  10. public JwtAuthenticationToken build() {
  11. final var claims = claimsBuilder.build();
  12. final var iat = Instant.now();
  13. final var exp = iat.plusMillis(60000);
  14. final var jwt = new Jwt(bearerString, iat, exp, headers, claims);
  15. return new JwtAuthenticationToken(jwt, authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()), claims.getName());
  16. }
  17. public JwtTestingBuilder authorities(String... authorities) {
  18. return authorities(Stream.of(authorities));
  19. }
  20. public JwtTestingBuilder authorities(Stream&lt;String&gt; authorities) {
  21. this.authorities.clear();
  22. this.authorities.addAll(authorities.toList());
  23. return this;
  24. }
  25. public JwtTestingBuilder claims(Consumer&lt;OpenidClaimSetBuilder&gt; tokenBuilderConsumer) {
  26. tokenBuilderConsumer.accept(claimsBuilder);
  27. return this;
  28. }
  29. public JwtTestingBuilder bearerString(String bearerString) {
  30. this.bearerString = bearerString;
  31. return this;
  32. }
  33. }

Steps:

  1. public class GreetingControllerSuite {
  2. @Autowired
  3. MockMvc mockMvc;
  4. MvcResult result;
  5. @Given(&quot;user is not authenticated&quot;)
  6. public void unauthenticatedUser() {
  7. TestSecurityContextHolder.clearContext();
  8. }
  9. @Given(&quot;the following user roles:&quot;)
  10. public void authenticateAsUser(List&lt;String&gt; rolesTable) {
  11. TestSecurityContextHolder.clearContext();
  12. final Stream&lt;String&gt; roles = rolesTable.stream().map(String::trim);
  13. TestSecurityContextHolder.setAuthentication(new JwtTestingBuilder().authorities(roles).build());
  14. }
  15. @When(&quot;a get request is sent to greeting endpoint&quot;)
  16. public void getGreet() throws Exception {
  17. result = mockMvc.perform(get(&quot;/greet&quot;)).andReturn();
  18. }
  19. @Then(&quot;401 is returned&quot;)
  20. public void unauthorizedStatus() throws Exception {
  21. assertEquals(401, result.getResponse().getStatus());
  22. }
  23. @Then(&quot;a greeting is returned&quot;)
  24. public void greetingIsReturned() throws Exception {
  25. assertEquals(200, result.getResponse().getStatus());
  26. final var body = result.getResponse().getContentAsString();
  27. assertThat(body.contains(&quot;Hello user! You are granted with &quot;));
  28. assertThat(body.contains(&quot;ROLE_user&quot;));
  29. assertThat(body.contains(&quot;ROLE_TESTER&quot;));
  30. }
  31. }

Cucumber JUnit 4 adapter:

  1. import org.junit.runner.RunWith;
  2. import io.cucumber.junit.Cucumber;
  3. import io.cucumber.junit.CucumberOptions;
  4. @RunWith(Cucumber.class)
  5. @CucumberOptions(features = &quot;classpath:cucumber-features&quot;, plugin = {
  6. &quot;pretty&quot;,
  7. &quot;html:target/cucumber&quot; }, extraGlue = &quot;com.c4_soft.springaddons.samples.webmvc.cucumber.extraglue&quot;)
  8. public class CucumberIntegrationTest {
  9. }

Spring "extraglue":

  1. package com.c4_soft.springaddons.samples.webmvc.cucumber.extraglue;
  2. ...
  3. @CucumberContextConfiguration
  4. @SpringBootTest(webEnvironment = WebEnvironment.MOCK)
  5. @ContextConfiguration(classes = { SpringBootSampleApp.class })
  6. @AutoConfigureMockMvc
  7. public static class CucumberSpringConfiguration {
  8. }

答案2

得分: -1

其中一个解决方案是使用一个模拟实现,在类级别或方法级别使用以下注解:

@WithMockUser(username="admin",roles={"USER","ADMIN"})

更多详细信息请阅读此链接

英文:

One of the solution is to use an Mock impl, the following annotation at the class level or method level:

@WithMockUser(username=&quot;admin&quot;,roles={&quot;USER&quot;,&quot;ADMIN&quot;})

For more details read it

huangapple
  • 本文由 发表于 2023年7月20日 15:50:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76727731.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定