From 3337b5e66247350b5b7a08a50a1e0de36e183bde Mon Sep 17 00:00:00 2001 From: linxiaoxin Date: Tue, 23 Jul 2024 14:36:05 +0800 Subject: [PATCH] refactor auth service by introducing jwt helper class. return user id for isauthorisedUserAccess function --- .../com/quemistry/auth_ms/Util/JwtHelper.java | 53 +++++++++++++++++++ .../controller/AuthenticationController.java | 2 +- .../service/AuthenticationService.java | 2 +- .../service/AuthenticationServiceImpl.java | 48 ++++++++--------- .../AuthenticationControllerTest.java | 4 +- .../AuthenticationServiceImplTest.java | 29 +++++----- 6 files changed, 96 insertions(+), 42 deletions(-) create mode 100644 src/main/java/com/quemistry/auth_ms/Util/JwtHelper.java diff --git a/src/main/java/com/quemistry/auth_ms/Util/JwtHelper.java b/src/main/java/com/quemistry/auth_ms/Util/JwtHelper.java new file mode 100644 index 0000000..2817fe4 --- /dev/null +++ b/src/main/java/com/quemistry/auth_ms/Util/JwtHelper.java @@ -0,0 +1,53 @@ +package com.quemistry.auth_ms.Util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.Base64; +import java.util.Map; + +public class JwtHelper { + private static final String EMAIL = "email"; + private static final String USERID = "sub"; + private static final String USER_GROUP ="cognito:groups"; + + private final Map KeyValue; + public JwtHelper(String jwtString){ + this.KeyValue = JwtKeyValue(jwtString); + } + + public Boolean getValid(){ + return this.KeyValue != null; + } + + public String getEmail(){ + return (String)this.KeyValue.get(JwtHelper.EMAIL); + } + + public String getUserId(){ + return (String)this.KeyValue.get(JwtHelper.USERID); + } + + public String[] getUserGroup(){ + var useRoles = (ArrayList)this.KeyValue.get(JwtHelper.USER_GROUP); + if(useRoles != null) { + return useRoles.toArray(new String[useRoles.size()]); + } + return new String[0]; + } + private static Map JwtKeyValue(String jwtString) { + String[] chunks = (jwtString == null) ? null : jwtString.split("\\."); + if (chunks == null || chunks.length < 3) { + return null; + } + try { + Base64.Decoder decoder = Base64.getUrlDecoder(); + String payload = new String(decoder.decode(chunks[1])); + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(payload, new TypeReference<>() {}); + } catch (Exception ex) { + return null; + } + } +} diff --git a/src/main/java/com/quemistry/auth_ms/controller/AuthenticationController.java b/src/main/java/com/quemistry/auth_ms/controller/AuthenticationController.java index 89f76fa..fa63e78 100644 --- a/src/main/java/com/quemistry/auth_ms/controller/AuthenticationController.java +++ b/src/main/java/com/quemistry/auth_ms/controller/AuthenticationController.java @@ -68,7 +68,7 @@ public ResponseEntity isAuthorised(@RequestBody IsAuthorisedRequest req return ResponseEntity.status(HttpStatus.OK).body(result); } @PostMapping("isauthoriseduser") - public ResponseEntity isAuthorisedUser(@RequestBody IsAuthorisedRequest request){ + public ResponseEntity isAuthorisedUser(@RequestBody IsAuthorisedRequest request){ var result =authenticationService.checkUserSessionAccess(request.getSessionId(), request.getPath(), request.getMethod()); return ResponseEntity.status(HttpStatus.OK).body(result); } diff --git a/src/main/java/com/quemistry/auth_ms/service/AuthenticationService.java b/src/main/java/com/quemistry/auth_ms/service/AuthenticationService.java index 5adc761..4e6d13a 100644 --- a/src/main/java/com/quemistry/auth_ms/service/AuthenticationService.java +++ b/src/main/java/com/quemistry/auth_ms/service/AuthenticationService.java @@ -10,5 +10,5 @@ public interface AuthenticationService { Boolean checkAccess(String roleName, String path, String method); - Boolean checkUserSessionAccess(String sessionId, String path, String method); + String checkUserSessionAccess(String sessionId, String path, String method); } diff --git a/src/main/java/com/quemistry/auth_ms/service/AuthenticationServiceImpl.java b/src/main/java/com/quemistry/auth_ms/service/AuthenticationServiceImpl.java index 777fbc2..064225a 100644 --- a/src/main/java/com/quemistry/auth_ms/service/AuthenticationServiceImpl.java +++ b/src/main/java/com/quemistry/auth_ms/service/AuthenticationServiceImpl.java @@ -1,7 +1,6 @@ package com.quemistry.auth_ms.service; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.quemistry.auth_ms.Util.JwtHelper; import com.quemistry.auth_ms.model.TokenRequest; import com.quemistry.auth_ms.model.TokenResponse; import com.quemistry.auth_ms.model.UserProfile; @@ -16,9 +15,6 @@ import org.springframework.web.client.RestTemplate; import java.time.Duration; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Map; import java.util.UUID; @Service @@ -64,25 +60,20 @@ public UserProfile getAccessToken(TokenRequest request) { if(response.getStatusCode() == HttpStatus.OK){ var idToken = response.getBody().getIdToken(); - String[] chunks = (idToken == null) ? null : idToken.split("\\."); - if(chunks == null || chunks.length < 3) { - log.error("Invalid response from Idp Cognito."); - } - Base64.Decoder decoder = Base64.getUrlDecoder(); - String payload = new String(decoder.decode(chunks[1])); - ObjectMapper mapper = new ObjectMapper(); - try { - Map map = mapper.readValue(payload, new TypeReference<>() {}); + if(idToken == null) + return null; + try { + JwtHelper jwtIdToken = new JwtHelper(idToken); + if(!jwtIdToken.getValid()){ + log.error("Invalid Id token"); + return null; + } //creates session id as key and store user profile and access tokens info in redis user = new UserProfile(); user.setSessionId(UUID.randomUUID().toString()); - user.setEmail((String)map.get("email")); - var useRoles = (ArrayList)map.get("cognito:groups"); - if(useRoles != null) { - var roles = useRoles.toArray(new String[useRoles.size()]); - user.setRoles(roles); - } + user.setEmail(jwtIdToken.getEmail()); + user.setRoles(jwtIdToken.getUserGroup()); redisTemplate.opsForValue().set(user.getSessionId()+"_profile", user, Duration.ofSeconds(SESSION_TIMEOUT)); redisTemplate.opsForValue().set(user.getSessionId()+"_tokens", response.getBody(), Duration.ofSeconds(SESSION_TIMEOUT)); @@ -133,16 +124,21 @@ public Boolean checkAccess(String roleName, String path, String method) { } @Override - public Boolean checkUserSessionAccess(String sessionId, String path, String method) { + public String checkUserSessionAccess(String sessionId, String path, String method) { //get user profile role log.info("checkUserSessionAccess invoked"); + String userId = ""; var profile = ((UserProfile) redisTemplate.opsForValue().get(sessionId + "_profile")); - if(profile == null) + var tokens = ((TokenResponse) redisTemplate.opsForValue().get(sessionId + "_tokens")); + if(tokens == null || profile == null) { log.info("checkUserSessionAccess: session not found"); - return false; + return ""; }else{ log.info("checkUserSessionAccess: found. With roles:"+ String.join(";",profile.getRoles())); + //check if user token has expired. If yes to refresh, asynchronously. + JwtHelper jwtAccessToken = new JwtHelper(tokens.getAccessToken()); + userId = jwtAccessToken.getUserId(); } //get role var roles = roleRepository.findByNames(profile.getRoles()); @@ -154,11 +150,11 @@ public Boolean checkUserSessionAccess(String sessionId, String path, String meth roles.forEach(role -> rolesFound.append(role.getName()+";")); log.info("checkUserSessionAccess: "+rolesFound); } - if(roles.stream().anyMatch(role -> + if(roles.stream().anyMatch(role -> role.getGrantedWith().stream().anyMatch(granted -> granted.getPath().compareToIgnoreCase(path) == 0 && granted.getMethod().compareToIgnoreCase(method) == 0))){ - return true; + return userId; } - return false; + return ""; } } diff --git a/src/test/java/com/quemistry/auth_ms/controller/AuthenticationControllerTest.java b/src/test/java/com/quemistry/auth_ms/controller/AuthenticationControllerTest.java index 63ff3c1..f85a078 100644 --- a/src/test/java/com/quemistry/auth_ms/controller/AuthenticationControllerTest.java +++ b/src/test/java/com/quemistry/auth_ms/controller/AuthenticationControllerTest.java @@ -137,7 +137,7 @@ void givenisAuthorisedUser_Success() throws Exception{ ObjectMapper mapper = new ObjectMapper(); given(authenticationService.checkUserSessionAccess(request.getSessionId(), request.getPath(), request.getMethod())) - .willReturn(true); + .willReturn("userid-test"); var result = mockMvc.perform(post("/v1/auth/isauthoriseduser") .contentType(MediaType.APPLICATION_JSON) @@ -145,6 +145,6 @@ void givenisAuthorisedUser_Success() throws Exception{ .andExpect(status().isOk()) .andReturn(); - Assertions.assertEquals("true", result.getResponse().getContentAsString()); + Assertions.assertEquals("userid-test", result.getResponse().getContentAsString()); } } diff --git a/src/test/java/com/quemistry/auth_ms/service/AuthenticationServiceImplTest.java b/src/test/java/com/quemistry/auth_ms/service/AuthenticationServiceImplTest.java index c48cee0..a404c72 100644 --- a/src/test/java/com/quemistry/auth_ms/service/AuthenticationServiceImplTest.java +++ b/src/test/java/com/quemistry/auth_ms/service/AuthenticationServiceImplTest.java @@ -51,11 +51,10 @@ public class AuthenticationServiceImplTest { @InjectMocks private AuthenticationServiceImpl authenticationService; - private TokenResponse tokenResponse; private UserProfile user; - private String idToken; + private final String UserId = "c9aad54c-60e1-7045-e712-9ad1da73f87a"; @BeforeEach void init() throws NoSuchFieldException, IllegalAccessException { @@ -65,11 +64,12 @@ void init() throws NoSuchFieldException, IllegalAccessException { user.setSessionId(UUID.randomUUID().toString()); //idtoken with email set as testUser@email.com - idToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdF9oYXNoIjoidThRdnNKdFY1TXRETFdodF9xQmFpZyIsInN1YiI6ImM5YWFkNTRjLTYwZTEtNzA0NS1lNzEyLTlhZDFkYTczZjg3YSIsImNvZ25pdG86Z3JvdXBzIjpbInR1dG9yIiwiYWRtaW4iXSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJpc3MiOiJodHRwczovL2NvZ25pdG8taWRwLmFwLXNvdXRoZWFzdC0xLmFtYXpvbmF3cy5jb20vYXAtc291dGhlYXN0LTFfWmIwSmwwN1dzIiwiY29nbml0bzp1c2VybmFtZSI6Imdvb2dsZV8xMDQwMjk0OTE2Njc5NjE1ODg4NDAiLCJvcmlnaW5fanRpIjoiYTY1M2ExMDEtNTUyMi00ZDIyLTk1NzctZGZkZjA4ZDM5NDc4IiwiYXVkIjoiMXEzMHZtZDB2Y2VlNmsxbHJwMmluMTA2MjMiLCJpZGVudGl0aWVzIjpbeyJkYXRlQ3JlYXRlZCI6IjE3MTcyNTI0Mzk3MDMiLCJ1c2VySWQiOiIxMDQwMjk0OTE2Njc5NjE1ODg4NDAiLCJwcm92aWRlck5hbWUiOiJHb29nbGUiLCJwcm92aWRlclR5cGUiOiJHb29nbGUiLCJpc3N1ZXIiOm51bGwsInByaW1hcnkiOiJ0cnVlIn1dLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTcyMTE0MDE3NywiZXhwIjoxNzIxMTQxMDc3LCJpYXQiOjE3MjExNDAxNzcsImp0aSI6IjU5YjlkZmZlLWFkMjItNDMyZC05ZWIxLTRiZmVhYjFhOGY4MyIsImVtYWlsIjoidGVzdFVzZXJAZW1haWwuY29tIn0.HabZEsulPCsu-IYRE_G42RUWo0k5jMJqYSxJx_QgtuY"; + String idToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdF9oYXNoIjoidThRdnNKdFY1TXRETFdodF9xQmFpZyIsInN1YiI6ImM5YWFkNTRjLTYwZTEtNzA0NS1lNzEyLTlhZDFkYTczZjg3YSIsImNvZ25pdG86Z3JvdXBzIjpbInR1dG9yIiwiYWRtaW4iXSwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJpc3MiOiJodHRwczovL2NvZ25pdG8taWRwLmFwLXNvdXRoZWFzdC0xLmFtYXpvbmF3cy5jb20vYXAtc291dGhlYXN0LTFfWmIwSmwwN1dzIiwiY29nbml0bzp1c2VybmFtZSI6Imdvb2dsZV8xMDQwMjk0OTE2Njc5NjE1ODg4NDAiLCJvcmlnaW5fanRpIjoiYTY1M2ExMDEtNTUyMi00ZDIyLTk1NzctZGZkZjA4ZDM5NDc4IiwiYXVkIjoiMXEzMHZtZDB2Y2VlNmsxbHJwMmluMTA2MjMiLCJpZGVudGl0aWVzIjpbeyJkYXRlQ3JlYXRlZCI6IjE3MTcyNTI0Mzk3MDMiLCJ1c2VySWQiOiIxMDQwMjk0OTE2Njc5NjE1ODg4NDAiLCJwcm92aWRlck5hbWUiOiJHb29nbGUiLCJwcm92aWRlclR5cGUiOiJHb29nbGUiLCJpc3N1ZXIiOm51bGwsInByaW1hcnkiOiJ0cnVlIn1dLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTcyMTE0MDE3NywiZXhwIjoxNzIxMTQxMDc3LCJpYXQiOjE3MjExNDAxNzcsImp0aSI6IjU5YjlkZmZlLWFkMjItNDMyZC05ZWIxLTRiZmVhYjFhOGY4MyIsImVtYWlsIjoidGVzdFVzZXJAZW1haWwuY29tIn0.HabZEsulPCsu-IYRE_G42RUWo0k5jMJqYSxJx_QgtuY"; + tokenResponse = new TokenResponse(); tokenResponse.setIdToken(idToken); - tokenResponse.setAccessToken("testAccessToken"); - tokenResponse.setAccessToken("testRefreshToken"); + tokenResponse.setAccessToken(idToken); + tokenResponse.setRefreshToken("testRefreshToken"); tokenResponse.setExpiresIn(120); } @@ -101,10 +101,9 @@ void givenGetAccessToken_Success(){ Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations); - var result = authenticationService.getAccessToken(tokenRequest); - tokenResponse.setEmail("testUser@email.com"); + var result = authenticationService.getAccessToken(tokenRequest); - Assertions.assertEquals(user.getEmail(), result.getEmail() ); + Assertions.assertEquals(user.getEmail(), result.getEmail() ); Assertions.assertEquals(user.getRoles().length, result.getRoles().length ); } @@ -182,11 +181,13 @@ void givencheckUserSessionAccess_Success(){ Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations); Mockito.when(redisTemplate.opsForValue().get(user.getSessionId()+"_profile")) .thenReturn(user); + Mockito.when(redisTemplate.opsForValue().get(user.getSessionId()+"_tokens")) + .thenReturn(tokenResponse); Mockito.when(roleRepository.findByNames(user.getRoles())).thenReturn(roles); var result = authenticationService.checkUserSessionAccess(user.getSessionId(), "/questions", "GET"); - Assertions.assertEquals(result, true); + Assertions.assertEquals(result, UserId); } @Test @@ -207,11 +208,13 @@ void givencheckUserSessionAccess_InValidSession(){ Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations); Mockito.when(redisTemplate.opsForValue().get(user.getSessionId()+"_profile")) .thenReturn(null); + Mockito.when(redisTemplate.opsForValue().get(user.getSessionId()+"_tokens")) + .thenReturn(null); Mockito.when(roleRepository.findByNames(user.getRoles())).thenReturn(roles); var result = authenticationService.checkUserSessionAccess(user.getSessionId(), "/questions", "GET"); - Assertions.assertEquals(result, false); + Assertions.assertEquals(result, ""); } @Test @@ -231,11 +234,13 @@ void givencheckUserSessionAccess_NoAccess(){ Mockito.when(redisTemplate.opsForValue()).thenReturn(valueOperations); Mockito.when(redisTemplate.opsForValue().get(user.getSessionId()+"_profile")) - .thenReturn(null); + .thenReturn(user); + Mockito.when(redisTemplate.opsForValue().get(user.getSessionId()+"_tokens")) + .thenReturn(tokenResponse); Mockito.when(roleRepository.findByNames(user.getRoles())).thenReturn(roles); var result = authenticationService.checkUserSessionAccess(user.getSessionId(), "/questions", "POST"); - Assertions.assertEquals(result, false); + Assertions.assertEquals(result, ""); } }