英文:
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检查的想法。
已尝试的选项:
- 在测试中设置
@EnableGlobalMethodSecurity(prePostEnabled = true)
。但未奏效。 - 参考了这个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:
- set
@EnableGlobalMethodSecurity(prePostEnabled = true)
in test. It didn't work - 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<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;
}
}
Steps:
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 adapter:
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 {
}
答案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="admin",roles={"USER","ADMIN"})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论