From 6137e59d58310b6863e149f9d7893b198106396c Mon Sep 17 00:00:00 2001 From: Calvin Date: Thu, 25 Jul 2024 01:31:58 +0800 Subject: [PATCH 1/3] [refactor] move code from AccountController to AccountService --- .../manager/controller/AccountController.java | 78 +++------------- .../manager/service/AccountService.java | 32 +++++++ .../service/impl/AccountServiceImpl.java | 93 +++++++++++++++++++ .../controller/AccountControllerTest.java | 19 +++- 4 files changed, 156 insertions(+), 66 deletions(-) create mode 100644 manager/src/main/java/org/apache/hertzbeat/manager/service/AccountService.java create mode 100644 manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AccountServiceImpl.java diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/controller/AccountController.java b/manager/src/main/java/org/apache/hertzbeat/manager/controller/AccountController.java index 54fdd517e82..29a53fbe5da 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/controller/AccountController.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/controller/AccountController.java @@ -19,25 +19,19 @@ import static org.apache.hertzbeat.common.constants.CommonConstants.MONITOR_LOGIN_FAILED_CODE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; -import com.usthe.sureness.provider.SurenessAccount; -import com.usthe.sureness.provider.SurenessAccountProvider; -import com.usthe.sureness.provider.ducument.DocumentAccountProvider; -import com.usthe.sureness.util.JsonWebTokenUtil; -import com.usthe.sureness.util.Md5Util; -import io.jsonwebtoken.Claims; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; -import java.util.HashMap; -import java.util.List; import java.util.Map; +import javax.naming.AuthenticationException; import lombok.extern.slf4j.Slf4j; import org.apache.hertzbeat.common.entity.dto.Message; -import org.apache.hertzbeat.common.util.JsonUtil; import org.apache.hertzbeat.manager.pojo.dto.LoginDto; import org.apache.hertzbeat.manager.pojo.dto.RefreshTokenResponse; +import org.apache.hertzbeat.manager.service.AccountService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -50,49 +44,21 @@ * Authentication registration TOKEN management API */ @Tag(name = "Auth Manage API") -@RestController() +@RestController @RequestMapping(value = "/api/account/auth", produces = {APPLICATION_JSON_VALUE}) @Slf4j public class AccountController { - /** - * Token validity time in seconds - */ - private static final long PERIOD_TIME = 3600L; - /** - * account data provider - */ - private SurenessAccountProvider accountProvider = new DocumentAccountProvider(); + @Autowired + private AccountService accountService; @PostMapping("/form") @Operation(summary = "Account password login to obtain associated user information", description = "Account password login to obtain associated user information") public ResponseEntity>> authGetToken(@Valid @RequestBody LoginDto loginDto) { - SurenessAccount account = accountProvider.loadAccount(loginDto.getIdentifier()); - if (account == null || account.getPassword() == null) { - return ResponseEntity.ok(Message.fail(MONITOR_LOGIN_FAILED_CODE, "Incorrect Account or Password")); - } else { - String password = loginDto.getCredential(); - if (account.getSalt() != null) { - password = Md5Util.md5(password + account.getSalt()); - } - if (!account.getPassword().equals(password)) { - return ResponseEntity.ok(Message.fail(MONITOR_LOGIN_FAILED_CODE, "Incorrect Account or Password")); - } - if (account.isDisabledAccount() || account.isExcessiveAttempts()) { - return ResponseEntity.ok(Message.fail(MONITOR_LOGIN_FAILED_CODE, "Expired or Illegal Account")); - } + try { + return ResponseEntity.ok(Message.success(accountService.authGetToken(loginDto))); + } catch (AuthenticationException e) { + return ResponseEntity.ok(Message.fail(MONITOR_LOGIN_FAILED_CODE, e.getMessage())); } - // Get the roles the user has - rbac - List roles = account.getOwnRoles(); - // Issue TOKEN - String issueToken = JsonWebTokenUtil.issueJwt(loginDto.getIdentifier(), PERIOD_TIME, roles); - Map customClaimMap = new HashMap<>(1); - customClaimMap.put("refresh", true); - String issueRefresh = JsonWebTokenUtil.issueJwt(loginDto.getIdentifier(), PERIOD_TIME << 5, customClaimMap); - Map resp = new HashMap<>(2); - resp.put("token", issueToken); - resp.put("refreshToken", issueRefresh); - resp.put("role", JsonUtil.toJson(roles)); - return ResponseEntity.ok(Message.success(resp)); } @GetMapping("/refresh/{refreshToken}") @@ -101,30 +67,12 @@ public ResponseEntity> refreshToken( @Parameter(description = "Refresh TOKEN", example = "xxx") @PathVariable("refreshToken") @NotNull final String refreshToken) { try { - Claims claims = JsonWebTokenUtil.parseJwt(refreshToken); - String userId = String.valueOf(claims.getSubject()); - boolean isRefresh = claims.get("refresh", Boolean.class); - if (userId == null || !isRefresh) { - return ResponseEntity.ok(Message.fail(MONITOR_LOGIN_FAILED_CODE, "Illegal Refresh Token")); - } - SurenessAccount account = accountProvider.loadAccount(userId); - if (account == null) { - return ResponseEntity.ok(Message.fail(MONITOR_LOGIN_FAILED_CODE, "Not Exists This Token Mapping Account")); - } - List roles = account.getOwnRoles(); - String issueToken = issueToken(userId, roles, PERIOD_TIME); - String issueRefresh = issueToken(userId, roles, PERIOD_TIME << 5); - RefreshTokenResponse response = new RefreshTokenResponse(issueToken, issueRefresh); - return ResponseEntity.ok(Message.success(response)); + return ResponseEntity.ok(Message.success(accountService.refreshToken(refreshToken))); + } catch (AuthenticationException e) { + return ResponseEntity.ok(Message.fail(MONITOR_LOGIN_FAILED_CODE, e.getMessage())); } catch (Exception e) { log.error("Exception occurred during token refresh: {}", e.getClass().getName(), e); return ResponseEntity.ok(Message.fail(MONITOR_LOGIN_FAILED_CODE, "Refresh Token Expired or Error")); } } - - private String issueToken(String userId, List roles, long expirationMillis) { - Map customClaimMap = new HashMap<>(1); - customClaimMap.put("refresh", true); - return JsonWebTokenUtil.issueJwt(userId, expirationMillis, roles, customClaimMap); - } } diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/AccountService.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/AccountService.java new file mode 100644 index 00000000000..993110cc115 --- /dev/null +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/AccountService.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.manager.service; + +import java.util.Map; +import javax.naming.AuthenticationException; +import org.apache.hertzbeat.manager.pojo.dto.LoginDto; +import org.apache.hertzbeat.manager.pojo.dto.RefreshTokenResponse; + +/** + * Account service + */ +public interface AccountService { + Map authGetToken(LoginDto loginDto) throws AuthenticationException; + + RefreshTokenResponse refreshToken(String refreshToken) throws AuthenticationException; +} diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AccountServiceImpl.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AccountServiceImpl.java new file mode 100644 index 00000000000..28455298066 --- /dev/null +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AccountServiceImpl.java @@ -0,0 +1,93 @@ +package org.apache.hertzbeat.manager.service.impl; + +import com.usthe.sureness.provider.SurenessAccount; +import com.usthe.sureness.provider.SurenessAccountProvider; +import com.usthe.sureness.provider.ducument.DocumentAccountProvider; +import com.usthe.sureness.util.JsonWebTokenUtil; +import com.usthe.sureness.util.Md5Util; +import io.jsonwebtoken.Claims; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.naming.AuthenticationException; +import lombok.extern.slf4j.Slf4j; +import org.apache.hertzbeat.common.util.JsonUtil; +import org.apache.hertzbeat.manager.pojo.dto.LoginDto; +import org.apache.hertzbeat.manager.pojo.dto.RefreshTokenResponse; +import org.apache.hertzbeat.manager.service.AccountService; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Service; + +/** + * Implementation of Account service + */ +@Service +@Order(value = Ordered.HIGHEST_PRECEDENCE) +@Slf4j +public class AccountServiceImpl implements AccountService { + /** + * Token validity time in seconds + */ + private static final long PERIOD_TIME = 3600L; + /** + * account data provider + */ + private final SurenessAccountProvider accountProvider = new DocumentAccountProvider(); + + @Override + public Map authGetToken(LoginDto loginDto) throws AuthenticationException { + SurenessAccount account = accountProvider.loadAccount(loginDto.getIdentifier()); + if (account == null || account.getPassword() == null) { + throw new AuthenticationException("Incorrect Account or Password"); + } else { + String password = loginDto.getCredential(); + if (account.getSalt() != null) { + password = Md5Util.md5(password + account.getSalt()); + } + if (!account.getPassword().equals(password)) { + throw new AuthenticationException("Incorrect Account or Password"); + } + if (account.isDisabledAccount() || account.isExcessiveAttempts()) { + throw new AuthenticationException("Expired or Illegal Account"); + } + } + // Get the roles the user has - rbac + List roles = account.getOwnRoles(); + // Issue TOKEN + String issueToken = JsonWebTokenUtil.issueJwt(loginDto.getIdentifier(), PERIOD_TIME, roles); + Map customClaimMap = new HashMap<>(1); + customClaimMap.put("refresh", true); + String issueRefresh = JsonWebTokenUtil.issueJwt(loginDto.getIdentifier(), PERIOD_TIME << 5, customClaimMap); + Map resp = new HashMap<>(2); + resp.put("token", issueToken); + resp.put("refreshToken", issueRefresh); + resp.put("role", JsonUtil.toJson(roles)); + + return resp; + } + + @Override + public RefreshTokenResponse refreshToken(String refreshToken) throws AuthenticationException { + Claims claims = JsonWebTokenUtil.parseJwt(refreshToken); + String userId = String.valueOf(claims.getSubject()); + boolean isRefresh = claims.get("refresh", Boolean.class); + if (userId == null || !isRefresh) { + throw new AuthenticationException("Illegal Refresh Token"); + } + SurenessAccount account = accountProvider.loadAccount(userId); + if (account == null) { + throw new AuthenticationException("Not Exists This Token Mapping Account"); + } + List roles = account.getOwnRoles(); + String issueToken = issueToken(userId, roles, PERIOD_TIME); + String issueRefresh = issueToken(userId, roles, PERIOD_TIME << 5); + return new RefreshTokenResponse(issueToken, issueRefresh); + } + + private String issueToken(String userId, List roles, long expirationMillis) { + Map customClaimMap = new HashMap<>(1); + customClaimMap.put("refresh", true); + return JsonWebTokenUtil.issueJwt(userId, expirationMillis, roles, customClaimMap); + } +} diff --git a/manager/src/test/java/org/apache/hertzbeat/manager/controller/AccountControllerTest.java b/manager/src/test/java/org/apache/hertzbeat/manager/controller/AccountControllerTest.java index 634c32a4ef3..cb50ea0e18b 100644 --- a/manager/src/test/java/org/apache/hertzbeat/manager/controller/AccountControllerTest.java +++ b/manager/src/test/java/org/apache/hertzbeat/manager/controller/AccountControllerTest.java @@ -20,13 +20,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.usthe.sureness.util.JsonWebTokenUtil; +import java.util.HashMap; +import java.util.Map; +import javax.naming.AuthenticationException; import org.apache.hertzbeat.common.constants.CommonConstants; import org.apache.hertzbeat.common.util.JsonUtil; import org.apache.hertzbeat.manager.pojo.dto.LoginDto; +import org.apache.hertzbeat.manager.service.AccountService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -43,6 +49,8 @@ class AccountControllerTest { @InjectMocks private AccountController accountController; + @Mock + private AccountService accountService; @BeforeEach void setUp() { @@ -59,6 +67,12 @@ void authGetToken() throws Exception { .identifier("admin") .credential("hertzbeat") .build(); + Map resp = new HashMap<>(2); + resp.put("token", "token"); + resp.put("refreshToken", "refreshToken"); + resp.put("role", "roles"); + Mockito.when(accountService.authGetToken(loginDto)).thenReturn(resp); + this.mockMvc.perform(MockMvcRequestBuilders.post("/api/account/auth/form") .contentType(MediaType.APPLICATION_JSON) .content(JsonUtil.toJson(loginDto))) @@ -67,6 +81,7 @@ void authGetToken() throws Exception { .andExpect(jsonPath("$.data.token").exists()) .andReturn(); loginDto.setCredential("wrong_credential"); + Mockito.when(accountService.authGetToken(loginDto)).thenThrow(new AuthenticationException()); this.mockMvc.perform(MockMvcRequestBuilders.post("/api/account/auth/form") .contentType(MediaType.APPLICATION_JSON) .content(JsonUtil.toJson(loginDto))) @@ -76,8 +91,10 @@ void authGetToken() throws Exception { @Test void refreshToken() throws Exception { + String refreshToken = "123456"; + Mockito.when(accountService.refreshToken(refreshToken)).thenThrow(new AuthenticationException()); this.mockMvc.perform(MockMvcRequestBuilders.get("/api/account/auth/refresh/{refreshToken}", - "123456")) + refreshToken)) .andExpect(jsonPath("$.code").value((int) CommonConstants.MONITOR_LOGIN_FAILED_CODE)) .andReturn(); } From 0edf710627734902dd71d2213a705989954a0595 Mon Sep 17 00:00:00 2001 From: Calvin Date: Fri, 26 Jul 2024 00:08:30 +0800 Subject: [PATCH 2/3] [refactor] add apache license in AccountServiceImpl --- .../service/impl/AccountServiceImpl.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AccountServiceImpl.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AccountServiceImpl.java index 28455298066..e59a18f9560 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AccountServiceImpl.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/impl/AccountServiceImpl.java @@ -1,3 +1,20 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.hertzbeat.manager.service.impl; import com.usthe.sureness.provider.SurenessAccount; From 079dc262fd9416f4cd1452b1f2be8febe4c879f1 Mon Sep 17 00:00:00 2001 From: Calvin Date: Fri, 26 Jul 2024 21:45:23 +0800 Subject: [PATCH 3/3] [refactor] add comment in AccountService --- .../hertzbeat/manager/service/AccountService.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/manager/src/main/java/org/apache/hertzbeat/manager/service/AccountService.java b/manager/src/main/java/org/apache/hertzbeat/manager/service/AccountService.java index 993110cc115..9a47f80c13b 100644 --- a/manager/src/main/java/org/apache/hertzbeat/manager/service/AccountService.java +++ b/manager/src/main/java/org/apache/hertzbeat/manager/service/AccountService.java @@ -26,7 +26,19 @@ * Account service */ public interface AccountService { + /** + * Account password login to obtain associated user information + * @param loginDto loginDto + * @return token info + * @throws AuthenticationException when authentication is failed + */ Map authGetToken(LoginDto loginDto) throws AuthenticationException; + /** + * Use refresh TOKEN to re-acquire TOKEN + * @param refreshToken refreshToken + * @return token and refresh token + * @throws AuthenticationException failed to refresh + */ RefreshTokenResponse refreshToken(String refreshToken) throws AuthenticationException; }