I have a Spring Boot + Spring Security application that has severalantMatchers paths; some fullyAuthenticated(), some permitAll().
How to I write a test that verifies SecurityConfiguration has my endpoints under /api/** (and ultimately others) secured correctly?
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
//...
.antMatchers("/api/**").fullyAuthenticated()
}
}
Using spring-boot-1.5.2.RELEASE, spring-security-core-4.2.2-release.
Clarification1: I want to as-directly-as-possible test the SecurityConfiguration, as opposed to transitively testing via one of the /api/** endpoints, which may have their own #PreAuthorize security.
Clarification2: I would like something similar to this WebSecurityConfigurerAdapterTests.
Clarification3: I would like to #Autowire something at the Spring Security layer, ideally HttpSecurity, to test.
So you want to ensure that if someone changes .antMatchers("/api/**") to .antMatchers("/WRONG_PATH/**") then you have a test that will figure it out ?
The rules you define using HttpSecurity will end up configuring a FilterChainProxy with one or more SecurityFilterChain, each with a list of filters. Each filter, such as UsernamePasswordAuthenticationFilter
(used for form-based login), will have a RequestMatcher defined in the super class AbstractAuthenticationProcessingFilter. The problem is that RequestMatcher is an interface which currently have 12 different implementations, and this includes AndRequestMatcher and OrRequestMatcher, so the matching logic is not always simple. And most importantly RequestMatcher only has one method boolean matches(HttpServletRequest request), and the implementation often does not expose the configuration, so you will have to use reflection to access the private configurations of each RequestMatcher implementation (which could change in the future).
If you go down this path, and autowire FilterChainProxy into a test and use reflection to reverse-engineer the configuration, you have to consider all the implementation dependencies you have. For instance WebSecurityConfigurerAdapter has a default list of filters, which may change between releases, and unless disable it, and when it is disabled you have to define every filter explicitly. In addition new filters and RequestMatchers could be added over time, or the filter chain generated by HttpSecurity in one version of Spring Security may be slightly different in the next version (maybe not likely, but still possible).
Writing a generic test for your spring security configuration, is technically possible, but it is not exactly an easy thing to do, and the Spring Security filters certainly were not designed to support this. I have worked extensively with Spring Security since 2010, and I have never had the need for such a test, and personally I think it would be a waste of time trying to implement it. I think the time will be much better spent writing a test framework that makes it easy to write integration tests, which will implicitly test the security layer as well as the business logic.
I see below test case can help you achieve what you want. It is an Integration Test to test the Web Security configuration and we have similar testing done for all our code that is TDD driven.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#WebAppConfiguration
public class WebConfigIT {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setup() throws Exception {
mockMvc = webAppContextSetup(webApplicationContext)
.addFilter(springSecurityFilterChain)
.build();
}
#Test
public void testAuthenticationAtAPIURI() throws Exception {
mockMvc.perform(get("/api/xyz"))
.andExpect(status.is3xxRedirection());
}
This though looks like doing an explicit testing of the end-point (which is anyways a testing one have to do if doing TDD) but this is also bringing the Spring Security Filter Chain in context to enable you test the Security Context for the APP.
MockMVC should be enough to verify you security configuration since the only thing it mocks is the Http layer. However if you really wish to test your Spring Boot application, Tomcat server and all, you need to use #SpringBootTest, like this
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class NoGoServiceTest {
#LocalServerPort
private int port;
private <T> T makeDepthRequest(NoGoRequest request, NoGoResponse response, String path, Class<T> responseClass) {
testService.addRequestResponseMapping(request, response);
RestTemplate template = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Bearer " + tokenProvider.getToken());
RequestEntity<NoGoRequest> requestEntity = new RequestEntity<>(request, headers, HttpMethod.POST, getURI(path));
ResponseEntity<T> responseEntity = template.exchange(requestEntity, responseClass);
return responseEntity.getBody();
}
#SneakyThrows(URISyntaxException.class)
private URI getURI(String path) {
return new URI("http://localhost:" +port + "/nogo" + path);
}
// Test that makes request using `makeDepthRequest`
}
This code is a part on a test taken from an open source project (https://github.com/maritime-web/NoGoService). The basic idea is to start the test on a random port, which Spring will then inject into a field on the test. This allows you to construct URLs and use Springs RestTemplate to make http request to the server, using the same DTO classes as your Controllers. If the authentication mechanism is Basic or Token you simply have to add the correct Authorization header as in this example.
If you use Form authentication, then it becomes a bit harder, because you first have to GET /login, then extract the CSRF token and the JSessionId cookie, and the POST them with the credentials to /login, and after login you have to extract the new JSessionId cookie, as the sessionId is changed after login for security reasons.
Hope this was what you needed.
If you want to programatically know which endpoints exist, you can autowire the List of RequestHandlerProvider into your test and filter them based on the path they are exposed on.
#Autowired
List<RequestHandlerProvider> handlerProviders;
#Test
public void doTest() {
for (RequestHandlerProvider handlerProvider : handlerProviders) {
for (RequestHandler requestHandler : handlerProvider.requestHandlers()) {
for (String pattern : requestHandler.getPatternsCondition().getPatterns()) {
// call the endpoint without security and check that you get 401
}
}
}
}
Using the RequestHandlerProvider is how SpringFox determines which endpoint are available and their signature, when it build the swagger definition for an API.
Unless you spend a long time building the correct input for each endpoint you will not get 200 OK back from the endpoint when including a valid security token, so you probably have to accept 400 as a correct response.
If you are already worried some developer would make security related mistakes when introducing a new endpoint, I would be equally worried about the logic of the endpoint, which is why I think you should have an integration test for each of them, and that would test your security as well.
Thinking outside the box a little, and answering the question in a different way, would it not be easier to simply define a static String[], e.g.
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final String[] FULLY_AUTH_PUBLIC_URLS = {"/api/**", "/swagger-resources/**", "/health", "/info" };
protected void configure(HttpSecurity http) throws Exception {
http
//...
.antMatchers(FULLY_AUTH_PUBLIC_URLS).fullyAuthenticated()
}
}
...
And then if the purpose of the test is to ensure that no changes are made to the public urls simply test the known list?
The assumption here is that Spring Security works and has been tested so the only thing we are testing for is that the list of public URLs has not been changed. If they have changed a test should fail highlighting to the developer that there are dragons changing these values? I understand this does not cover the clarifications but assuming the supplied static public URLs are known to be accurate then this approach would provide a unit testable back stop if this is needed.
Related
we have a spring boot app with a java package that has spring controllers with endpoints for admin-like functionality. right now they all start with the same request mapping.
so one way i could do authorization of every endpoint in the package is by WebSecurityConfigurerAdapter implementation...
http.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
but i was thinking it would be nice to use AOP somehow to target the package of the admin controllers to PreAuthorize all controller methods in the package. just in case someone on the project creates a new controller in the proejct with a different request mapping it would automatically be projected. also, if we decided to PreAuthorize at the #Service level instead of the controller level then this way could be used as well.
so, is it possible to PreAuthorize at the package level with AOP or some other way?
Spring provides default AOP interceptor for #Secured and #PreAuthorized annotations, but it works only on class or method level, and AFAIK not intended to be expanded to package level.
To put it simply, Spring intercepts calls to certain methods or all class methods with these annotations and check whether SecurityContextHolder holds Authority object and whether its collection of GrantedAuthority matches any of the annotation's value field values. So, you can do the same thing using AOP, for example like this:
#Aspect
#Component
public class AdminServiceAOPAuthorization {
private static final List<String> ALLOWED_ROLES = List.of("ADMIN", "SUPER_ADMIN");
#Pointcut("within(com.example.service.admin.*)") // <- any method in any class of the package
public void adminServiceLayer() {}
#Before("adminServiceLayer()")
public void authorize(JoinPoint jp) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
// throw some exception depending on the logic
}
boolean authorized = authentication.getAuthorities().stream()
.anyMatch(ga -> ALLOWED_ROLES.contains(ga.getAuthority()));
if (!authorized) {
throw new AccessDeniedException("Access denied");
// log or whatever
}
}
}
For better performance I'd advise to use it at the service layer to let Spring create proxy using implemented interface (if you use them, of course) instead of proxying controller class.
I'm building a Spring Boot authorization server which needs to generate Oauth2 tokens with two different auth methods. I want to have a different endpoint for each method, but by default Spring only creates /oauth/token, and while it can be changed, I don't think it is possible to have two different paths for it.
As an alternative, I'm trying to create two methods in a controller which do an internal forward to /oauth/token, adding a parameter to the request so I can know where it came from.
I have something like this:
#RequestMapping(value = "/foo/oauth/token", method = RequestMethod.POST)
public ModelAndView fooOauth(ModelMap model) {
model.addAttribute("method", "foo");
return new ModelAndView("forward:/oauth/token", model);
}
This performs the forward correctly, but the auth fails with:
There is no client authentication. Try adding an appropriate authentication filter.
The same request works correctly when sent to /oauth/token directly, so I'm guessing that the problem is that the BasicAuthenticationFilter is not running after the forward.
How can I make it work?
I had exactly the same issue. After some research I found out that the problem was caused by Spring Boot 2, not by Spring Security configurations. According to the Spring Boot 2.0 migration guide:
Spring Security and Spring Session filters are configured for ASYNC, ERROR, and REQUEST dispatcher types.
and the Spring Boot's SecurityFilterAutoConfiguration source code:
#Bean
#ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes(
SecurityProperties securityProperties) {
if (securityProperties.getFilter().getDispatcherTypes() == null) {
return null;
}
return securityProperties.getFilter().getDispatcherTypes().stream()
.map((type) -> DispatcherType.valueOf(type.name())).collect(Collectors
.collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
}
where the defaults for securityProperties.getFilter().getDispatcherTypes() are defined in SecurityProperties as:
private Set<DispatcherType> dispatcherTypes = new HashSet<>(
Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
Thus by default, Spring Boot configures Spring Security so that its filters will not be applied to FORWARD requests (but only to ASYNC, ERROR and REQUEST), and therefore no security filter will be applied to authenticate the requests when forwarding them to /oauth/token.
The solution is simple. You can either add the following line to your application.properties in order to apply default filters to ALL forwarded requests
spring.security.filter.dispatcher-types=async,error,request,forward
or create your own custom filter chain with a path matcher and dispatcherType=FORWARD to only filter requests that are forwared to /oauth/token.
Looking carefully to the filter chains created for the Oauth endpoints, and for the forwarding controllers, it's easy to see that the latter are missing the BasicAuthenticationFilter, because they aren't authenticated, and auth isn't performed again after the forward.
To solve it, I created a new config like this:
#Configuration
public class ForwarderSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
#Autowired
private FooClientDetailsService fooClientDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
for (AuthorizationServerConfigurer configurerBit : configurers) configurerBit.configure(configurer);
http.apply(configurer);
http
.authorizeRequests()
.antMatchers("/foo/oauth/token").fullyAuthenticated()
.and()
.requestMatchers()
.antMatchers("/foo/oauth/token");
http.setSharedObject(ClientDetailsService.class, fooClientDetailsService);
}
}
This code mimics what Spring Oauth does behind the scenes (here), running identical filter chains with the same authentication options on both endpoints.
When the /oauth/token endpoint finally runs, it finds the auth results that it expects, and everything works.
Finally, if you want to run a different ClientDetailsService on two forwarding endpoints, you just have to create two configuration classes like this one, and replace the ClientDetailsService on the setSharedObject call in each of them. Note that for this, you'll have to set different #Order values in each class.
I would like to add a couple of tests to the example shown here:
https://spring.io/guides/gs/securing-web/
to be able to verify that a user can no longer access resources requiring authentication when session closes or expires. I would like to simulate both the following conditions in my tests:
a) the user voluntarily ends their session (e.g. close their browser);
b) the session times out;
I don't know how to reproduce those conditions using MockMvc.
I managed to do the following:
#Test
public void sessionIsInvalid() throws Exception {
FormLoginRequestBuilder login = formLogin()
.user("user")
.password("password");
mockMvc.perform(login)
.andExpect(authenticated())
.andDo(mvcResult -> {
MockHttpSession session = (MockHttpSession)mvcResult.getRequest().getSession();
session.invalidate();
mockMvc.perform(get("/hello")
.session(session))
.andExpect(status().isFound());
});
}
...which seems to work but I am not totally sure what invalidate does in this context and whether it matches condition a) above.
To emulate the session timeout, I've done instead:
#Test
public void sessionExpires() throws Exception {
FormLoginRequestBuilder login = formLogin()
.user("user")
.password("password");
mockMvc.perform(login)
.andExpect(authenticated())
.andDo(mvcResult -> {
MockHttpSession session = (MockHttpSession)mvcResult.getRequest().getSession();
session.setMaxInactiveInterval(1);
Thread.sleep(3);
mockMvc.perform(get("/hello")
.session(session))
.andExpect(status().isFound());
});
}
...but this doesn't work. Can someone help me understand what I am doing wrong?
When using Spring Boot with Spring Security (which is all about in you link), my approach is this:
create a custom spring security filter that is able to "convince" spring security that the session is expired (whatever it believes a session is)
add the custom filter just before ConcurrentSessionFilter
create an inner static #TestConfiguration class which could, in theory, just configure the HttpSecurity to add the custom filter (that's all we want). In practice I found that usually I have to have the class annotated with #TestConfiguration to extend my project's security configuration class (or at least the main one, if having many, e.g. SecurityConfiguration for my project); because in SecurityConfiguration I usually declare other #Bean too (e.g. CorsConfigurationSource) I usually have to also use #WebMvcTest(properties = "spring.main.allow-bean-definition-overriding=true", ...) to avoid the bean overriding error; have the class annotated with #TestConfiguration to be annotated with #Order(HIGHEST_PRECEDENCE) too.
create a simple web mvc test trying to GET some project-existing endpoint, e.g.:
#Test
#SneakyThrows
#WithMockUser
void sessionExpired() {
this.mvc.perform(get("/some-endpoint-here")).andExpect(...);
}
run the test and expect for your configured session expiration strategy to kick in; see HttpSecurity.sessionManagement(session -> session...expiredUrl(...)) or HttpSecurity.sessionManagement(session -> session...expiredSessionStrategy(...))
The below spring security configuration provided as a #TestConfiguration works with Spring Boot 2.3.12.RELEASE (and probably many more).
#TestConfiguration
#Order(HIGHEST_PRECEDENCE)
static class Config extends SecurityConfiguration {
public Config(SessionInformationExpiredStrategy expiredSessionStrategy, InvalidSessionStrategy invalidSessionStrategy) {
super(expiredSessionStrategy, invalidSessionStrategy);
}
#SneakyThrows
#Override
protected void configure(HttpSecurity http) {
super.configure(http);
// the custom filter as a lambda expression
http.addFilterBefore((request, response, chain) -> {
// preparing some objects we gonna need
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession session = httpRequest.getSession(false);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// getting our hands on the object that Spring Security believes
// is the "session" and which is in ConcurrentSessionFilter
List<Filter> filters = (List) ReflectionTestUtils.getField(chain, "additionalFilters");
int currentPosition = (int) ReflectionTestUtils.getField(chain, "currentPosition");
ConcurrentSessionFilter concurrentSessionFilter = (ConcurrentSessionFilter) filters.get(currentPosition);
SessionRegistry sessionRegistry = (SessionRegistry) ReflectionTestUtils.getField(concurrentSessionFilter, "sessionRegistry");
// the "session" does not exist (from Spring Security's PoV),
// we actually have to create (aka "register") it
sessionRegistry.registerNewSession(session.getId(), authentication.getPrincipal());
// the actual session expiration (from Spring Security's PoV)
sessionRegistry.getSessionInformation(session.getId()).expireNow();
// let the filters continue their job; ConcurrentSessionFilter
// follows and it'll determine that the "session" is expired
chain.doFilter(request, response);
}, ConcurrentSessionFilter.class);
log.debug("begin");
}
}
session.setMaxInactiveInterval(1); // in seconds
Thread.sleep(3); // in milliseconds
I have a Spring web server that on a request makes an external call to some third-party web API (e.g. retreive Facebook oauth token). After getting data from this call it computes a response:
#RestController
public class HelloController {
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
// Ask facebook about something
HttpGet httpget = new HttpGet(buildURI("https", "graph.facebook.com", "/oauth/access_token"));
String response = httpClient.execute(httpget).getEntity().toString();
// .. Do something with a response
return response;
}
}
I'm writing an integration test that checks that hitting url on my server leads to some expected result. However I want to mock the external server locally so that I don't even need internet access to test all this. What is the best way to do this?
I'm a novice in spring, this is what I have so far.
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest({})
public class TestHelloControllerIT {
#Test
public void getHelloToFacebook() throws Exception {
String url = new URL("http://localhost:8080/hello_to_facebook").toString();
//Somehow setup facebook server mock ...
//FaceBookServerMock facebookMock = ...
RestTemplate template = new TestRestTemplate();
ResponseEntity<String> response = template.getForEntity(url, String.class);
assertThat(response.getBody(), equalTo("..."));
//Assert that facebook mock got called
//facebookMock.verify();
}
}
The actual real set up is more complicated - I'm making Facebook oauth login and all that logic is not in the controller but in various Spring Security objects. However I suspect that testing code is supposed to be the same since I'm just hitting urls and expect a response, isn't it?
After playing a bit with various scenarios, here is the one way how can one achieve what was asked with minimal interventions to the main code
Refactor your controller to use a parameter for thirdparty server address:
#RestController
public class HelloController {
#Value("${api_host}")
private String apiHost;
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
// Ask facebook about something
HttpGet httpget = new HttpGet(buildURI("http", this.apiHost, "/oauth/access_token"));
String response = httpClient.execute(httpget).getEntity().toString();
// .. Do something with a response
return response + "_PROCESSED";
}
}
'api_host' equals to 'graph.facebook.com' in application.properties in the src/main/resources
Create a new controller in the src/test/java folder that mocks the thirdparty server.
Override 'api_host' for testing to 'localhost'.
Here is the code for steps 2 and 3 in one file for brevity:
#RestController
class FacebookMockController {
#RequestMapping("/oauth/access_token")
public String oauthToken() {
return "TEST_TOKEN";
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#IntegrationTest({"api_host=localhost",})
public class TestHelloControllerIT {
#Test
public void getHelloToFacebook() throws Exception {
String url = new URL("http://localhost:8080/hello_to_facebook").toString();
RestTemplate template = new TestRestTemplate();
ResponseEntity<String> response = template.getForEntity(url, String.class);
assertThat(response.getBody(), equalTo("TEST_TOKEN_PROCESSED"));
// Assert that facebook mock got called:
// for example add flag to mock, get the mock bean, check the flag
}
}
Is there a nicer way to do this? All feedback is appreciated!
P.S. Here are some complications I encountered putting this answer into more realistic app:
Eclipse mixes test and main configuration into classpath so you might screw up your main configuration by test classes and parameters: https://issuetracker.springsource.com/browse/STS-3882 Use gradle bootRun to avoid it
You have to open access to your mocked links in the security config if you have spring security set up. To append to a security config instead of messing with a main configuration config:
#Configuration
#Order(1)
class TestWebSecurityConfig extends WebSecurityConfig {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/access_token").permitAll();
super.configure(http);
}
}
It is not straightforward to hit https links in integration tests. I end up using TestRestTemplate with custom request factory and configured SSLConnectionSocketFactory.
If you use RestTemplate inside the HelloController you would be able to test it MockRestServiceTest, like here: https://www.baeldung.com/spring-mock-rest-template#using-spring-test
In this case
#RunWith(SpringJUnit4ClassRunner.class)
// Importand we need a working environment
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class TestHelloControllerIT {
#Autowired
private RestTemplate restTemplate;
// Available by default in SpringBootTest env
#Autowired
private TestRestTemplate testRestTemplate;
#Value("${api_host}")
private String apiHost;
private MockRestServiceServer mockServer;
#Before
public void init(){
mockServer = MockRestServiceServer.createServer(this.restTemplate);
}
#Test
public void getHelloToFacebook() throws Exception {
mockServer.expect(ExpectedCount.manyTimes(),
requestTo(buildURI("http", this.apiHost, "/oauth/access_token"))))
.andExpect(method(HttpMethod.POST))
.andRespond(withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body("{\"token\": \"TEST_TOKEN\"}")
);
// You can use relative URI thanks to TestRestTemplate
ResponseEntity<String> response = testRestTemplate.getForEntity("/hello_to_facebook", String.class);
// Do the test you need
}
}
Remember that you need a common RestTemplateConfiguration for autowiring, like this:
#Configuration
public class RestTemplateConfiguration {
/**
* A RestTemplate that compresses requests.
*
* #return RestTemplate
*/
#Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
And that you have to use it inside HelloController as well
#RestController
public class HelloController {
#Autowired
private RestTemplate restTemplate;
#RequestMapping("/hello_to_facebook")
public String hello_to_facebook() {
String response = restTemplate.getForEntity(buildURI("https", "graph.facebook.com", "/oauth/access_token"), String.class).getBody();
// .. Do something with a response
return response;
}
}
2018 Things have improved much.
I ended up using spring-cloud-contracts
Here's a video introduction https://www.youtube.com/watch?v=JEmpIDiX7LU . The first part of the talk walk you through a legacy service. That's the one you can use for external API.
Gist is,
You create a Contract for the external service using Groovy DSL or other methods that even support explicit calls/proxy or recording. Check documentation on what works for you
Since you dont actually have control over the 3rd party in this case, you will use the contract-verifier and create the stub locally but remember to skipTests
With the stub-jar now compiled and available you can run it from within your test cases as it will run a Wiremock for you.
This question and several stackoverflow answers helped me find the solution so here is my sample project for the next person who has these and other similar microservices related tests.
https://github.com/abshkd/spring-cloud-sample-games
With everything working once you will never ever look back and do all your tests with spring-cloud-contracts
#marcin-grzejszczak the author, is also on SO and he helped a lot figure this out. so if you get stuck, just post on SO.
You could have another spring configuration file that exposes the same endpoint as the HelloController class. You could then simply return the canned json response.
From your code, I'm not sure about just what you are trying to accomplish. If you simply want to see that the call to facebook works then there's no substitute for testing against the service that actually talks to facebook. Mocking the facebook response just to ensure that it is mocked correctly, doesn't strike me as a terribly useful test.
If you are testing to see that the data that comes back from facebook is mutated in some way and you want to make sure that the work being done on it is correct, then you could do that work in a separate method that took the facebook response as a paramater, and then carried out the mutation. You could then check based on various json inputs that it was working correctly.
You could test without bringing the web service into it at all.
I have a respository method annotated with #Secured. I am trying to write a unit test for this method, but my test fails because I need authentication to call the method. The method itself, happens to be the save() method. The error I get when I call the method is:
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
I cannot test this method because it requires authentication, and I cannot save a user to authenticate against (I am using hsqldb) because I would need to call this method to save. Any advice on how to unit test a method annotated with #secured or how to mock the authentication.
It depends on what you want to test.
If you want to test business logic of your application without any security stuff, you can disable security altogether. Assuming that you use SpringJunit4Runner, you can put security configuration into separate file and don't include it into #ContextConfiguration.
If you want to test authorization (e.g. correct work of #Secured annotations), you can access SecurityContext directly, bypassing all authentication-related stuff (in this case you can put authentication configuration into separate file and don't load it as well):
All you need is to put an appropriate Authentication object into SecurityContextHolder:
#Test
public void myTest() {
login(...);
...
logout();
}
private void login(...) {
SecurityContextHolder.getContext().setAuthentication(
new UsernamePasswordAuthenticationToken(...)
)
}
private void logout() {
SecurityContextHolder.clearContext();
}
Finally, if you want to test authentication, you can run the test with the database containing test data.
a) This is not a unit test, it's an integration test. A unit test would be no problem.
b) I'd try to keep your test setups separated. Why not just deactive spring security in tests where you are not testing security-related features?
c) If all else fails: inject a JdbcTemplate object and create the user data manulally in SQL during test setup. See Support classes for integration testing
I'd consider using an annotation such as #WithMockUser to populate test security context with "personas" (test user with specific authorities like 'admin', 'salesman', etc.).
If #Secured is on the repo, it must be because specs were stating something like "user must have this grant to query that", and so security should be unit tested too.
If UsernameAuthenticationToken (what #WithMockUser creates) doesn't meet your needs, then you might choose an other annotation, like this one I wrote, or even write one of your own creating the exact Authentication implementation you want.
Libs here, sample result extracted from there
#RunWith(SpringRunner.class)
#Import(JwtAuthenticationTokenMessageServiceTests.TestConfig.class)
public class JwtAuthenticationTokenMessageServiceTests {
#Autowired
private MessageService<JwtAuthenticationToken> messageService;
#Test
#WithMockAuthentication(authType = JwtAuthenticationToken.class, authorities = "ROLE_AUTHORIZED_PERSONNEL")
public void secretWithScopeAuthorizedPersonnelAuthority() {
assertThat(messageService.getSecret()).isEqualTo("Secret message");
}
#TestConfiguration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Import({ JwtTestConf.class, JwtAuthenticationTokenServletApp.JwtAuthenticationTokenMessageService.class })
public static class TestConfig {
}