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

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

Disable @PreAuthorize annotation when running a profile

问题

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

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禁用了测试用例中的安全性:

@TestConfiguration
@Order(1)
@Profile("test")
public class TestSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .anyRequest()
                .permitAll();
    }

}

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

{
    "timestamp": 1689838624242,
    "errorType": "UNKNOWN",
    "errors": [
        "Access is denied"
    ]
}

已尝试多种选项来禁用特定配置文件或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:

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:

@TestConfiguration
@Order(1)
@Profile("test")
public class TestSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests()
                .anyRequest()
                .permitAll();
    }

}

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:

{
    "timestamp": 1689838624242,
    "errorType": "UNKNOWN",
    "errors": [
        "Access is denied"
    ]
}

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功能:

Feature: 测试安全的REST API
  用户只有在经过身份验证后才能获取问候

  Scenario: 授权用户应该受到问候
    Given 具有以下用户角色:
      | ROLE_user   |
      | ROLE_TESTER |
    When 发送GET请求到问候端点
    Then 返回一个问候

  Scenario: 未经授权的用户不应能够访问问候
    Given 用户未经身份验证
    When 发送GET请求到问候端点
    Then 返回401

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

public class JwtTestingBuilder {
	protected final OpenidClaimSetBuilder claimsBuilder;
	private final Set<String> authorities;
	private String bearerString = "machin.truc.chose";
	private Map<String, Object> headers = new HashMap<>(Map.of("machin", "truc"));

	public JwtTestingBuilder() {
		this.claimsBuilder = new OpenidClaimSetBuilder().subject(Defaults.SUBJECT).name(Defaults.AUTH_NAME);
		this.authorities = new HashSet<>(Defaults.AUTHORITIES);
	}

	public JwtAuthenticationToken build() {
		final var claims = claimsBuilder.build();
		final var iat = Instant.now();
		final var exp = iat.plusMillis(60000);
		final var jwt = new Jwt(bearerString, iat, exp, headers, claims);
		return new JwtAuthenticationToken(jwt, authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()), claims.getName());
	}

	public JwtTestingBuilder authorities(String... authorities) {
		return authorities(Stream.of(authorities));
	}

	public JwtTestingBuilder authorities(Stream<String> authorities) {
		this.authorities.clear();
		this.authorities.addAll(authorities.toList());
		return this;
	}

	public JwtTestingBuilder claims(Consumer<OpenidClaimSetBuilder> tokenBuilderConsumer) {
		tokenBuilderConsumer.accept(claimsBuilder);
		return this;
	}

	public JwtTestingBuilder bearerString(String bearerString) {
		this.bearerString = bearerString;
		return this;
	}
}

步骤:

public class GreetingControllerSuite {

	@Autowired
	MockMvc mockMvc;

	MvcResult result;

	@Given("user is not authenticated")
	public void unauthenticatedUser() {
		TestSecurityContextHolder.clearContext();
	}

	@Given("the following user roles:")
	public void authenticateAsUser(List<String> rolesTable) {
		TestSecurityContextHolder.clearContext();
		final Stream<String> roles = rolesTable.stream().map(String::trim);
		TestSecurityContextHolder.setAuthentication(new JwtTestingBuilder().authorities(roles).build());
	}

	@When("a get request is sent to greeting endpoint")
	public void getGreet() throws Exception {
		result = mockMvc.perform(get("/greet")).andReturn();
	}

	@Then("401 is returned")
	public void unauthorizedStatus() throws Exception {
		assertEquals(401, result.getResponse().getStatus());
	}

	@Then("a greeting is returned")
	public void greetingIsReturned() throws Exception {
		assertEquals(200, result.getResponse().getStatus());
		final var body = result.getResponse().getContentAsString();
		assertThat(body.contains("Hello user! You are granted with "));
		assertThat(body.contains("ROLE_user"));
		assertThat(body.contains("ROLE_TESTER"));
	}
}

Cucumber JUnit 4适配器:

import org.junit.runner.RunWith;

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)
@CucumberOptions(features = "classpath:cucumber-features", plugin = {
		"pretty",
		"html:target/cucumber" }, extraGlue = "com.c4_soft.springaddons.samples.webmvc.cucumber.extraglue")
public class CucumberIntegrationTest {
}

Spring "extraglue":

package com.c4_soft.springaddons.samples.webmvc.cucumber.extraglue;

...

