diff --git a/pom.xml b/pom.xml index 169f8ac8569cea1ea402168e4982652930242087..f1b66f010bcc2042d26bb5fef37f0bf2e21ad98e 100644 --- a/pom.xml +++ b/pom.xml @@ -123,6 +123,12 @@ 4.5.13 + + com.auth0 + java-jwt + 3.4.0 + + diff --git a/src/main/java/com/easysoftware/adapter/MyErrorController.java b/src/main/java/com/easysoftware/adapter/MyErrorController.java deleted file mode 100644 index 26ae0438b4d2281020222bcd35e6e89858c6612d..0000000000000000000000000000000000000000 --- a/src/main/java/com/easysoftware/adapter/MyErrorController.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.easysoftware.adapter; - -import org.springframework.boot.web.servlet.error.ErrorController; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import com.easysoftware.common.entity.MessageCode; -import com.easysoftware.common.utils.ResultUtil; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@RestController -public class MyErrorController implements ErrorController{ - private final String ERROR_PATH = "/error"; - - @RequestMapping(value = ERROR_PATH) - public ResponseEntity errorHtml(HttpServletRequest request, HttpServletResponse response) { - return ResultUtil.fail(HttpStatus.NOT_FOUND, MessageCode.EC0001); - } -} - diff --git a/src/main/java/com/easysoftware/adapter/query/ApplicationVersionQueryAdapter.java b/src/main/java/com/easysoftware/adapter/query/ApplicationVersionQueryAdapter.java index 7529b04e88f4912bfdbea25809f3584e6b461b36..a60d9d28e7ad70866b8c2f6adb7b9cc101cabeb6 100644 --- a/src/main/java/com/easysoftware/adapter/query/ApplicationVersionQueryAdapter.java +++ b/src/main/java/com/easysoftware/adapter/query/ApplicationVersionQueryAdapter.java @@ -9,6 +9,8 @@ import org.springframework.web.bind.annotation.RestController; import com.easysoftware.aop.LimitRequest; import com.easysoftware.application.applicationversion.ApplicationVersionService; import com.easysoftware.application.applicationversion.dto.ApplicationVersionSearchCondition; +import com.easysoftware.common.interceptor.CompatibleToken; +import com.easysoftware.common.interceptor.OneidToken; import jakarta.validation.Valid; @@ -20,6 +22,8 @@ public class ApplicationVersionQueryAdapter { @GetMapping() @LimitRequest() + @OneidToken + @CompatibleToken public ResponseEntity searchAppVersion(@Valid ApplicationVersionSearchCondition condition) { ResponseEntity res = appVersionService.searchAppVersion(condition); return res; diff --git a/src/main/java/com/easysoftware/common/config/OneidInterceptorConfig.java b/src/main/java/com/easysoftware/common/config/OneidInterceptorConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..f433af0271c00e780e7003686b7f659ac7d33361 --- /dev/null +++ b/src/main/java/com/easysoftware/common/config/OneidInterceptorConfig.java @@ -0,0 +1,22 @@ +package com.easysoftware.common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import com.easysoftware.common.interceptor.OneidInterceptor; + +@Configuration +public class OneidInterceptorConfig implements WebMvcConfigurer { + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(OneidInterceptor()) + .addPathPatterns("/appVersion/**"); + } + + @Bean + public OneidInterceptor OneidInterceptor() { + return new OneidInterceptor(); + } +} diff --git a/src/main/java/com/easysoftware/common/entity/MessageCode.java b/src/main/java/com/easysoftware/common/entity/MessageCode.java index 7120984286c88850261fcb747badf863d55184e2..44f03303e1949ad306a8f0da94662c87ce5e939d 100644 --- a/src/main/java/com/easysoftware/common/entity/MessageCode.java +++ b/src/main/java/com/easysoftware/common/entity/MessageCode.java @@ -28,6 +28,7 @@ public enum MessageCode { EC0009("EC0009", "Item not existed", "项目不存在"), EC00010("EC00010", "Request exceeds the limit", "请求超过限制"), EC00011("EC00011", "Failed to retrieve field using reflection", "无法通过反射获取字段"), + EC00012("EC00012", "Unauthorized", "身份认证失败"), // self service exception ES0001("ES0001", "Internal Server Error", "服务异常"); diff --git a/src/main/java/com/easysoftware/common/exception/AuthException.java b/src/main/java/com/easysoftware/common/exception/AuthException.java new file mode 100644 index 0000000000000000000000000000000000000000..07a44c8a24d0176cd6e8760791a5a43361ce33a4 --- /dev/null +++ b/src/main/java/com/easysoftware/common/exception/AuthException.java @@ -0,0 +1,12 @@ +package com.easysoftware.common.exception; + +public class AuthException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public AuthException(String message) { + super(message); + } + + public AuthException() { + } +} \ No newline at end of file diff --git a/src/main/java/com/easysoftware/common/exception/GlobalExceptionHandler.java b/src/main/java/com/easysoftware/common/exception/GlobalExceptionHandler.java index b145e2b79ff89a4a60688fad8247a8419171609f..1424f2a1fe0c605c161ffba8052c12c89c96104a 100644 --- a/src/main/java/com/easysoftware/common/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/easysoftware/common/exception/GlobalExceptionHandler.java @@ -14,7 +14,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -@RestControllerAdvice() +@RestControllerAdvice public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); @@ -38,4 +38,23 @@ public class GlobalExceptionHandler { logger.error(MessageCode.EC0009.getMsgEn()); return ResultUtil.fail(HttpStatus.BAD_REQUEST, MessageCode.EC0009); } + + @ExceptionHandler(AuthException.class) + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public ResponseEntity exception(AuthException e) { + logger.error(e.getMessage()); + return ResultUtil.fail(HttpStatus.UNAUTHORIZED, MessageCode.EC00012, e.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ResponseEntity exception(Exception e) { + return ResultUtil.fail(HttpStatus.INTERNAL_SERVER_ERROR, MessageCode.ES0001); + } + + @ExceptionHandler(RuntimeException.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public ResponseEntity exception(RuntimeException e) { + return ResultUtil.fail(HttpStatus.INTERNAL_SERVER_ERROR, MessageCode.ES0001, e.getMessage()); + } } diff --git a/src/main/java/com/easysoftware/common/interceptor/CompatibleToken.java b/src/main/java/com/easysoftware/common/interceptor/CompatibleToken.java new file mode 100644 index 0000000000000000000000000000000000000000..c3df6908903882f3d4a692e4bb6c4d22bb7518d5 --- /dev/null +++ b/src/main/java/com/easysoftware/common/interceptor/CompatibleToken.java @@ -0,0 +1,12 @@ +package com.easysoftware.common.interceptor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface CompatibleToken { + boolean required() default true; +} diff --git a/src/main/java/com/easysoftware/common/interceptor/OneidInterceptor.java b/src/main/java/com/easysoftware/common/interceptor/OneidInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..d215237998677c48f3c6bf362bd6a2d8914e72e8 --- /dev/null +++ b/src/main/java/com/easysoftware/common/interceptor/OneidInterceptor.java @@ -0,0 +1,304 @@ +package com.easysoftware.common.interceptor; + +import java.lang.reflect.Method; +import java.security.interfaces.RSAPrivateKey; +import java.util.*; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTDecodeException; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.easysoftware.common.exception.AuthException; +import com.easysoftware.common.utils.HttpClientUtil; +import com.easysoftware.common.utils.ObjectMapperUtil; +import com.fasterxml.jackson.databind.JsonNode; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.SneakyThrows; + +import org.apache.commons.lang3.StringUtils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.DigestUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +public class OneidInterceptor implements HandlerInterceptor { + + @Value("${cookie.token.name}") + private String cookieTokenName; + + // @Value("${cookie.token.domains}") + // private String allowDomains; + + // @Value("${cookie.token.secures}") + // private String cookieSecures; + + // @Value("${oneid.tokenBasePassword}") + // private String oneidTokenBasePassword; + + @Value("${oneid.permissionApi}") + private String permissionApi; + + // @Value("${rsa.authing.privateKey}") + // private String rsaAuthingPrivateKey; + + @Value("${oneid.manage.apiBody}") + private String manageApiBody; + + @Value("${oneid.manage.tokenApi}") + private String manageTokenApi; + + private static final Logger logger = LoggerFactory.getLogger(OneidInterceptor.class); + + @Override + public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, + Object object) throws Exception { + if (!(object instanceof HandlerMethod)) { + return true; + } + + // 检查是否有用户权限的注解 + HandlerMethod handlerMethod = (HandlerMethod) object; + Method method = handlerMethod.getMethod(); + if (!method.isAnnotationPresent(OneidToken.class) && !method.isAnnotationPresent(CompatibleToken.class)) { + return true; + } + OneidToken oneidToken = method.getAnnotation(OneidToken.class); + CompatibleToken compatibleToken = method.getAnnotation(CompatibleToken.class); + if ((oneidToken == null || !oneidToken.required()) + && (compatibleToken == null || !compatibleToken.required())) { + return true; + } + + // String headerToken = httpServletRequest.getHeader("token"); + // String headJwtTokenMd5 = verifyHeaderToken(headerToken); + // if (StringUtils.isBlank(headJwtTokenMd5) || headJwtTokenMd5.equals("unauthorized") ) { + // throw new AuthException("unauthorized"); + // } + + // // 校验domain + // String verifyDomainMsg = verifyDomain(httpServletRequest); + // if (!verifyDomainMsg.equals("success")) { + // throw new AuthException(verifyDomainMsg); + // } + + // 校验cookie + Cookie tokenCookie = verifyCookie(httpServletRequest); + if (tokenCookie == null) { + throw new AuthException("unauthorized"); + } + + // // 解密cookie中加密的token + // String token = tokenCookie.getValue(); + // try { + // RSAPrivateKey privateKey = RSAUtil.getPrivateKey(rsaAuthingPrivateKey); + // token = RSAUtil.privateDecrypt(token, privateKey); + // } catch (Exception e) { + // throw new AuthException("unauthorized"); + // } + + // // 解析token + // String userId; + // Date issuedAt; + // Date expiresAt; + // String permission; + // String verifyToken; + // try { + // DecodedJWT decode = JWT.decode(token); + // userId = decode.getAudience().get(0); + // issuedAt = decode.getIssuedAt(); + // expiresAt = decode.getExpiresAt(); + // String permissionTemp = decode.getClaim("permission").asString(); + // permission = new String(Base64.getDecoder().decode(permissionTemp.getBytes())); + // verifyToken = decode.getClaim("verifyToken").asString(); + // } catch (JWTDecodeException j) { + // throw new AuthException("token error"); + // } + + // // 校验token + // String verifyTokenMsg = verifyToken(headJwtTokenMd5, token, verifyToken, userId, issuedAt, expiresAt, + // permission); + // logger.info(verifyTokenMsg); + // if (!verifyTokenMsg.equals("success")) { + // throw new AuthException(verifyTokenMsg); + // } + + // 校验查看版本兼容页面的权限 + if (compatibleToken != null && compatibleToken.required()) { + String verifyUserMsg = verifyUser(compatibleToken, httpServletRequest, tokenCookie); + if (!verifyUserMsg.equals("success")) { + throw new AuthException(verifyUserMsg); + } + } + + return true; + } + + @Override + public void postHandle(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, + Object o, ModelAndView modelAndView) throws Exception { + + } + + @Override + public void afterCompletion(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, + Object o, Exception e) throws Exception { + } + + // private String verifyToken(String headerToken, String token, String verifyToken, + // String userId, Date issuedAt, Date expiresAt, String permission) { + // try { + // if (!headerToken.equals(verifyToken)) { + // return "unauthorized"; + // } + + // if (expiresAt.before(new Date())) { + // return "token expires"; + // } + + // // token 签名密码验证 + // String password = permission + oneidTokenBasePassword; + // JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(password)).build(); + // jwtVerifier.verify(token); + + // } catch (Exception e) { + // return "unauthorized"; + // } + // return "success"; + // } + + // /** + // * 校验header中的token + // * + // * @param headerToken header中的token + // * @return 校验正确返回token的MD5值 + // */ + // private String verifyHeaderToken(String headerToken) { + // try { + // if (StringUtils.isBlank(headerToken)) { + // return "unauthorized"; + // } + + // // token 签名密码验证 + // String password = oneidTokenBasePassword; + // JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(password)).build(); + // jwtVerifier.verify(headerToken); + // return DigestUtils.md5DigestAsHex(headerToken.getBytes()); + // } catch (Exception e) { + // logger.error("verify headertoken exception", e); + // return "unauthorized"; + // } + // } + + /** + * 校验用户操作权限 + */ + private String verifyUser(CompatibleToken compatibleToken, HttpServletRequest httpServletRequest, + Cookie tokenCookie) { + try { + if (compatibleToken != null && compatibleToken.required()) { + List pers = getUserPermission(httpServletRequest, tokenCookie); + for (String per : pers) { + if (per.equalsIgnoreCase("easysoftwareread")) { + return "success"; + } + } + } + } catch (Exception e) { + logger.error("verify user exception", e); + throw new RuntimeException(); + } + return "No permission"; + } + + @SneakyThrows + private List getUserPermission(HttpServletRequest httpServletRequest, Cookie tokenCookie) { + String token = getManageToken(); + String userToken = httpServletRequest.getHeader("token"); + String tokenCookieValue = tokenCookie.getValue(); + String response = HttpClientUtil.getHttpClient(permissionApi, token, userToken, tokenCookieValue); + JsonNode res = ObjectMapperUtil.toJsonNode(response); + JsonNode permissions = res.get("data").get("permissions"); + List list = new ArrayList<>(); + for (JsonNode per : permissions) { + list.add(per.asText()); + } + return list; + } + + @SneakyThrows + private String getManageToken() { + String response = HttpClientUtil.postHttpClient(manageTokenApi, manageApiBody); + JsonNode res = ObjectMapperUtil.toJsonNode(response); + return res.get("token").asText(); + } + + /** + * 获取包含存token的cookie + * + * @param httpServletRequest request + * @return cookie + */ + private Cookie verifyCookie(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; + } + + // /** + // * 校验domain + // * + // * @param httpServletRequest request + // * @return 是否可访问 + // */ + // private String verifyDomain(HttpServletRequest httpServletRequest) { + // String referer = httpServletRequest.getHeader("referer"); + // String origin = httpServletRequest.getHeader("origin"); + // String[] domains = allowDomains.split(";"); + + // boolean checkReferer = checkDomain(domains, referer); + // boolean checkOrigin = checkDomain(domains, origin); + + // if (!checkReferer && !checkOrigin) { + // return "unauthorized"; + // } + // return "success"; + // } + + // private boolean checkDomain(String[] domains, String input) { + // if (StringUtils.isBlank(input)) + // return true; + // int fromIndex; + // int endIndex; + // if (input.startsWith("http://")) { + // fromIndex = 7; + // endIndex = input.indexOf(":", fromIndex); + // } else { + // fromIndex = 8; + // endIndex = input.indexOf("/", fromIndex); + // endIndex = endIndex == -1 ? input.length() : endIndex; + // } + // String substring = input.substring(0, endIndex); + // for (String domain : domains) { + // if (substring.endsWith(domain)) + // return true; + // } + // return false; + // } + +} \ No newline at end of file diff --git a/src/main/java/com/easysoftware/common/interceptor/OneidToken.java b/src/main/java/com/easysoftware/common/interceptor/OneidToken.java new file mode 100644 index 0000000000000000000000000000000000000000..eb58dccdbdc6d390c4f2a8db1f45eb135b6d1e91 --- /dev/null +++ b/src/main/java/com/easysoftware/common/interceptor/OneidToken.java @@ -0,0 +1,12 @@ +package com.easysoftware.common.interceptor; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface OneidToken { + boolean required() default true; +} diff --git a/src/main/java/com/easysoftware/common/utils/ApiUtil.java b/src/main/java/com/easysoftware/common/utils/ApiUtil.java index 8f8fbfb1319d8f6e80340a055b3e4a14621fdfc1..afa115712c52182ddf6a63afe078f75475225ed7 100644 --- a/src/main/java/com/easysoftware/common/utils/ApiUtil.java +++ b/src/main/java/com/easysoftware/common/utils/ApiUtil.java @@ -9,7 +9,7 @@ public class ApiUtil { public static Map getApiResponse(String url) { Map res = new HashMap<>(); - String response = HttpClientUtil.getHttpClient(url); + String response = HttpClientUtil.getHttpClient(url, null, null, null); if (response != null) { JsonNode info = ObjectMapperUtil.toJsonNode(response); if (info.get("code").asInt() == 200 && !info.get("data").isNull()) { diff --git a/src/main/java/com/easysoftware/common/utils/HttpClientUtil.java b/src/main/java/com/easysoftware/common/utils/HttpClientUtil.java index bba20a6a2905ea02bb2cbcfa7bdc2829f3d5cf4a..46ca66d7534a6c2ae210e0fd797e3333a6173a0f 100644 --- a/src/main/java/com/easysoftware/common/utils/HttpClientUtil.java +++ b/src/main/java/com/easysoftware/common/utils/HttpClientUtil.java @@ -9,6 +9,8 @@ import java.net.URL; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; 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; @@ -65,16 +67,35 @@ public class HttpClientUtil { return null; } - public static String getHttpClient(String uri) { + public static String getHttpClient(String uri, String token, String userToken, String cookie) { HttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(uri); + + if (token != null) httpGet.addHeader("token", token); + if (userToken != null) httpGet.addHeader("user-token", userToken); + if (cookie != null) httpGet.addHeader("Cookie", "_Y_G_=" + cookie); + try { HttpResponse response = httpClient.execute(httpGet); String responseBody = EntityUtils.toString(response.getEntity()); return responseBody; } catch (Exception e) { - logger.error(MessageCode.EC0001.getMsgEn(), e); + throw new RuntimeException(MessageCode.EC0001.getMsgEn()); + } + } + + public static String postHttpClient(String uri, String requestBody) { + HttpClient httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(uri); + try { + httpPost.setHeader("Content-Type", "application/json"); + StringEntity stringEntity = new StringEntity(requestBody); + httpPost.setEntity(stringEntity); + HttpResponse response = httpClient.execute(httpPost); + String responseBody = EntityUtils.toString(response.getEntity()); + return responseBody; + } catch (Exception e) { + throw new RuntimeException(MessageCode.EC0001.getMsgEn()); } - return null; } }