Comments (27)
It is in your runtime config, but I can't see your WebSecurityConfig
referenced in test configuration you provided.
If you share a small project, I might add it to samples in tests once we have it working.
from spring-addons.
don't bother with the sample. I built one and figured out why security is not working: WithSecurityContextTestExecutionListener
is not triggered by Cucumber JUnit test runner.
I oppened a ticket on Cucumber-jvm: cucumber/cucumber-jvm#2408
from spring-addons.
Hi,
Do you have cucumber-spring
on your test classpath?
Did you check articles like https://www.baeldung.com/cucumber-spring-integration?
I think what you encounter is not a bug from this lib but rather a compatibility issue between Spring and Cucumber.
You need a spring context (with a spring security-context) for @WithMock...
to work (for the dots, put User
, KeycloakAuth
, JwtAuth
or whichever test annotation itslef decorated with @WithSecurityContext
): your test must run with a JUnit spring runner or extension, something like @WebMvcTest
, @SpringBootTest
, etc.
from spring-addons.
Hi,
Yes we already have hundreds of cucumber tests running with Spring. But this is the first bunch tests that I want to run against spring security configurations. I think I will switch to junit testing and give your lib a try outside cucumber. Will let you know.
Thanks
from spring-addons.
Hi,
As expected, your lib is working in a pure JUnit MockMvc context. It would nice to understand why it is not working in a cucumber MockMVC context. Do you know why the Principal parameter in the controller method is not instanciated as it is at runtime? Here's a snippet and would expect the Principal parameter to not be null.
Thanks
@RestController
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@PostMapping("")
public List<ProductLightDTO> searchProducts(@RequestBody ProductFilter productFilter, Principal principal) {
return productService.search(productFilter);
}
from spring-addons.
Hi @eaudet ,
I got a look at Cucumber and here is what I understood. Please correct me if I'm wrong:
When running with Cucumber, REST service is launched separatly from steps
=> tested code (REST api) and testing code (Cucumber steps) run in different processes
=> @WithSecurityContext
annotations will be of no help, you need actual (valid) JWT bearer tokens in authorisation header of your requests
This lib (and spring-security test framework) are designed to run unit tests, not integration: it is designed to shortcut JWT decoding, validation etc. and inject a mocked security context in current thread (no encoded JSON web token is created).
from spring-addons.
Hi @ch4mpy ,
Steps and Spring components run in the same process/thread. Steps can autowire a spring component. Hence, you can test a service directly without calling a REST controller. You can also use SpringMVC (MockMVC) for testing rest endpoints. You can see the thread id is the same in service and cucumber steps. So I guess your wrong. We're not doing integration tests here but really unit tests. The KeycloakAuthenticationProvider.class is not even called during junit test and cucumber test. So your lib must be able to inject the KC token or a basic auth into spring context...
Erick
from spring-addons.
If so, yes, it should be possible to us @WithMockKeycloakAuth
to build a KeycloakAuthenticationToken
and inject it in test security context.
You might have a test configuration issue. Most probably @EnableWebSecurity
and @EnableGlobalMethodSecurity
are not included.
I'd be interested in a sample spring-boot project with Cucumber for unit-tests. Ideally working for non secured REST endpoints.
from spring-addons.
Both @EnableWebSecurity
and @EnableGlobalMethodSecurity
are included as I wrote in my original issue. I will try to find a moment to build a project.
from spring-addons.
Hi, I have jump to cucumber 7.0.0 (as mentionned) but still have the same error. The annotation @WithMockKeycloakAuth({"ROLE_user"}) is not injected in spring context. Here's the stack trace:
Step failed
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at org.springframework.test.web.servlet.TestDispatcherServlet.service(TestDispatcherServlet.java:77)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
at org.springframework.mock.web.MockFilterChain$ServletFilterProxy.doFilter(MockFilterChain.java:167)
at org.springframework.mock.web.MockFilterChain.doFilter(MockFilterChain.java:134)
at org.springframework.test.web.servlet.MockMvc.perform(MockMvc.java:183)
at com.c4_soft.springaddons.security.oauth2.test.mockmvc.MockMvcSupport.perform(MockMvcSupport.java:154)
from spring-addons.
Keep in mind that:
WithSecurityContextTestExecutionListener
looks for annotations on test methods and if not found there on the test class.
- Cucumber does not have test methods. So I don't think this will work (but I don't know how
WithMockKeycloakAuth
is supposed to work):
@WithMockKeycloakAuth({ "ROLE_user" })
public List<ProductLightDTO> securedProductSearch(ProductFilter filter, String username, String password) throws Exception {
So you may have to put the @WithMockKeycloakAuth
annotation on the class annotated with @CucumberContextConfiguration
.
from spring-addons.
@mpkorstanje in order to make this @WithMockKeycloakAuth
useful, it needs to be on the test method and not global on the class annotated @CucumberContextConfiguration
. Otherwise you cannot test finegrained role base access to spring services.
I found something interesting by adding a RequestPostProcessor
including a principal and a role in the mockMvc. This actually injects a principal in spring security context and I am able to have a fully instantiated Principal object in my spring service. But the role is still not injected correctly. Maybe this can help?
MvcResult result = mockMvc.perform(post("/products").contentType(APPLICATION_JSON_UTF8)
.content(body).with(authUtil.getRequestPostProcessor("fakeOwner", "ROLE_user"))).andReturn();
My authUtil class:
@Component
public class AuthUtil {
public RequestPostProcessor getRequestPostProcessor(String ownerUuid, String userRole) {
return mockHttpServletRequest -> {
mockHttpServletRequest.setUserPrincipal(() -> ownerUuid);
mockHttpServletRequest.addUserRole(userRole);
return mockHttpServletRequest;
};
}
}
from spring-addons.
-
@WithMockKeycloakAuth
is decorated with@WithSecurityContext
. It works just as@WithMockUser
:WithSecurityContextTestExecutionListener
calls a factory to get anAuthentication
instance and then configures testSecurityContext
with it. -
I know
WithSecurityContextTestExecutionListener
is a Spring construct made to work with JUnit and that it does not work with Cucumber out of the box, reason for creating the ticket on cucumber-jvm which contains a cucumber-spring module (which I understand as being intended to make a bridge with Spring JUnit tests)
I'm very new to Cucumber, but maybe could it be possible to either
- define
@Given
steps which triggerWithSecurityContextTestExecutionListener
logic - hook into
@When
steps also decorated with@WithSecurityContext
annotations (@WithMockUser
,@WithMockKeycloakAuth
or whatever) to triggerWithSecurityContextTestExecutionListener
logic
Maybe second option is more flexible but first is more "Cucumber oriented"?
from spring-addons.
@eaudet I have a workaround for you untill we find a way to have annotations working: use MockMvc request post processor.
I just pushed a demo of that on gh-29 branch:
public class GreetingControllerSuite {
@Autowired
MockMvc mockMvc;
@Autowired
ServletKeycloakAuthUnitTestingSupport keycloak;
MvcResult result;
@When("authenticated users want to get greeting")
public void authenticatedUsersWantToGetGreeting() throws Exception {
result = mockMvc.perform(get("/greet").with(keycloak.authentication())).andReturn();
}
@Then("a greeting is returned")
public void aGreetingIsReturned() throws Exception {
assertThat(result.getResponse().getContentAsString()).contains("Hello user");
}
}
You can of course chain many method calls after .with(keycloak.authentication())
to custumize KeycloakAuthenticationToken
(authorities, OpenID claims, etc.)
from spring-addons.
It's not quite obvious but a cucumber scenario matches to a junit method. As if calling several step definition methods from a single test method. This means that if the JUnit method does something to setup authorization this must be reflected in the steps of the scenario. So the annotation is essentially an extra method call.
This means that your workaround is infact the correct solution.
from spring-addons.
It's also not possible to trigger any TestExecutionListener
on a specific step definition method. The listeners are part of Springs Test Context manager framework and the expectation is that the listeners are invoked around a test. This doesn't match the scope of step definition methods which are strictly inside a test.
from spring-addons.
TestSecurityContext being configured before the test or as first step inside the test would not make much difference.
from spring-addons.
Not in this instance but remember we're managing this through an abstraction. The conceptual mismatch isn't something that can be resolved for all possible cases.
from spring-addons.
@mpkorstanje what do you suggest to test secured @Service
with Cucumber?
MockMvc request post processors can not be used in that case and Spring security-context must be configured.
from spring-addons.
@ch4mpy in my link above (#29 (comment)) I can inject a postProcessor and get a Principal in my @service methods....if only I could get the roles in the same way. This seems like a MockMVC bug to me.
from spring-addons.
what do you suggest to test secured @service with Cucumber?
Haven't tried but WithSecurityContextTestExecutionListener
ultimately calls TestSecurityContextHolder.setContext(SecurityContext)
and TestSecurityContextHolder.clearContext()
. I believe you can do that in a step definition manually.
from spring-addons.
That's what I'm working on.
It's pretty verbose and inconvenient.
from spring-addons.
You can get it pretty lean I think.
withMockKeycloakAuth().authorities("USER").claims(OpenIdClaims.preferredUsername("ch4mpy"));
Of course you'd have to write the DSL for that but that's a one time thing.
from spring-addons.
I got the following suite pass:
Feature: Testing a secured REST API
Authenticated users should be able to GET greetings
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 shouldn't be greeted
Given user is not authenticated
When a get request is sent to greeting endpoint
Then user is redirected to login
With this steps class:
package com.c4_soft.springaddons.samples.webmvc.keycloak.cucumber.steps;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import java.util.List;
import java.util.stream.Stream;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.test.context.TestSecurityContextHolder;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import com.c4_soft.springaddons.security.oauth2.test.mockmvc.keycloak.ServletKeycloakAuthUnitTestingSupport;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
public class GreetingControllerSuite {
@Autowired
MockMvc mockMvc;
@Autowired
ServletKeycloakAuthUnitTestingSupport keycloak;
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(keycloak.authentication().authorities(roles).build());
}
@When("a get request is sent to greeting endpoint")
public void getGreet() throws Exception {
result = mockMvc.perform(get("/greet")).andReturn();
}
@Then("user is redirected to login")
public void redirectedToLogin() throws Exception {
assertEquals(302, result.getResponse().getStatus());
}
@Then("a greeting is returned")
public void greetingIsReturned() throws Exception {
assertEquals(200, result.getResponse().getStatus());
final String body = result.getResponse().getContentAsString();
assertThat(body.contains("Hello user! You are granted with "));
assertThat(body.contains("ROLE_user"));
assertThat(body.contains("ROLE_TESTER"));
}
}
I'm sorry, but I can't get annotations working with Cucumber for the reasons mentioned above.
I hope the solution in this message is acceptable enough. If so, would you kindly close the ticket @eaudet?
from spring-addons.
The input from the table is already trimmed. You can remove that line.
from spring-addons.
@ch4mpy and @mpkorstanje thanks for the analysis. Even if annotations are not available, using roles in the Scenario directly is even better. Thanks!
from spring-addons.
Added a Cucumber.md to master branch
from spring-addons.
Related Issues (20)
- `authorization-request-params` ignored HOT 1
- POST /logout response Forbidden 403 HOT 9
- Support several JWT authentication converters (or converters with a `@Qualifier` which is not `jwtAuthenticationConverter`)
- Doubled path-prefix by `SpringAddonsServerOAuth2AuthorizationRequestResolver` HOT 1
- Allow anonymous CORS preflight requests (`OPTIONS` requests to a path configured with CORS) HOT 1
- Configuration properties to add parameters to token requests HOT 1
- Spring Starter OICD, Resource Server: Option to disable the default behavior for authorized/protected routes HOT 1
- BFF configuration token is not refreshed HOT 3
- Getting response 401 (Unauthorized) for permit-all requests after update HOT 2
- (Not a bug)Why the custom JwtDecoder bean is useless HOT 2
- `spring-security-oauth2-resource-server`, `spring-security-oauth2-client` and `spring-webflux` should be `optional` dependencies HOT 1
- Support for resource owner password credential flow (ROPC) HOT 1
- Handle CORS Requests with Keycloak's "allowed-origins" claim like the keycloak adapter (now deprecated) HOT 2
- Downstream services times out reading request body when csrf is set to cookie-accessible-from-js HOT 2
- Expand servlet-client tutorial to show calling servlet-resource-server with user that has NICE privileges. HOT 2
- Logout Issue (Invalid CSRF Token) HOT 3
- Import keycloak realms with spring-addons-starters-rest HOT 1
- `@WithOidcLogin` using json file similarly as `@WithJwt` HOT 8
- Need support in resolving 401 Unauthorized Error for Multi Tenant JWT Auth with Resource Server HOT 1
- Invalid SpringAddonsOidcProperties breaks native image HOT 7
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from spring-addons.