@CucumberContextConfiguration
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@ContextConfiguration(classes = { SpringBootSampleApp.class })
@AutoConfigureMockMvc
public static class CucumberSpringConfiguration {
}
英文:

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:

Feature: Testing a secured REST API
Users should be able to GET greetings only if authenticated
Scenario: Authorized users should be greeted
Given the following user roles:
| ROLE_user   |
| ROLE_TESTER |
When a get request is sent to greeting endpoint
Then a greeting is returned
Scenario: Unauthorized users should not be able to access greetings
Given user is not authenticated
When a get request is sent to greeting endpoint
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):

public class JwtTestingBuilder {
	protected final OpenidClaimSetBuilder claimsBuilder;
	private final Set&lt;String&gt; authorities;
	private String bearerString = &quot;machin.truc.chose&quot;;
	private Map&lt;String, Object&gt; headers = new HashMap&lt;&gt;(Map.of(&quot;machin&quot;, &quot;truc&quot;));

	public JwtTestingBuilder() {
		this.claimsBuilder = new OpenidClaimSetBuilder().subject(Defaults.SUBJECT).name(Defaults.AUTH_NAME);
		this.authorities = new HashSet&lt;&gt;(Defaults.AUTHORITIES);
	}

	public JwtAuthenticationToken build() {
		final var claims = claimsBuilder.build();
		final var iat = Instant.now();
		final var exp = iat.plusMillis(60000);
		final var jwt = new Jwt(bearerString, iat, exp, headers, claims);
		return new JwtAuthenticationToken(jwt, authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()), claims.getName());
	}

	public JwtTestingBuilder authorities(String... authorities) {
		return authorities(Stream.of(authorities));
	}

	public JwtTestingBuilder authorities(Stream&lt;String&gt; authorities) {
		this.authorities.clear();
		this.authorities.addAll(authorities.toList());
		return this;
	}

	public JwtTestingBuilder claims(Consumer&lt;OpenidClaimSetBuilder&gt; tokenBuilderConsumer) {
		tokenBuilderConsumer.accept(claimsBuilder);
		return this;
	}

	public JwtTestingBuilder bearerString(String bearerString) {
		this.bearerString = bearerString;
		return this;
	}
}

Steps:

public class GreetingControllerSuite {

	@Autowired
	MockMvc mockMvc;

	MvcResult result;

	@Given(&quot;user is not authenticated&quot;)
	public void unauthenticatedUser() {
		TestSecurityContextHolder.clearContext();
	}

	@Given(&quot;the following user roles:&quot;)
	public void authenticateAsUser(List&lt;String&gt; rolesTable) {
		TestSecurityContextHolder.clearContext();
		final Stream&lt;String&gt; roles = rolesTable.stream().map(String::trim);
		TestSecurityContextHolder.setAuthentication(new JwtTestingBuilder().authorities(roles).build());
	}

	@When(&quot;a get request is sent to greeting endpoint&quot;)
	public void getGreet() throws Exception {
		result = mockMvc.perform(get(&quot;/greet&quot;)).andReturn();
	}

	@Then(&quot;401 is returned&quot;)
	public void unauthorizedStatus() throws Exception {
		assertEquals(401, result.getResponse().getStatus());
	}

	@Then(&quot;a greeting is returned&quot;)
	public void greetingIsReturned() throws Exception {
		assertEquals(200, result.getResponse().getStatus());
		final var body = result.getResponse().getContentAsString();
		assertThat(body.contains(&quot;Hello user! You are granted with &quot;));
		assertThat(body.contains(&quot;ROLE_user&quot;));
		assertThat(body.contains(&quot;ROLE_TESTER&quot;));
	}
}

Cucumber JUnit 4 adapter:

import org.junit.runner.RunWith;

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;

@RunWith(Cucumber.class)
@CucumberOptions(features = &quot;classpath:cucumber-features&quot;, plugin = {
		&quot;pretty&quot;,
		&quot;html:target/cucumber&quot; }, extraGlue = &quot;com.c4_soft.springaddons.samples.webmvc.cucumber.extraglue&quot;)
public class CucumberIntegrationTest {
}

Spring "extraglue":

package com.c4_soft.springaddons.samples.webmvc.cucumber.extraglue;

...

@CucumberContextConfiguration
@SpringBootTest(webEnvironment = WebEnvironment.MOCK)
@ContextConfiguration(classes = { SpringBootSampleApp.class })
@AutoConfigureMockMvc
public static class CucumberSpringConfiguration {
}

答案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:

确定