I have a microservice setup with Spring boot and OAuth 2 with JWT.
I have additional fields in my JWT token.
Most of my services call a static method that has a thread local of the additional fields in the token.
How can I write unit tests for such services?
Even if I tried to inject a mock user it doesn't work and I couldn't find a way of sending the JWT because I am no testing the controllers.
Code:
SecurityUtils static Calss (also check the package for other relevant JWT handler) .
Example on a method that will call the static class (Line 79).
Method:
public CommonResponse saveUpdateProfile(ProfileRequest request) {
String authUsername = SecurityUtils.getUsername();
Optional<ProfileEntity> optionalProfile = findProfileByUsername(authUsername);
ProfileEntity profile;
if (optionalProfile.isPresent()) {
profile = optionalProfile.get();
} else {
profile = ProfileEntity.builder()
.username(authUsername)
.build();
}
profile.setFirstName(request.getFirstName());
profile.setLastName(request.getLastName());
ProfileEntity savedProfile = profileRepository.save(profile);
if (savedProfile == null) {
throw new RuntimeException("Failed to save user in database");
}
return CommonResponse.ok(savedProfile);
}
I appreciate all the help.
Ok, so that's a common problem when using static methods. You can't easily override them, e.g. in tests. I think what I would do is to turn your SecurityUtils class into a service and make it implement an interface. Then inject this interface into any other service that needs to use it, instead of calling static methods. Then you can easily provide another implementation to your tests.
So you would have something like that:
interface SecurityUtils {
String getUsername();
...
}
#Service
class MySecurityUtils immplements SecurityUtils {
private JwtToken getJwtToken() {
return MySecurityContextHolder.getContext().getJwtToken();
}
public String getUsername() {
return getJwtToken().getUsername();
}
...
}
Then in the unit test you can just inject a mock implementation of SecurityUtils to any class you're testing.
Related
I'm writing unit tests for my application and one of the steps I do inside my Service is get the currently authenticated user from SpringSecurityContext.
I know that if I want to mock Spring Security Authentication I can use #WithMockUser but it's not working for me as it is always returning null when the tested method reaches the getAuthentication() method call...
I've already search many SO questions and many blogs post but none of them has a solution.
I annotate my Test class with #ExtendWith(MockitoExtension.class)
I'd like to avoid having to write 4 lines to mock a single method call
// mock Authentication
// mock Principal
// when SpringContextHolder.getContext().getAuthentication -> return Authentication
// when getPrincipal() -> return Principal
edit:
Test class
#ExtendWith(MockitoExtension.class)
public class SegmetnServiceTest {
#InjectMocks
private SegmentService service;
#Test
void testWithMockUser() {
//given
UpdateSegmentReq request = new UpdateSegmentReq();
String name = "TEST"
request.setName(name)
//when
SegmentNode segment = service.updateSegment(request);
//then
assertEquals(segment.getName(), name)
}
}
Service class
public class SegmentService {
private SegmentRepository repository;
SegmentNode updateSegment(String code){
SegmentNode segment = repository.findByCode(code);
String userId = (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
segment.updatedBy(userId);
return segment;
}
}
The problem is, even if I annotate my Test method with #WithMockUser, when it reaches the service method getAuthentication is null and getPrincipal throws NPE.
You should be using #ContextConfiguration to load Spring Security test configuration into the Application Context(assuming you also have spring-testas a dependency)See for clarification.
You can use the annotation #SpringJUnitConfig to combine the required annotations -- #ExtendWith(SpringExtension.class) and #ContextConfiguration -- to setup spring test in your tests.
I'm using MSAL on the front end (PKCE) and azure-active-directory-spring-boot-starter on the server to provide an entity which represents the logged in user and their claims.
I've built a class which wraps Microsoft's UserPrincipal to provide easy access to well-known claims:
import com.microsoft.azure.spring.autoconfigure.aad.UserPrincipal;
public class MyCustomUser {
private UserPrincipal userPrincipal;
public MyCustomUser(UserPrincipal userPrincipal) {
this.userPrincipal = userPrincipal;
}
public String getEmployeeId() {
return String.valueOf(this.userPrincipal.getClaim("emplid"));
}
}
I make this available via a helper class
#Component
public class MyAppSecurityContext {
public MyCustomUser getUser() {
UserPrincipal principal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return new MyCustomUser(principal);
}
}
and then use it in my service layer:
#Service
public class AnnouncementServiceImpl implements AnnouncementService {
#Autowired
private MyAppSecurityContext securityContext;
#Override
public List<Announcement> saveAnnouncement(Announcement announcement) {
MyCustomUser currentUser = this.securityContext.getUser();
String empid = currentUser.getEmployeeId();
return this.announcementRepository.saveAnnouncement(empid, announcement);
}
}
This works, but it feels wrong. I'd prefer to have MyCustomUser extend UserPrincipal and have getPrincipal() return my custom type (without effectively re-implementing my own UserPrincipal) instead of providing a facade in front of a member object. The problem is that UserPrincipal's constructor expects JWT concerns, which suggests that this isn't the correct approach. Is there another, more appropriate way to model the user for a Spring security project which relies on client-side claims only?
#Josh.
In azure-active-directory-spring-boot-starter, UserPrincipal is used in AADAuthenticationFilter and AADAppRoleStatelessAuthenticationFilter. Now both of the 2 filters are deprecated.
Could you please use the latest version of azure-spring-boot-starter-active-diectory? Which is 3.7.0, and it works for spring-boot:2.5.0. Since you used UserPrincipal, then you application is a resource server.
Here is the docs: https://github.com/Azure/azure-sdk-for-java/tree/azure-spring-boot-starter-active-directory_3.7.0/sdk/spring/azure-spring-boot-starter-active-directory#accessing-a-resource-server
We have some samples.
You current application is similar to:
https://github.com/Azure-Samples/azure-spring-boot-samples/tree/azure-spring-boot_3.6/aad/azure-spring-boot-sample-active-directory-resource-server-by-filter-stateless
https://github.com/Azure-Samples/azure-spring-boot-samples/tree/azure-spring-boot_3.6/aad/azure-spring-boot-sample-active-directory-resource-server-by-filter
But the 2 usage is deprecated, I suggest you to learn the new way:https://github.com/Azure-Samples/azure-spring-boot-samples/tree/azure-spring-boot_3.6/aad/azure-spring-boot-sample-active-directory-resource-server
Okay, so I am pretty new to testing and Spring Boot in general, so please correct me if I am doing something completely wrong here in the first place.
As a project my team and I are making a Web Application using Spring Boot where we are making calls to the Microsoft Graph API in some of our services. See this service for cancelling an event in a user's calendar:
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.models.extensions.IGraphServiceClient;
import com.microsoft.graph.requests.extensions.GraphServiceClient;
import org.springframework.stereotype.Service;
#Service
public class CancelEventService {
public void cancelEvent(final String token, final String id) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
IGraphServiceClient graphServiceClient = GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
graphServiceClient.me().events(id)
.buildRequest()
.delete();
}
}
This is working great, but I have been struggling for a couple of days with how to write unit tests for this. The way I see it, I want to either make mocks of the GraphServiceClient, or use a tool like WireMock to make the request go to a mockserver and return some values I configure.
I've tried doing both, but I can't make mocks of GraphServiceClient because it is not a Bean in my project, so I can't figure out how I should proceed to make an implementation I can autowire in to my Service.
When it comes to WireMock I am not even sure I understand if it is capable of doing what I want to, and if it is, I sure haven't been able to configure it correctly (note that we are using JUnit 5 for this project). A simple example where you make a GET-request to Google.com and return "Hello" via WireMock would suffice to get me going here.
Any concrete examples of what I should do, or even just a nod in the right direction would be well appreciated.
Well, I cannot assure you that it will work but it will give you a better landscape of the situation:
1) So first, we need to make a slight change on your service.
Need to extract IGraphServiceClient from the method so we can mock it later, see:
#Service
public class CancelEventService {
private IGraphServiceClient graphServiceClient;
#Autowired
public CancelEventService(IGraphServiceClient graphServiceClient){
this.graphServiceClient = graphServiceClient;
}
public void cancelEvent(final String token, final String id) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
graphServiceClient = GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
graphServiceClient.me().events(id)
.buildRequest()
.delete();
}
}
2) The test would look like this:
(Notice that all we are using here is included in spring boot test module, so you shouldn't need to add anything to the project dependencies)
#RunWith(SpringRunner.class)
public class CancelEventServiceTest {
private IGraphServiceClient graphServiceClientMock;
private CancelEventService serviceToBeTested;
#Before
public void setUp(){
graphServiceClientMock = Mockito.mock(IGraphServiceClient.class, RETURNS_DEEP_STUBS);
serviceToBeTested = new CancelEventService(graphServiceClientMock);
}
#Test
public void test_1() {
serviceToBeTested.cancelEvent("token", "id");
verify(graphServiceClientMock, times(1)).me().events("id").buildRequest()
.delete();
}
}
Hope it helps!
As a follow up on this, we found a solution to the problem by creating a class
GraphServiceClientImpl with a method that returns an instantiated GraphServiceClient.
#Component
public class GraphServiceClientImpl {
public IGraphServiceClient instantiateGraphServiceClient(final String token) {
IAuthenticationProvider authenticationProvider = iHttpRequest -> iHttpRequest.addHeader("Authorization", "Bearer " + token);
return GraphServiceClient.builder().authenticationProvider(authenticationProvider).buildClient();
}
}
#Service
public class CancelEventService{
private GraphServiceClientImpl graphServiceClientImpl;
public CancelEventService(final GraphServiceClientImpl graphServiceClientImpl) {
this.graphServiceClientImpl = graphServiceClientImpl;
}
public void cancelEvent(final String token, final String id) {
IGraphServiceClient graphServiceClient = graphServiceClientImpl.instantiateGraphServiceClient(token);
graphServiceClient
.me()
.events(id)
.buildRequest()
.delete();
}
}
Then, our test:
#ExtendWith(MockitoExtension.class)
class CancelEventServiceTest {
#Mock
private GraphServiceClientImpl graphServiceClientImpl;
#InjectMocks
private CancelEventService cancelEventService;
#BeforeEach
void setUp() {
MockitoAnnotations.initMocks(this);
}
#Test
void cancelEvent_Successful() {
//given
IGraphServiceClient serviceClientMock = mock(IGraphServiceClient.class, RETURNS_DEEP_STUBS);
given(graphServiceClientImpl.instantiateGraphServiceClient(anyString())).willReturn(serviceClientMock);
//when
cancelEventService.cancelBooking("Token", "1");
//then
verify(serviceClientMock, times(1)).me();
}
}
Probably not the optimal solution, but it works. Any other takes on this would be welcome!
I have an Spring Rest application, in which there's a requirement to get some access token in a service(Annotated with #Service) class. The token generation is in a separate class(Annotated with #Component) and the requirement given is to get a unique token for each new request. And I'll be using the generated token two times within the same request in the service class. Now the questions are as follows.
I can inject the tokenGenerator class using #Autowired. And get the token, store it in the private instance variable inside the class and use it wherever I wanted inside the service class. Is it the correct approach to store a value that should live as long as the request is alive. When I tested this approach I find the same access token is printed across the methods in service class for a single request. what would go wrong here?
Another approach I tried was using WebApplicationContext.SCOPE_REQUEST on the TokenGenerator class and use the Provider in my service class to get the class instance and then access token from it. This also works the same way as the previous approach. If so why do I even need to use the approach for request scoped values?
Approach 1:
Here I have used the dependency injection to get the access token.
#Component
public class TokenGenerator
{
public string getToken();//okhttp Rest API Call
}
#Service
public class ServiceClass{
#Autowired
TokenGenetor token;
private String tokenValue;//**setTokenValue as setter**
public void method1()
{
setTokenValue(token.getToken());
}
public method2(){print(tokenValue)}// prints 1234abcd
public method3(){print(tokenValue)}// prints 1234abcd
}
Approach 2:
Here I have used the RequestScope to get the access token. and Used the Provider to get the instance in the service class.
#Component
#scope(WebApplicationContext=scope_request)
public class TokenGenerator
{
public string getToken();//okhttp Rest API Call
}
#Service
public class ServiceClass{
#Autowired
private Provider<TokenGenetor> token;
private String tokenValue;//setTokenValue as setter
public void method1()
{
// Not Setting anything
}
public method2(){print(token.get().getToken();)}// prints 1234abcd
public method3(){print(token.get().getToken();)}// prints 1234abcd
}
I have a method in the service class that uses an external wrapper to call the slack api. The wrapper I'm using is this one if it makes any difference. This is how I'm using the wrapper,
//This is the method in my service class
public String sendMess(SlackObj obj) {
//SlackObj contains the channel url, channel name and the message
//build the payload from the SlackObj
//Slack is the name of the wrapper's class that I'm using
Slack slack = Slack.getInstance();
//slack.send is the method that sends the message to slack
WebhookResponse res = slack.send(url, payload);
//other logic
}
//This is what I've tried
#Test
public void slackSendMessageTest(){
//build the slack obj and payload
//build the mock WebhookResponse
Slack slackMock = mock(Slack.class)
when(slackMock.send(channelUrl, payload)).thenReturn(mockWebHookRes);
assertEquals("SUCCESS", testService.sendMessage(testSlackObj);
}
I am trying to write some tests for this method, so my question is, how would i test it without having the message sent every time I run the test? I believe the cause of this is because slack itself is not mocked and I have no idea as how to inject the mock into the mocked service class.
I am open to refactoring the service class if it helps with the testing. Any suggestions and recommendation is appreciated. Thanks.
You are going to have to find a way to mock Slack, which appears to be a singleton, unfortunately.
Here's what I would do:
1) Make Slack available as a bean that can be autowired:
#Configuration
public class SlackConfiguration {
#Bean
public Slack slack() {
return Slack.getInstance();
}
}
2) Change your class to take an injected Slack:
Note that I am totally guessing on the name here, as you just show the method. You would inject the Slack object you turned into a #Bean above, and not use Slack.getInstance() directly anywhere else.
#Component
public class SlackService {
private final Slack slack;
#Autowired
public SlackService(final Slack slack) {
this.slack = slack;
}
public String sendMessage(final Object message) {
final WebhookResponse res = slack.send(url, payload);
// etc
}
}
3) Mock the Slack object and pass it to your SlackService in test:
This allows you to mock out the implementation of Slack, so you can alter its behavior. I won't go into mocking in detail.
public class SlacServiceTest {
private final Slack slack = mock(Slack.class);
private final SlackService serviceUnderTest = new SlackService(slack);
#Test
public void testSomething() {
// TODO: set mock responses here
// Given... when... then...
}
}