Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 19 days ago.
Improve this question
I have Spring Security config that configures OAuth2 resource server and endpoints with the predefined scopes and what you can do with what endpoint. Config is below:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(
auth -> auth
.antMatchers("/api/v1/private/**")
.hasAnyAuthority("SCOPE_api:read", "SCOPE_api:write")
.antMatchers("/api/v1/public/**")
.authenticated()
).oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.httpBasic().disable()
.csrf().disable()
.formLogin().disable()
.logout().disable();
return http.build();
}
}
There are multiple answers on the similar question on StackOverflow such as 1, 2, 3, even though I look at them, I cannot wrap my head around it and how to test the setup, correctness etc.
My questions has two parts:
How can I test my config? There is a nice Baeldung article but it forces to run 2 separate applications to integration tests to succeed. Great for demo, not so great for CI and development.
What is normally can be tested in these use-cases? What is normal to unit-test and what is normal to integration test?
I have solved similar task with testcontainers and Keycloack, but that was possible as we had in test and prod Keycloack. This time, I have no control over resource server and I don't know it's type, so it makes my head to explode.
As with all 3rd parties, you shouldn't test a 3rd party service (at least not from your Java project). You'll just have to trust that the devs are doing their job, and that the service correctly issues a JWT.
What you want to test are OAuth scopes (aka Authorities in Spring Security). That is done by mocking your Authentication context so that it includes a principal with your required scopes.
The way I set it up in my projects is the following:
Make sure you have org.springframework.security:spring-security-test as a dependency
In your test source, create an annotation that will act as your fake user setup
#Retention(RetentionPolicy.RUNTIME)
#WithSecurityContext(factory = WithMockUserSecurityContextFactory.class)
public #interface WithMockUser {
long userId() default 1L;
String[] authorities() default "ROLE_USER";
}
Again in your test source make a class that will mock the Security Context
public class WithMockUserSecurityContextFactory implements WithSecurityContextFactory<WithMockUser> {
#Override
public SecurityContext createSecurityContext(WithMockUser annotation) {
var context = SecurityContextHolder.createEmptyContext();
var authorities = Arrays.stream(annotation.authorities())
.map(SimpleGrantedAuthority::new)
.toList();
var principal = UserPrincipal.builder()
.userId( annotation.userId() )
.email("test#test.com")
.authorities( authorities )
.enabled(true)
.build();
var auth = new UserPrincipalAuthentication(principal);
context.setAuthentication(auth);
return context;
}
}
Note that UserPrincipalAuthentication is my custom class that extends AbstractAuthenticationToken.
Now you should be able to annotate your test methods with the custom annotation and pass in the authorities you want to test for, such as this:
#AutoConfigureMockMvc
#SpringBootTest
class EndpointTest {
#Test
#WithMockUser(authorities = "scope:READ")
void returnsUserData_ifHasReadScope() {
// ...
// MockMvc calls to your endpoint, and assertions
}
}
What this does, is it completely removes the 3rd party resource server from testing scope. In our test scope we don't care which server the JWT came from, it doesn't matter how it was decoded (that can be done through other unit tests), all that matters is if Access Control is working, given that User comes with a particular set of Authorities.
For end-to-end or "smoke" tests (test which include more than just your resource-server: authorization-server, REST client, etc.), their are better suited tools than Java tests, and yes, this is very likely to require you to lift containers and program robots to manipulate UIs (perform user login, fill forms, etc.).
As stated by #Thorne in his answer, testing JWT decoding and validation should not be in the scope of your Java tests (unless you write your own decoder). Also exchange with an authorization-server to issue valid access-tokens would be way to slow and fragile for a decent coverage in unit-tests (JUnit with #ExtendWith(SpringExtension.class), #WebMvcTest or #WebfluxTest) or spring-boot integration-tests (#SpringBootTest).
What I unit and integration test regarding security conf is access-control, using mocked OAuth2 identities.
spring-security-test comes with some MockMvc request post-processors (see org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt in your case) as well as WebTestClient mutators (see org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockJwt) to configure Authentication of the right type (JwtAuthenticationToken in your case) and set it in test security context, but this is limited to MockMvc and WebTestClient and as so to #Controller tests.
Sample usage with your security conf and a #GetMapping("/api/v1/private/machin") #PreAuthorize("hasAuthority('SCOPE_api:read')"):
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
#WebMvcTest(controllers = MachinController.class, properties = { "server.ssl.enabled=false" })
class MachinControllerTest {
#Autowired
MockMvc api;
#Test
void givenUserIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/private/machin"))
.andExpect(status().isUnauthorized());
}
#Test
void givenUserIsGrantedWithApiRead_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/private/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(new SimpleGrantedAuthority("SCOPE_api:read"))))))
.andExpect(status().isOk());
}
#Test
void givenUserIsAuthenticatedButNotGrantedWithApiRead_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/private/machin")
.with(jwt().jwt(jwt -> jwt.authorities(List.of(new SimpleGrantedAuthority("SCOPE_openid"))))))
.andExpect(status().isForbidden());
}
}
You might also use #WithMockJwtAuth from this libs I maintain. This repo contains quite a few samples for unit and integration testing of any kind of #Component (#Controllers of course but also #Services or #Repositories decorated with method-security).
Above Sample becomes:
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-oauth2-test</artifactId>
<version>6.0.12</version>
<scope>test</scope>
</dependency>
#WebMvcTest(controllers = MachinController.class, properties = { "server.ssl.enabled=false" })
class MachinControllerTest {
#Autowired
MockMvc api;
#Test
void givenUserIsAnonymous_whenGetMachin_thenUnauthorized() throws Exception {
api.perform(get("/api/v1/private/machin"))
.andExpect(status().isUnauthorized());
}
#Test
#WithMockJwtAuth("SCOPE_api:read")
void givenUserIsGrantedWithApiRead_whenGetMachin_thenOk() throws Exception {
api.perform(get("/api/v1/private/machin"))
.andExpect(status().isOk());
}
#Test
#WithMockJwtAuth("SCOPE_openid")
void givenUserIsAuthenticatedButNotGrantedWithApiRead_whenGetMachin_thenForbidden() throws Exception {
api.perform(get("/api/v1/private/machin"))
.andExpect(status().isForbidden());
}
}
Spring-addons starter
In the same repo, you'll find starters to simplify your resource server security config (and also synchronize sessions and CSRF protection disabling as the second should not be disabled with active sessions...).
Usage is super simple and all you'd have to change between environments would be properties, even if using different OIDC authorization-servers, for instance a standalone Keycloak on your dev machine and a cloud provider like Cognito, Auth0, etc. in production.
Instead of directly importing spring-boot-starter-oauth2-resource-server, import a thin wrapper around it (composed of 3 files only):
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.12</version>
</dependency>
As you already apply fine grained method-security to protected routes, you could probably empty this Java conf (keep default expressionInterceptUrlRegistryPostProcessor bean which requires users to be authenticated to access any route but those listed in com.c4-soft.springaddons.security.permit-all property):
#Configuration
#EnableMethodSecurity
public class SecurityConfig {
#Bean
ExpressionInterceptUrlRegistryPostProcessor expressionInterceptUrlRegistryPostProcessor() {
return (AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) -> registry
.requestMatchers("/api/v1/private/**").hasAnyAuthority("SCOPE_api:read", "SCOPE_api:write")
.anyRequest().authenticated();
}
}
Replace spring.security.oauth2.resourceserver properties with:
# Single OIDC JWT issuer but you can add as many as you like
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
# Mimic spring-security default authorities converter: map from "scope" claim (accepts a comma separated list claims), with "SCOPE_" prefix
com.c4-soft.springaddons.security.issuers[0].authorities.claims=scope
com.c4-soft.springaddons.security.issuers[0].authorities.prefix=SCOPE_
# Fine-grained CORS configuration can be set per path as follow:
com.c4-soft.springaddons.security.cors[0].path=/api/**
com.c4-soft.springaddons.security.cors[0].allowed-origins=https://localhost,https://localhost:8100,https://localhost:4200
com.c4-soft.springaddons.security.cors[0].allowedOrigins=*
com.c4-soft.springaddons.security.cors[0].allowedMethods=*
com.c4-soft.springaddons.security.cors[0].allowedHeaders=*
com.c4-soft.springaddons.security.cors[0].exposedHeaders=*
# Comma separated list of ant path matchers for resources accessible to anonymous
com.c4-soft.springaddons.security.permit-all=/api/v1/public/**
Related
I want to unit test my controllers security using the method based security when I've checked the docs I've found this but it uses #RunWith and it doesn't seem to be loading only the security config.
What I tried to do:
Loading my secuirty config
#ContextConfiguration(classes = {SecurityConfigImpl.class})
public class PeopleGQLApiSecutiryTest {
private final PersonService personService = mock(PersonService.class);
private final PeopleGQLApi peopleGQLApi = new PeopleGQLApi(personService);
#Test
#WithAnonymousUser
void itShouldCreateNewPerson() {
when(personService.createNewPerson(any(Person.class))).thenReturn(new Person());
// this should fail!
peopleGQLApi.createPerson(
Person.LevelEnum.WEAK,
// rest of the arguments
);
}
}
This one should fail (by throwing an expection or anything like that) because create person is a secured function with ROLE_ADMIN.
Note: this is a GraphQL mutation
#MutationMapping("createPerson")
#Secured({"ROLE_ADMIN"})
public Person createPerson(
#Argument("level") Level level,
// rest of the arguments
) {
// method implementation
}
I can't use a fully fledged #SpringBootTest because I will have to provide mongodb config too which out of the scope of this test.
I think the word you are looking for is #WithMockUser
You can use something like #WithMockUser(authorities = "ROLE_ADMIN") to mock a user with this role
Also for more tweaks, you can use the Spring Security Context
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("username", "password"));
SecurityContextHolder.setContext(securityContext);
And you can add any authorities to your security context or read them.
better to add them to securityUtil class to be used in multiple situations as well.
Please make sure that you have an assertion to validate the unauthorized requests inside your test case
I have the Spring application.
We have the service which makes HTTP requests to external services. Now I think about writing Unit Test on this functionality. I want to write the integration tests. Thus I want to know that service request is correct.
Is there ways to do it in spring? (actually I don't know ways to do it outside the Spring too)
In tests you can use some kind of mock server. Wiremock for example.
Wiremock can work as a proxy and log all your requests and responses. More than that - it can then save responses and use them as stubs. It can be very useful to test integration problems with number of fault simulations.
For spring boot there is a starter:
<dependency>
<groupId>com.epages</groupId>
<artifactId>wiremock-spring-boot-starter</artifactId>
<version>0.7.18</version>
<scope>test</scope>
</dependency>
More documentation available here: https://github.com/ePages-de/restdocs-wiremock
Then create a test and setup a proxy:
#RunWith(SpringRunner.class)
#EnableAutoConfiguration
#SpringBootTest(
classes = {
YourApplication.class,
}
)
#WireMockTest // it comes from starter
public class YourTest {
#Inject
private WireMockServer server;
#Value("http://localhost:${wiremock.port}")
private String uri;
#Test
public void shouldLogAllTheThings() throws Exception {
server.stubFor(get(urlMatching("/other/service/.*"))
.willReturn(
aResponse()
.proxiedFrom("http://otherhost.com/approot")
));
// your call with rest template using uri field value
// as base uri
}
#TestConfiguration
public static class Internal {
#Inject
public WireMockConfiguration configuration;
#PostConstruct
public void wiremock() {
// this adds verbosive logger
// which will print all communication
configuration.notifier(new Slf4jNotifier(true));
}
}
}
More info:
http://wiremock.org/docs/proxying/
Mappings can be saved using this doc: http://wiremock.org/docs/record-playback/
If you don't want to use starter, you can use wiremock as JUnit rule:
http://wiremock.org/docs/junit-rule/
I am using Spring Boot 1.3, Spring 4.2 and Spring Security 4.0. I am running integration tests using MockMvc, for example:
mockMvc = webAppContextSetup(webApplicationContext).build();
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
In my tests I am simulating a user login like this:
CurrentUser principal = new CurrentUser(user);
Authentication auth =
new UsernamePasswordAuthenticationToken(principal, "dummypassword",
principal.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
This works fine for my methods that are annotated with #PreAuthorize, for example when calling a method like this from a test:
#PreAuthorize("#permissionsService.canDoThisThing(principal)")
public void mySecuredMethod()
the principle, my CurrentUser object, is non-null in PermissionsService#canDoThisThing.
I have a class annotated with #ControllerAdvice that adds the currently logged-in user to the model so it can be accessed in every view:
#ControllerAdvice
public class CurrentUserControllerAdvice {
#ModelAttribute("currentUser")
public CurrentUser getCurrentUser(Authentication authentication) {
if (authentication == null) {
return null;
}
return (CurrentUser) authentication.getPrincipal();
}
}
This works fine when running the application, however (and this is my problem) - when running my tests the authentication parameter passed in to the getCurrentUser method above is always null. This means any references to the currentUser attribute in my view templates cause errors, so those tests fail.
I know I could get round this by retrieving the principle like this:
authentication = SecurityContextHolder.getContext().getAuthentication();
but I would rather not change my main code just so the tests work.
Setting the SecurityContextHolder does not work when using Spring Security with MockMvc. The reason is that Spring Security's SecurityContextPersistenceFilter attempts to resolve the SecurityContext from the HttpServletRequest. By default this is done using HttpSessionSecurityContextRepository by retrieving at the HttpSession attribute named SPRING_SECURITY_CONTEXT. The attribute name is defined by the constant HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY. Whatever SecurityContext is found in the HttpSession will then be set on the SecurityContextHolder which overrides the value you previously set.
Manually Solving the Issue
The fix that involves the least amount of change is to set the SecurityContext in the HttpSession. You can do this using something like this:
MvcResult result = mockMvc.perform(get("/").sessionAttr(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext))
.andExpect(status().isOk())
.etc;
The key is to ensure that you set the HttpSession attribute named SPRING_SECURITY_CONTEXT to the SecurityContext. In our example, we leverage the constant HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY to define the attribute name.
Spring Security Test
Spring Security 4.0 has officially added test support. This is by far the easiest and most flexible way to test your application with Spring Security.
Add spring-security-test
Since you are using Spring Boot, the easiest way to ensure you have this dependency is to include spring-security-test in your Maven pom. Spring Boot manages the version, so there is no need to specify a version.
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
Naturally, Gradle inclusion would be very similar.
Setting up MockMvc
In order to integrate with MockMvc there are some steps you must perform outlined in the reference.
The first step is to ensure you use #RunWith(SpringJUnit4ClassRunner.class). This should come as no surprise since this is a standard step when testing with Spring applications.
The next step is to ensure you build your MockMvc instance using SecurityMockMvcConfigurers.springSecurity(). For example:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class MyTests {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // sets up Spring Security with MockMvc
.build();
}
...
Running as a User
Now you can easily run with a specific user. There are two ways of accomplishing this with MockMvc.
Using a RequestPostProcessor
The first option is using a RequestPostProcessor. For your example, you could do something like this:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
// use this if CustomUser (principal) implements UserDetails
mvc
.perform(get("/").with(user(principal)))
...
// otherwise use this
mvc
.perform(get("/").with(authentication(auth)))
...
Using Annotations
You can also use annotations to specify the user. Since you use a custom user object (i.e. CurrentUser), you would probably consider using #WithUserDetails or #WithSecurityContext.
#WithUserDetails makes sense if you expose the UserDetailsService as a bean and you are alright with the user being looked up (i.e. it must exist). An example of #WithUserDetails might look like:
#Test
#WithUserDetails("usernameThatIsFoundByUserDetailsService")
public void run() throws Exception {
MvcResult result = mockMvc.perform(get("/"))
.andExpect(status().isOk())
.etc;
}
The alternative is to use #WithSecurityContext. This makes sense if you do not want to require the user to actually exist (as is necessary for WithUserDetails). I won't elaborate on this as it is well documented and without more details about your object model, I cannot provide a concrete example of this.
Assume I have made a simple client in my application that uses a remote web service that is exposing a RESTful API at some URI /foo/bar/{baz}. Now I wish to unit test my client that makes calls to this web service.
Ideally, in my tests, I’d like to mock the responses I get from the web service, given a specific request like /foo/bar/123 or /foo/bar/42. My client assumes the API is actually running somewhere, so I need a local "web service" to start running on http://localhost:9090/foo/bar for my tests.
I want my unit tests to be self-contained, similar to testing Spring controllers with the Spring MVC Test framework.
Some pseudo-code for a simple client, fetching numbers from the remote API:
// Initialization logic involving setting up mocking of remote API at
// http://localhost:9090/foo/bar
#Autowired
NumberClient numberClient // calls the API at http://localhost:9090/foo/bar
#Test
public void getNumber42() {
onRequest(mockAPI.get("/foo/bar/42")).thenRespond("{ \"number\" : 42 }");
assertEquals(42, numberClient.getNumber(42));
}
// ..
What are my alternatives using Spring?
Best method is to use WireMock.
Add the following dependencies:
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>org.igniterealtime.smack</groupId>
<artifactId>smack-core</artifactId>
<version>4.0.6</version>
</dependency>
Define and use the wiremock as shown below
#Rule
public WireMockRule wireMockRule = new WireMockRule(8089);
String response ="Hello world";
StubMapping responseValid = stubFor(get(urlEqualTo(url)).withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse().withStatus(200)
.withHeader("Content-Type", "application/json").withBody(response)));
If you use Spring RestTemplate you can use MockRestServiceServer. An example can be found here REST Client Testing With MockRestServiceServer.
If you want to unit test your client, then you'd mock out the services that are making the REST API calls, i.e. with mockito - I assume you do have a service that is making those API calls for you, right?
If on the other hand you want to "mock out" the rest APIs in that there is some sort of server giving you responses, which would be more in line of integration testing, you could try one of the many framework out there like restito, rest-driver or betamax.
You can easily use Mockito to mock a REST API in Spring Boot.
Put a stubbed controller in your test tree:
#RestController
public class OtherApiHooks {
#PostMapping("/v1/something/{myUUID}")
public ResponseEntity<Void> handlePost(#PathVariable("myUUID") UUID myUUID ) {
assert (false); // this function is meant to be mocked, not called
return new ResponseEntity<Void>(HttpStatus.NOT_IMPLEMENTED);
}
}
Your client will need to call the API on localhost when running tests. This could be configured in src/test/resources/application.properties. If the test is using RANDOM_PORT, your client under test will need to find that value. This is a bit tricky, but the issue is addressed here: Spring Boot - How to get the running port
Configure your test class to use a WebEnvironment (a running server) and now your test can use Mockito in the standard way, returning ResponseEntity objects as needed:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestsWithMockedRestDependencies {
#MockBean private OtherApiHooks otherApiHooks;
#Test public void test1() {
Mockito.doReturn(new ResponseEntity<Void>(HttpStatus.ACCEPTED))
.when(otherApiHooks).handlePost(any());
clientFunctionUnderTest(UUID.randomUUID()); // calls REST API internally
Mockito.verify(otherApiHooks).handlePost(eq(id));
}
}
You can also use this for end-to-end testing of your entire microservice in an environment with the mock created above. One way to do this is to inject TestRestTemplate into your test class, and use that to call your REST API in place of clientFunctionUnderTest from the example.
#Autowired private TestRestTemplate restTemplate;
#LocalServerPort private int localPort; // you're gonna need this too
How this works
Because OtherApiHooks is a #RestController in the test tree, Spring Boot will automatically establish the specified REST service when running the SpringBootTest.WebEnvironment.
Mockito is used here to mock the controller class -- not the service as a whole. Therefore, there will be some server-side processing managed by Spring Boot before the mock is hit. This may include such things as deserializing (and validating) the path UUID shown in the example.
From what I can tell, this approach is robust for parallel test runs with IntelliJ and Maven.
What you are looking for is the support for Client-side REST Tests in the Spring MVC Test Framework.
Assuming your NumberClient uses Spring's RestTemplate, this aforementioned support is the way to go!
Hope this helps,
Sam
Here is a basic example on how to mock a Controller class with Mockito:
The Controller class:
#RestController
#RequestMapping("/users")
public class UsersController {
#Autowired
private UserService userService;
public Page<UserCollectionItemDto> getUsers(Pageable pageable) {
Page<UserProfile> page = userService.getAllUsers(pageable);
List<UserCollectionItemDto> items = mapper.asUserCollectionItems(page.getContent());
return new PageImpl<UserCollectionItemDto>(items, pageable, page.getTotalElements());
}
}
Configure the beans:
#Configuration
public class UserConfig {
#Bean
public UsersController usersController() {
return new UsersController();
}
#Bean
public UserService userService() {
return Mockito.mock(UserService.class);
}
}
The UserCollectionItemDto is a simple POJO and it represents what the API consumer sends to the server. The UserProfile is the main object used in the service layer (by the UserService class). This behaviour also implements the DTO pattern.
Finally, mockup the expected behaviour:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader = AnnotationConfigContextLoader.class)
#Import(UserConfig.class)
public class UsersControllerTest {
#Autowired
private UsersController usersController;
#Autowired
private UserService userService;
#Test
public void getAllUsers() {
initGetAllUsersRules();
PageRequest pageable = new PageRequest(0, 10);
Page<UserDto> page = usersController.getUsers(pageable);
assertTrue(page.getNumberOfElements() == 1);
}
private void initGetAllUsersRules() {
Page<UserProfile> page = initPage();
when(userService.getAllUsers(any(Pageable.class))).thenReturn(page);
}
private Page<UserProfile> initPage() {
PageRequest pageRequest = new PageRequest(0, 10);
PageImpl<UserProfile> page = new PageImpl<>(getUsersList(), pageRequest, 1);
return page;
}
private List<UserProfile> getUsersList() {
UserProfile userProfile = new UserProfile();
List<UserProfile> userProfiles = new ArrayList<>();
userProfiles.add(userProfile);
return userProfiles;
}
}
The idea is to use the pure Controller bean and mockup its members. In this example, we mocked the UserService.getUsers() object to contain a user and then validated whether the Controller would return the right number of users.
With the same logic you can test the Service and other levels of your application. This example uses the Controller-Service-Repository Pattern as well :)
My company has been evaluating Spring MVC to determine if we should use it in one of our next projects. So far I love what I've seen, and right now I'm taking a look at the Spring Security module to determine if it's something we can/should use.
Our security requirements are pretty basic; a user just needs to be able to provide a username and password to be able to access certain parts of the site (such as to get info about their account); and there are a handful of pages on the site (FAQs, Support, etc) where an anonymous user should be given access.
In the prototype I've been creating, I have been storing a "LoginCredentials" object (which just contains username and password) in Session for an authenticated user; some of the controllers check to see if this object is in session to get a reference to the logged-in username, for example. I'm looking to replace this home-grown logic with Spring Security instead, which would have the nice benefit of removing any sort of "how do we track logged in users?" and "how do we authenticate users?" from my controller/business code.
It seems like Spring Security provides a (per-thread) "context" object to be able to access the username/principal info from anywhere in your app...
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
... which seems very un-Spring like as this object is a (global) singleton, in a way.
My question is this: if this is the standard way to access information about the authenticated user in Spring Security, what is the accepted way to inject an Authentication object into the SecurityContext so that it is available for my unit tests when the unit tests require an authenticated user?
Do I need to wire this up in the initialization method of each test case?
protected void setUp() throws Exception {
...
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(testUser.getLogin(), testUser.getPassword()));
...
}
This seems overly verbose. Is there an easier way?
The SecurityContextHolder object itself seems very un-Spring-like...
Just do it the usual way and then insert it using SecurityContextHolder.setContext() in your test class, for example:
Controller:
Authentication a = SecurityContextHolder.getContext().getAuthentication();
Test:
Authentication authentication = Mockito.mock(Authentication.class);
// Mockito.whens() for your authorization object
SecurityContext securityContext = Mockito.mock(SecurityContext.class);
Mockito.when(securityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(securityContext);
Without answering the question about how to create and inject Authentication objects, Spring Security 4.0 provides some welcome alternatives when it comes to testing. The #WithMockUser annotation enables the developer to specify a mock user (with optional authorities, username, password and roles) in a neat way:
#Test
#WithMockUser(username = "admin", authorities = { "ADMIN", "USER" })
public void getMessageWithMockUserCustomAuthorities() {
String message = messageService.getMessage();
...
}
There is also the option to use #WithUserDetails to emulate a UserDetails returned from the UserDetailsService, e.g.
#Test
#WithUserDetails("customUsername")
public void getMessageWithUserDetailsCustomUsername() {
String message = messageService.getMessage();
...
}
More details can be found in the #WithMockUser and the #WithUserDetails chapters in the Spring Security reference docs (from which the above examples were copied)
The problem is that Spring Security does not make the Authentication object available as a bean in the container, so there is no way to easily inject or autowire it out of the box.
Before we started to use Spring Security, we would create a session-scoped bean in the container to store the Principal, inject this into an "AuthenticationService" (singleton) and then inject this bean into other services that needed knowledge of the current Principal.
If you are implementing your own authentication service, you could basically do the same thing: create a session-scoped bean with a "principal" property, inject this into your authentication service, have the auth service set the property on successful auth, and then make the auth service available to other beans as you need it.
I wouldn't feel too bad about using SecurityContextHolder. though. I know that it's a static / Singleton and that Spring discourages using such things but their implementation takes care to behave appropriately depending on the environment: session-scoped in a Servlet container, thread-scoped in a JUnit test, etc. The real limiting factor of a Singleton is when it provides an implementation that is inflexible to different environments.
You are quite right to be concerned - static method calls are particularly problematic for unit testing as you cannot easily mock your dependencies. What I am going to show you is how to let the Spring IoC container do the dirty work for you, leaving you with neat, testable code. SecurityContextHolder is a framework class and while it may be ok for your low-level security code to be tied to it, you probably want to expose a neater interface to your UI components (i.e. controllers).
cliff.meyers mentioned one way around it - create your own "principal" type and inject an instance into consumers. The Spring <aop:scoped-proxy/> tag introduced in 2.x combined with a request scope bean definition, and the factory-method support may be the ticket to the most readable code.
It could work like following:
public class MyUserDetails implements UserDetails {
// this is your custom UserDetails implementation to serve as a principal
// implement the Spring methods and add your own methods as appropriate
}
public class MyUserHolder {
public static MyUserDetails getUserDetails() {
Authentication a = SecurityContextHolder.getContext().getAuthentication();
if (a == null) {
return null;
} else {
return (MyUserDetails) a.getPrincipal();
}
}
}
public class MyUserAwareController {
MyUserDetails currentUser;
public void setCurrentUser(MyUserDetails currentUser) {
this.currentUser = currentUser;
}
// controller code
}
Nothing complicated so far, right? In fact you probably had to do most of this already. Next, in your bean context define a request-scoped bean to hold the principal:
<bean id="userDetails" class="MyUserHolder" factory-method="getUserDetails" scope="request">
<aop:scoped-proxy/>
</bean>
<bean id="controller" class="MyUserAwareController">
<property name="currentUser" ref="userDetails"/>
<!-- other props -->
</bean>
Thanks to the magic of the aop:scoped-proxy tag, the static method getUserDetails will be called every time a new HTTP request comes in and any references to the currentUser property will be resolved correctly. Now unit testing becomes trivial:
protected void setUp() {
// existing init code
MyUserDetails user = new MyUserDetails();
// set up user as you wish
controller.setCurrentUser(user);
}
Hope this helps!
Personally I would just use Powermock along with Mockito or Easymock to mock the static SecurityContextHolder.getSecurityContext() in your unit/integration test e.g.
#RunWith(PowerMockRunner.class)
#PrepareForTest(SecurityContextHolder.class)
public class YourTestCase {
#Mock SecurityContext mockSecurityContext;
#Test
public void testMethodThatCallsStaticMethod() {
// Set mock behaviour/expectations on the mockSecurityContext
when(mockSecurityContext.getAuthentication()).thenReturn(...)
...
// Tell mockito to use Powermock to mock the SecurityContextHolder
PowerMockito.mockStatic(SecurityContextHolder.class);
// use Mockito to set up your expectation on SecurityContextHolder.getSecurityContext()
Mockito.when(SecurityContextHolder.getSecurityContext()).thenReturn(mockSecurityContext);
...
}
}
Admittedly there is quite a bit of boiler plate code here i.e. mock an Authentication object, mock a SecurityContext to return the Authentication and finally mock the SecurityContextHolder to get the SecurityContext, however its very flexible and allows you to unit test for scenarios like null Authentication objects etc. without having to change your (non test) code
Using a static in this case is the best way to write secure code.
Yes, statics are generally bad - generally, but in this case, the static is what you want. Since the security context associates a Principal with the currently running thread, the most secure code would access the static from the thread as directly as possible. Hiding the access behind a wrapper class that is injected provides an attacker with more points to attack. They wouldn't need access to the code (which they would have a hard time changing if the jar was signed), they just need a way to override the configuration, which can be done at runtime or slipping some XML onto the classpath. Even using annotation injection would be overridable with external XML. Such XML could inject the running system with a rogue principal.
I asked the same question myself over here, and just posted an answer that I recently found. Short answer is: inject a SecurityContext, and refer to SecurityContextHolder only in your Spring config to obtain the SecurityContext
General
In the meantime (since version 3.2, in the year 2013, thanks to SEC-2298) the authentication can be injected into MVC methods using the annotation #AuthenticationPrincipal:
#Controller
class Controller {
#RequestMapping("/somewhere")
public void doStuff(#AuthenticationPrincipal UserDetails myUser) {
}
}
Tests
In your unit test you can obviously call this Method directly. In integration tests using org.springframework.test.web.servlet.MockMvc you can use org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user() to inject the user like this:
mockMvc.perform(get("/somewhere").with(user(myUserDetails)));
This will however just directly fill the SecurityContext. If you want to make sure that the user is loaded from a session in your test, you can use this:
mockMvc.perform(get("/somewhere").with(sessionUser(myUserDetails)));
/* ... */
private static RequestPostProcessor sessionUser(final UserDetails userDetails) {
return new RequestPostProcessor() {
#Override
public MockHttpServletRequest postProcessRequest(final MockHttpServletRequest request) {
final SecurityContext securityContext = new SecurityContextImpl();
securityContext.setAuthentication(
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
);
request.getSession().setAttribute(
HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext
);
return request;
}
};
}
I would take a look at Spring's abstract test classes and mock objects which are talked about here. They provide a powerful way of auto-wiring your Spring managed objects making unit and integration testing easier.
Authentication is a property of a thread in server environment in the same way as it is a property of a process in OS. Having a bean instance for accessing authentication information would be inconvenient configuration and wiring overhead without any benefit.
Regarding test authentication there are several ways how you can make your life easier. My favourite is to make a custom annotation #Authenticated and test execution listener, which manages it. Check DirtiesContextTestExecutionListener for inspiration.
After quite a lot of work I was able to reproduce the desired behavior. I had emulated the login through MockMvc. It is too heavy for most unit tests but helpful for integration tests.
Of course I am willing to see those new features in Spring Security 4.0 that will make our testing easier.
package [myPackage]
import static org.junit.Assert.*;
import javax.inject.Inject;
import javax.servlet.http.HttpSession;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
#ContextConfiguration(locations={[my config file locations]})
#WebAppConfiguration
#RunWith(SpringJUnit4ClassRunner.class)
public static class getUserConfigurationTester{
private MockMvc mockMvc;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Autowired
private MockHttpServletRequest request;
#Autowired
private WebApplicationContext webappContext;
#Before
public void init() {
mockMvc = MockMvcBuilders.webAppContextSetup(webappContext)
.addFilters(springSecurityFilterChain)
.build();
}
#Test
public void testTwoReads() throws Exception{
HttpSession session = mockMvc.perform(post("/j_spring_security_check")
.param("j_username", "admin_001")
.param("j_password", "secret007"))
.andDo(print())
.andExpect(status().isMovedTemporarily())
.andExpect(redirectedUrl("/index"))
.andReturn()
.getRequest()
.getSession();
request.setSession(session);
SecurityContext securityContext = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
SecurityContextHolder.setContext(securityContext);
// Your test goes here. User is logged with
}
Adding to the answer of #matsev, to write an fully functional integration test with TestRestTemplate or Webclient you can combine #SpringBootTest and #AutoconfigureMockMvc and do the following:
#SpringBootTest(webEnvironment = RANDOM_PORT)
#AutoConfigureMockMvc
public class UserControllerIntegrationTest {
#Autowired
private WebTestClient webTestClient;
#Test
#WithMockUser(username = "user", roles = {"USER"})
public void testUserEndpoint() {
webTestClient.get().uri("/user")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.expectStatus().isOk();
}
}