From 8dfc541255841423791ed5a2947d9e35ee729884 Mon Sep 17 00:00:00 2001 From: zwjsec Date: Wed, 10 Jul 2024 14:29:01 +0800 Subject: [PATCH] =?UTF-8?q?=E3=80=90feature=E3=80=91=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E4=B8=8E=E6=9D=83=E9=99=90=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 9 +- .../query/ApplicationVersionQueryAdapter.java | 5 + .../adapter/query/UserAdapter.java | 109 +++++++++++++++ .../common/account/StpInterfaceImpl.java | 124 ++++++++++++++++++ .../common/constant/HttpConstant.java | 64 +++++++++ .../common/entity/MessageCode.java | 15 ++- .../exception/GlobalExceptionHandler.java | 41 ++++++ .../exception/HttpRequestException.java | 31 +++++ .../common/utils/HttpClientUtil.java | 118 +++++++++++++++++ .../com/easysoftware/redis/RedisGateway.java | 10 ++ 10 files changed, 522 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/easysoftware/adapter/query/UserAdapter.java create mode 100644 src/main/java/com/easysoftware/common/account/StpInterfaceImpl.java create mode 100644 src/main/java/com/easysoftware/common/constant/HttpConstant.java create mode 100644 src/main/java/com/easysoftware/common/exception/HttpRequestException.java create mode 100644 src/main/java/com/easysoftware/common/utils/HttpClientUtil.java diff --git a/pom.xml b/pom.xml index d3e2c72..b089613 100644 --- a/pom.xml +++ b/pom.xml @@ -31,8 +31,13 @@ redis.clients jedis 3.10.0 - --> - + --> + + cn.dev33 + sa-token-spring-boot3-starter + 1.38.0 + + org.springframework.boot spring-boot-starter-data-redis diff --git a/src/main/java/com/easysoftware/adapter/query/ApplicationVersionQueryAdapter.java b/src/main/java/com/easysoftware/adapter/query/ApplicationVersionQueryAdapter.java index fae4df8..b378670 100644 --- a/src/main/java/com/easysoftware/adapter/query/ApplicationVersionQueryAdapter.java +++ b/src/main/java/com/easysoftware/adapter/query/ApplicationVersionQueryAdapter.java @@ -21,6 +21,7 @@ import com.easysoftware.application.applicationversion.dto.ApplicationColumnSear import com.easysoftware.application.applicationversion.dto.ApplicationVersionSearchCondition; import com.easysoftware.common.aop.RequestLimitRedis; +import cn.dev33.satoken.stp.StpUtil; import jakarta.validation.Valid; @RestController @@ -42,6 +43,8 @@ public class ApplicationVersionQueryAdapter { @GetMapping() @RequestLimitRedis() public ResponseEntity searchAppVersion(@Valid final ApplicationVersionSearchCondition condition) { + // 检查会话权限 + StpUtil.checkPermission("easysoftwareread"); return appVersionService.searchAppVersion(condition); } @@ -55,6 +58,8 @@ public class ApplicationVersionQueryAdapter { @GetMapping("/column") @RequestLimitRedis() public ResponseEntity searchAppVerColumn(@Valid final ApplicationColumnSearchCondition condition) { + // 检查会话权限 + StpUtil.checkPermission("easysoftwareread"); return appVersionService.searchAppVerColumn(condition); } } diff --git a/src/main/java/com/easysoftware/adapter/query/UserAdapter.java b/src/main/java/com/easysoftware/adapter/query/UserAdapter.java new file mode 100644 index 0000000..7f9c320 --- /dev/null +++ b/src/main/java/com/easysoftware/adapter/query/UserAdapter.java @@ -0,0 +1,109 @@ +package com.easysoftware.adapter.query; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.easysoftware.common.constant.HttpConstant; +import com.easysoftware.common.utils.ResultUtil; + +import jakarta.servlet.http.Cookie; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import cn.dev33.satoken.exception.NotLoginException; + +import cn.dev33.satoken.stp.StpUtil; +import com.easysoftware.redis.RedisGateway; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.Arrays; + +@RestController +@RequestMapping("/user/") +public class UserAdapter { + /** + * Value injected for the cookie token name. + */ + @Value("${cookie.token.name}") + private String cookieTokenName; + + /** + * Autowired redisGateway. + */ + @Autowired + private RedisGateway redisGateway; + + /** + * Verify login status from oneid, and maintain session. + * + * @param httpServletRequest request of https. + * @return ResponseEntity. + */ + @RequestMapping("verifyLogin") + public ResponseEntity doLogin(final HttpServletRequest httpServletRequest) { + + // 校验是否真正登录oneid + // 校验cookie是否正确设置 + Cookie tokenCookie = verifyCookie(httpServletRequest); + if (tokenCookie == null) { + throw new NotLoginException("oneid unloggin, missing cookie", "", ""); + } + // 校验userToken + String userToken = httpServletRequest.getHeader(HttpConstant.TOKEN); + if (userToken == null) { + throw new NotLoginException("oneid unloggin, missing token", "", ""); + } + + // 用户已经真正登录oneid 以usertoken登录 并维持会话 + // 设置用户会话token + StpUtil.login(userToken); + redisGateway.setWithExpire(userToken, userToken, 300, TimeUnit.SECONDS); + + return ResultUtil.success(HttpStatus.OK); + } + + /** + * Logout and detele session. + * + * @param httpServletRequest request of https. + * @return ResponseEntity. + */ + @RequestMapping("logout") + public ResponseEntity logout(final HttpServletRequest httpServletRequest) { + // 用户退出 删除token信息 + String userToken = httpServletRequest.getHeader(HttpConstant.TOKEN); + + if (redisGateway.hasKey(userToken)) { + redisGateway.detele(userToken); + } + + StpUtil.logout(); + + return ResultUtil.success(HttpStatus.OK); + } + + /** + * Verifies and retrieves a cookie from the HttpServletRequest. + * + * @param httpServletRequest The HTTP servlet request + * @return The verified Cookie object + */ + private Cookie verifyCookie(final HttpServletRequest httpServletRequest) { + Cookie[] cookies = httpServletRequest.getCookies(); + Cookie cookie = null; + if (cookies != null) { + // 获取cookie中的token + Optional first = Arrays.stream(cookies).filter(c -> cookieTokenName.equals(c.getName())) + .findFirst(); + if (first.isPresent()) { + cookie = first.get(); + } + } + return cookie; + } + +} diff --git a/src/main/java/com/easysoftware/common/account/StpInterfaceImpl.java b/src/main/java/com/easysoftware/common/account/StpInterfaceImpl.java new file mode 100644 index 0000000..f9d27e3 --- /dev/null +++ b/src/main/java/com/easysoftware/common/account/StpInterfaceImpl.java @@ -0,0 +1,124 @@ +/* Copyright (c) 2024 openEuler Community + EasySoftware is licensed under the Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. +*/ + +package com.easysoftware.common.account; + +import java.util.ArrayList; +import java.util.List; + +import cn.dev33.satoken.exception.NotLoginException; +import cn.dev33.satoken.stp.StpInterface; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import com.easysoftware.common.exception.HttpRequestException; +import com.easysoftware.common.utils.HttpClientUtil; +import com.easysoftware.common.utils.ObjectMapperUtil; +import com.easysoftware.redis.RedisGateway; +import com.fasterxml.jackson.databind.JsonNode; + +@Component +public class StpInterfaceImpl implements StpInterface { + /** + * Value injected for the manage token API. + */ + @Value("${oneid.manage.tokenApi}") + private String manageTokenApi; + + /** + * Value injected for the manage API body. + */ + @Value("${oneid.manage.apiBody}") + private String manageApiBody; + + /** + * Value injected for the permission API. + */ + @Value("${oneid.permissionApi}") + private String permissionApi; + + /** + * Value injected for the cookie token name. + */ + @Value("${cookie.token.name}") + private String cookieTokenName; + + /** + * Autowired redisGateway. + */ + @Autowired + private RedisGateway redisGateway; + + /** + * Get permission from oneid. + * + * @param loginId the login id of user. + * @param loginType the type of login. + * @return ResponseEntity. + */ + @Override + public List getPermissionList(Object loginId, String loginType) { + + // 获取oneid manage token + String manageToken = getManageToken(); + + // 使用loginid 从redis中获取userToken + + if (!redisGateway.hasKey((String) loginId)) { + throw new NotLoginException("user token expired", "", ""); + } + String userToken = redisGateway.get((String) loginId); + + // 使用userToken manageToken查询用户权限 + String response = HttpClientUtil.getHttpClient(permissionApi, manageToken, userToken, userToken); + JsonNode resJson = ObjectMapperUtil.toJsonNode(response); + + String resStaus = resJson.get("status").asText(); + // 查询权限失败 + if (!resStaus.equals("200")) { + throw new HttpRequestException("query oneid failed"); + } + + // 空权限账户 + if (!resJson.has("data")) { + return new ArrayList(); + } + + // 设置权限 + JsonNode permissions = resJson.get("data").get("permissions"); + List list = new ArrayList(); + for (JsonNode per : permissions) { + list.add(per.asText()); + } + return list; + } + + /** + * Get role list from user. + * + * @param loginId the login id of user. + * @param loginType the type of login. + * @return ResponseEntity. + */ + @Override + public List getRoleList(Object loginId, String loginType) { + List list = new ArrayList(); + return list; + } + + private String getManageToken() { + String response = HttpClientUtil.postHttpClient(manageTokenApi, manageApiBody); + JsonNode resJson = ObjectMapperUtil.toJsonNode(response); + return resJson.get("token").asText(); + } + +} diff --git a/src/main/java/com/easysoftware/common/constant/HttpConstant.java b/src/main/java/com/easysoftware/common/constant/HttpConstant.java new file mode 100644 index 0000000..19a458c --- /dev/null +++ b/src/main/java/com/easysoftware/common/constant/HttpConstant.java @@ -0,0 +1,64 @@ +/* Copyright (c) 2024 openEuler Community + EasySoftware is licensed under the Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. +*/ +package com.easysoftware.common.constant; + +public final class HttpConstant { + // Private constructor to prevent instantiation of the HttpConstant class + private HttpConstant() { + // private constructor to hide the implicit public one + throw new AssertionError("HttpConstant class cannot be instantiated."); + } + + /** + * Timeout duration in milliseconds. + */ + public static final int TIME_OUT = 5000; + + /** + * HTTP GET method. + */ + public static final String GET = "GET"; + + /** + * HTTP POST method. + */ + public static final String POST = "POST"; + + /** + * Token key. + */ + public static final String TOKEN = "token"; + + /** + * Cookie key. + */ + public static final String COOKIE = "Cookie"; + + /** + * User token key. + */ + public static final String USER_TOKEN = "user-token"; + + /** + * Content type key. + */ + public static final String CONTENT_TYPE = "Content-Type"; + + /** + * http protol type key. + */ + public static final String HTTP_PROTOL = "http"; + + /** + * https protol type key. + */ + public static final String HTTPS_PROTOL = "https"; +} diff --git a/src/main/java/com/easysoftware/common/entity/MessageCode.java b/src/main/java/com/easysoftware/common/entity/MessageCode.java index 0941fc4..4b38d1b 100644 --- a/src/main/java/com/easysoftware/common/entity/MessageCode.java +++ b/src/main/java/com/easysoftware/common/entity/MessageCode.java @@ -119,6 +119,19 @@ public enum MessageCode { * Chinese: key删除错误. */ EC00017("EC00017", "Redis key delete failed", "key删除错误"), + + /** + * Error code EC00018: The user is not logged on. + * Chinese: 用户未登录. + */ + EC00018("EC00018", "The user is not logged on", "用户未登录"), + + /** + * Error code EC00019: Unauyhorized. + * Chinese: 权限不足. + */ + EC00019("EC00019", "Unauthorized ", "权限不足"), + /** * Error code ES0001: Internal Server Error. * Chinese: 服务异常. @@ -126,7 +139,6 @@ public enum MessageCode { // self service exception ES0001("ES0001", "Internal Server Error", "服务异常"); - /** * Error code. */ @@ -142,7 +154,6 @@ public enum MessageCode { */ private final String msgZh; - /** * Constructor for MessageCode enum. * diff --git a/src/main/java/com/easysoftware/common/exception/GlobalExceptionHandler.java b/src/main/java/com/easysoftware/common/exception/GlobalExceptionHandler.java index 8972bd5..0bb490d 100644 --- a/src/main/java/com/easysoftware/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/easysoftware/common/exception/GlobalExceptionHandler.java @@ -23,6 +23,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import cn.dev33.satoken.exception.NotPermissionException; +import cn.dev33.satoken.exception.NotLoginException; @RestControllerAdvice public class GlobalExceptionHandler { @@ -83,6 +85,45 @@ public class GlobalExceptionHandler { return ResultUtil.fail(HttpStatus.NOT_FOUND, MessageCode.EC0009.getMsgEn()); } + /** + * Handles exceptions of type HttpRequestException. + * + * @param e The HttpRequestException to handle + * @return ResponseEntity containing details about the exception + */ + @ExceptionHandler(HttpRequestException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ResponseEntity exception(final HttpRequestException e) { + LOGGER.error(e.getMessage()); + return ResultUtil.fail(HttpStatus.NOT_FOUND, MessageCode.EC0001); + } + + /** + * Handles exceptions of type NotPermissionException. + * + * @param e The NotPermissionException to handle + * @return ResponseEntity containing details about the exception + */ + @ExceptionHandler(NotPermissionException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ResponseEntity exception(final NotPermissionException e) { + LOGGER.error(e.getMessage()); + return ResultUtil.fail(HttpStatus.FORBIDDEN, MessageCode.EC00019); + } + + /** + * Handles exceptions of type NotLoginException. + * + * @param e The NotLoginException to handle + * @return ResponseEntity containing details about the exception + */ + @ExceptionHandler(NotLoginException.class) + @ResponseStatus(HttpStatus.FORBIDDEN) + public ResponseEntity exception(final NotLoginException e) { + LOGGER.error(e.getMessage()); + return ResultUtil.fail(HttpStatus.FORBIDDEN, MessageCode.EC00018); + } + /** * Handles exceptions of type AppPkgIconException. * diff --git a/src/main/java/com/easysoftware/common/exception/HttpRequestException.java b/src/main/java/com/easysoftware/common/exception/HttpRequestException.java new file mode 100644 index 0000000..d9f07fd --- /dev/null +++ b/src/main/java/com/easysoftware/common/exception/HttpRequestException.java @@ -0,0 +1,31 @@ +/* Copyright (c) 2024 openEuler Community + EasySoftware is licensed under the Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. +*/ +package com.easysoftware.common.exception; + +import java.io.Serial; + +public class HttpRequestException extends RuntimeException { + /** + * Serial version UID for serialization. + */ + @Serial + private static final long serialVersionUID = 1L; + + /** + * Constructor for HttpRequestException with a message. + * + * @param message The exception message + */ + public HttpRequestException(final String message) { + super(message); + } + +} diff --git a/src/main/java/com/easysoftware/common/utils/HttpClientUtil.java b/src/main/java/com/easysoftware/common/utils/HttpClientUtil.java new file mode 100644 index 0000000..4143c61 --- /dev/null +++ b/src/main/java/com/easysoftware/common/utils/HttpClientUtil.java @@ -0,0 +1,118 @@ +/* Copyright (c) 2024 openEuler Community + EasySoftware is licensed under the Mulan PSL v2. + You can use this software according to the terms and conditions of the Mulan PSL v2. + You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 + THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + See the Mulan PSL v2 for more details. +*/ + +package com.easysoftware.common.utils; + +import java.io.IOException; +import java.net.URL; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.easysoftware.common.constant.HttpConstant; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; + +public final class HttpClientUtil { + // Private constructor to prevent instantiation of the utility class + private HttpClientUtil() { + // private constructor to hide the implicit public one + throw new AssertionError("HttpClientUtil class cannot be instantiated."); + } + + /** + * Logger for HttpClientUtil. + */ + private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtil.class); + + /** + * Request configuration with timeout settings. + */ + private static final RequestConfig REQUEST_CONFIG = RequestConfig.custom() + .setConnectTimeout(HttpConstant.TIME_OUT) + .setSocketTimeout(HttpConstant.TIME_OUT).build(); + + /** + * Get an HTTP client with specified parameters. + * + * @param uri The URI for the HTTP client. + * @param token The token to include in the request. + * @param userToken The user token to include in the request. + * @param cookie The cookie value to include in the request. + * @return The HTTP client as a string. + */ + public static String getHttpClient(final String uri, final String token, + final String userToken, final String cookie) { + HttpClient httpClient = HttpClients.createDefault(); + HttpGet httpGet = new HttpGet(uri); + httpGet.setConfig(REQUEST_CONFIG); + + if (token != null) { + httpGet.addHeader(HttpConstant.TOKEN, token); + } + if (userToken != null) { + httpGet.addHeader(HttpConstant.USER_TOKEN, userToken); + } + if (cookie != null) { + httpGet.addHeader(HttpConstant.COOKIE, "_Y_G_=" + cookie); + } + String responseRes = ""; + try { + HttpResponse response = httpClient.execute(httpGet); + responseRes = EntityUtils.toString(response.getEntity()); + } catch (IOException e) { + LOGGER.error("error haapend in get request"); + } + return responseRes; + } + + /** + * Send a POST request using an HTTP client to the specified URI with the given + * request body. + * + * @param uri The URI for the POST request. + * @param requestBody The body of the POST request. + * @return The response from the POST request as a string. + */ + public static String postHttpClient(final String uri, final String requestBody) { + HttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(uri); + httpPost.setConfig(REQUEST_CONFIG); + String responseRes = ""; + try { + httpPost.setHeader(HttpConstant.CONTENT_TYPE, "application/json"); + StringEntity stringEntity = new StringEntity(requestBody); + httpPost.setEntity(stringEntity); + HttpResponse response = httpClient.execute(httpPost); + responseRes = EntityUtils.toString(response.getEntity()); + } catch (IOException e) { + LOGGER.error("error haapend in post request"); + } + return responseRes; + } + + /** + * Perform a security check for SSRF on the provided URL. + * + * @param url The URL to check for SSRF. + * @return Boolean value indicating the SSRF security check result. + */ + // ssrf检查,whitelist todo + private static Boolean sercuritySSRFUrlCheck(final URL url) { + return url.getProtocol().startsWith(HttpConstant.HTTP_PROTOL) + || url.getProtocol().startsWith(HttpConstant.HTTPS_PROTOL); + } +} diff --git a/src/main/java/com/easysoftware/redis/RedisGateway.java b/src/main/java/com/easysoftware/redis/RedisGateway.java index aeef2da..fd5548e 100644 --- a/src/main/java/com/easysoftware/redis/RedisGateway.java +++ b/src/main/java/com/easysoftware/redis/RedisGateway.java @@ -35,6 +35,16 @@ public class RedisGateway { stringRedisTemplate.opsForValue().set(key, value); } + /** + * delete a key from Redis. + * + * @param key The key to set. + * + */ + public void detele(final String key) { + stringRedisTemplate.delete(key); + } + /** * Get the value associated with a key in Redis. * -- Gitee