feat: open-api support auth

This commit is contained in:
tjq 2024-08-09 20:55:08 +08:00
parent c04cb08390
commit 84b90a366c
27 changed files with 751 additions and 42 deletions

View File

@ -0,0 +1,63 @@
package tech.powerjob.client;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import tech.powerjob.client.common.Protocol;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* 客户端配置
*
* @author 程序帕鲁
* @since 2024/2/20
*/
@Getter
@Setter
@ToString
public class ClientConfig implements Serializable {
/**
* 执行器 AppName
*/
private String appName;
/**
* 执行器密码
*/
private String password;
/**
* 地址列表支持格式
* - IP:Port, eg: 192.168.1.1:7700
* - 域名, eg: powerjob.apple-inc.com
*/
private List<String> addressList;
/**
* 客户端通讯协议
*/
private Protocol protocol = Protocol.HTTP;
/**
* 连接超时时间
*/
private Integer connectionTimeout;
/**
* 指定了等待服务器响应数据的最长时间更具体地说这是从服务器开始返回响应数据包括HTTP头和数据客户端读取数据的超时时间
*/
private Integer readTimeout;
/**
* 指定了向服务器发送数据的最长时间这是从客户端开始发送数据如POST请求的正文到数据完全发送出去的时间
*/
private Integer writeTimeout;
/**
* 默认携带的请求头
* 用于流量被基础设施识别
*/
private Map<String, String> defaultHeaders;
}

View File

@ -0,0 +1,28 @@
package tech.powerjob.client.common;
import lombok.Getter;
/**
* Protocol
*
* @author tjq
* @since 2024/2/20
*/
@Getter
public enum Protocol {
HTTP("http"),
HTTPS("https");
private final String protocol;
Protocol(String protocol) {
this.protocol = protocol;
}
@Override
public String toString() {
return protocol;
}
}

View File

@ -0,0 +1,33 @@
package tech.powerjob.client.module;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import java.util.Map;
/**
* App 鉴权请求
*
* @author tjq
* @since 2024/2/19
*/
@Getter
@Setter
@ToString
public class AppAuthRequest implements Serializable {
/**
* 应用名称
*/
private String appName;
/**
* 加密后密码
*/
private String encryptedPassword;
/**
* 额外参数方便开发者传递其他参数
*/
private Map<String, Object> extra;
}

View File

@ -0,0 +1,25 @@
package tech.powerjob.client.module;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
/**
* App 鉴权响应
*
* @author tjq
* @since 2024/2/21
*/
@Getter
@Setter
@ToString
public class AppAuthResult implements Serializable {
private Long appId;
private String token;
private String extra;
}

View File

@ -0,0 +1,13 @@
package tech.powerjob.client.service;
/**
* 请求服务
*
* @author tjq
* @since 2024/2/20
*/
public interface RequestService {
String request(String path, Object body);
}

View File

@ -0,0 +1,33 @@
package tech.powerjob.client.service.impl;
import tech.powerjob.client.ClientConfig;
import tech.powerjob.client.module.AppAuthRequest;
import tech.powerjob.client.module.AppAuthResult;
import tech.powerjob.common.utils.DigestUtils;
/**
* 封装鉴权相关逻辑
*
* @author tjq
* @since 2024/2/21
*/
abstract class AppAuthClusterRequestService extends ClusterRequestService {
protected AppAuthResult appAuthResult;
public AppAuthClusterRequestService(ClientConfig config) {
super(config);
}
protected void refreshAuthInfo() {
AppAuthRequest appAuthRequest = new AppAuthRequest();
appAuthRequest.setAppName(config.getAppName());
appAuthRequest.setEncryptedPassword(DigestUtils.md5(config.getPassword()));
try {
} catch (Exception e) {
}
}
}

View File

@ -0,0 +1,123 @@
package tech.powerjob.client.service.impl;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import tech.powerjob.client.ClientConfig;
import tech.powerjob.client.service.RequestService;
import tech.powerjob.common.OpenAPIConstant;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.serialize.JsonUtils;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* 集群请求服务
* 封装网络相关通用逻辑
*
* @author tjq
* @since 2024/2/21
*/
@Slf4j
abstract class ClusterRequestService implements RequestService {
protected final ClientConfig config;
/**
* 当前地址上次请求成功的地址
*/
protected String currentAddress;
/**
* 鉴权相关 headers
*/
protected Map<String, String> authHeaders = Maps.newHashMap();
/**
* 地址格式
* 协议://域名/OpenAPI/子路径
*/
protected static final String URL_PATTERN = "%s://%s%s%s";
/**
* 默认超时时间
*/
protected static final Integer DEFAULT_TIMEOUT_SECONDS = 2;
protected static final int HTTP_SUCCESS_CODE = 200;
public ClusterRequestService(ClientConfig config) {
this.config = config;
}
protected abstract String sendHttpRequest(String url, String body) throws IOException;
@Override
public String request(String path, Object obj) {
String body = obj instanceof String ? (String) obj : JsonUtils.toJSONStringUnsafe(obj);
List<String> addressList = config.getAddressList();
// 先尝试默认地址
String url = getUrl(path, currentAddress);
try {
String res = sendHttpRequest(url, body);
if (StringUtils.isNotEmpty(res)) {
return res;
}
} catch (IOException e) {
log.warn("[ClusterRequestService] request url:{} failed, reason is {}.", url, e.toString());
}
// 失败开始重试
for (String addr : addressList) {
if (Objects.equals(addr, currentAddress)) {
continue;
}
url = getUrl(path, addr);
try {
String res = sendHttpRequest(url, body);
if (StringUtils.isNotEmpty(res)) {
log.warn("[ClusterRequestService] server change: from({}) -> to({}).", currentAddress, addr);
currentAddress = addr;
return res;
}
} catch (IOException e) {
log.warn("[ClusterRequestService] request url:{} failed, reason is {}.", url, e.toString());
}
}
log.error("[ClusterRequestService] do post for path: {} failed because of no server available in {}.", path, addressList);
throw new PowerJobException("no server available when send post request");
}
/**
* 不验证证书
* X.509 是一个国际标准定义了公钥证书的格式这个标准是由国际电信联盟ITU-T制定的用于公钥基础设施PKI中数字证书的创建和分发X.509证书主要用于在公开网络上验证实体的身份如服务器或客户端的身份验证过程中确保通信双方是可信的X.509证书广泛应用于多种安全协议中包括SSL/TLS它是实现HTTPS的基础
*/
protected static class NoVerifyX509TrustManager implements X509TrustManager {
@Override
public void checkClientTrusted(X509Certificate[] arg0, String arg1) {
}
@Override
public void checkServerTrusted(X509Certificate[] arg0, String arg1) {
// 不验证
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
}
private String getUrl(String path, String address) {
String protocol = config.getProtocol().getProtocol();
return String.format(URL_PATTERN, protocol, address, OpenAPIConstant.WEB_PATH, path);
}
}

View File

@ -0,0 +1,109 @@
package tech.powerjob.client.service.impl;
import com.google.common.collect.Maps;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import tech.powerjob.client.ClientConfig;
import tech.powerjob.client.common.Protocol;
import tech.powerjob.common.OmsConstant;
import tech.powerjob.common.exception.PowerJobException;
import javax.net.ssl.*;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* desc
*
* @author tjq
* @since 2024/2/20
*/
@Slf4j
public class ClusterRequestServiceOkHttp3Impl extends ClusterRequestService {
private final OkHttpClient okHttpClient;
public ClusterRequestServiceOkHttp3Impl(ClientConfig config) {
super(config);
// 初始化 HTTP 客户端
if (Protocol.HTTPS.equals(config.getProtocol())) {
okHttpClient = initHttpsNoVerifyClient();
} else {
okHttpClient = initHttpClient();
}
}
@Override
protected String sendHttpRequest(String url, String payload) throws IOException {
// 公共 header
Map<String, String> headers = Maps.newHashMap();
if (config.getDefaultHeaders() != null) {
headers.putAll(config.getDefaultHeaders());
}
MediaType jsonType = MediaType.parse(OmsConstant.JSON_MEDIA_TYPE);
RequestBody requestBody = RequestBody.create(jsonType, payload);
Request request = new Request.Builder()
.post(requestBody)
.url(url)
.headers(Headers.of(headers))
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
int responseCode = response.code();
if (responseCode == HTTP_SUCCESS_CODE) {
ResponseBody body = response.body();
if (body == null) {
return null;
}else {
return body.string();
}
}
throw new PowerJobException(String.format("http request failed,code=%d", responseCode));
}
}
@SneakyThrows
private OkHttpClient initHttpClient() {
OkHttpClient.Builder okHttpBuilder = commonOkHttpBuilder();
return okHttpBuilder.build();
}
@SneakyThrows
private OkHttpClient initHttpsNoVerifyClient() {
X509TrustManager trustManager = new NoVerifyX509TrustManager();
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[]{trustManager}, new SecureRandom());
SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder okHttpBuilder = commonOkHttpBuilder();
// 不需要校验证书
okHttpBuilder.sslSocketFactory(sslSocketFactory, trustManager);
// 不校验 url中的 hostname
okHttpBuilder.hostnameVerifier((String hostname, SSLSession session) -> true);
return okHttpBuilder.build();
}
private OkHttpClient.Builder commonOkHttpBuilder() {
return new OkHttpClient.Builder()
// 设置读取超时时间
.readTimeout(Optional.ofNullable(config.getReadTimeout()).orElse(DEFAULT_TIMEOUT_SECONDS), TimeUnit.SECONDS)
// 设置写的超时时间
.writeTimeout(Optional.ofNullable(config.getReadTimeout()).orElse(DEFAULT_TIMEOUT_SECONDS), TimeUnit.SECONDS)
// 设置连接超时时间
.connectTimeout(Optional.ofNullable(config.getReadTimeout()).orElse(DEFAULT_TIMEOUT_SECONDS), TimeUnit.SECONDS);
}
}

View File

@ -16,6 +16,8 @@ public class OpenAPIConstant {
public static final String ASSERT = "/assert";
public static final String AUTH_APP = "/authApp";
/* ************* JOB 区 ************* */
public static final String SAVE_JOB = "/saveJob";
@ -56,4 +58,10 @@ public class OpenAPIConstant {
public static final String RETRY_WORKFLOW_INSTANCE = "/retryWfInstance";
public static final String FETCH_WORKFLOW_INSTANCE_INFO = "/fetchWfInstanceInfo";
public static final String MARK_WORKFLOW_NODE_AS_SUCCESS = "/markWorkflowNodeAsSuccess";
/* ************* 鉴权 ************* */
public static final String HEADER_ACCESS_TOKEN = "X-POWERJOB-ACCESS-TOKEN";
public static final String HEADER_APP_ID = "X-POWERJOB-APP-ID";
}

View File

@ -0,0 +1,37 @@
package tech.powerjob.common.response;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.exception.ExceptionUtils;
/**
* 新的 Result带状态码
*
* @author 程序帕鲁
* @since 2024/2/19
*/
@Getter
@Setter
public class PowerResultDTO<T> extends ResultDTO<T> {
private String code;
public static <T> PowerResultDTO<T> s(T data) {
PowerResultDTO<T> r = new PowerResultDTO<>();
r.success = true;
r.data = data;
return r;
}
public static <T> PowerResultDTO<T> f(String message) {
PowerResultDTO<T> r = new PowerResultDTO<>();
r.success = false;
r.message = message;
return r;
}
public static <T> PowerResultDTO<T> f(Throwable t) {
return f(ExceptionUtils.getStackTrace(t));
}
}

View File

@ -72,8 +72,9 @@ public class JsonUtils {
try {
return JSON_MAPPER.writeValueAsString(obj);
}catch (Exception e) {
throw new PowerJobException(e);
ExceptionUtils.rethrow(e);
}
throw new ImpossibleException();
}
public static byte[] toBytes(Object obj) {

View File

@ -1,6 +1,7 @@
package tech.powerjob.server.common.utils;
package tech.powerjob.common.utils;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import java.math.BigInteger;
import java.security.MessageDigest;
@ -20,6 +21,11 @@ public class DigestUtils {
*/
@SneakyThrows
public static String md5(String input) {
if (StringUtils.isEmpty(input)) {
return null;
}
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(input.getBytes());
byte[] byteArray = md5.digest();

View File

@ -108,6 +108,12 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-client</artifactId>
<version>${project.version}</version>
</dependency>
<!-- 存储扩展-MongoDB未使用可移除 -->
<dependency>
<groupId>org.mongodb</groupId>

View File

@ -33,6 +33,8 @@ public enum AuthErrorCode {
INVALID_TOKEN("-401", "INVALID_TOKEN"),
OPEN_API_AUTH_FAILED("-1001", "OPEN_API_AUTH_FAILED"),
;
private final String code;

View File

@ -5,7 +5,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import tech.powerjob.server.auth.jwt.SecretProvider;
import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.common.utils.DigestUtils;
import javax.annotation.Resource;

View File

@ -10,7 +10,7 @@ import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.auth.login.*;
import tech.powerjob.server.common.Loggers;
import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.common.utils.DigestUtils;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import tech.powerjob.server.persistence.remote.repository.PwjbUserInfoRepository;

View File

@ -1,5 +1,9 @@
package tech.powerjob.server.core.service;
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import java.util.Optional;
/**
* AppInfoService
*
@ -7,5 +11,21 @@ package tech.powerjob.server.core.service;
* @since 2023/3/4
*/
public interface AppInfoService {
/**
* 验证 APP 账号密码
* @param appName 账号
* @param password 原文密码
* @return AppId
*/
Long assertApp(String appName, String password);
Long assertAppWithEncryptedPassword(String appName, String encryptedPassword);
/**
* 获取 AppInfo带缓存
* @param appId appId
* @return App 信息
*/
Optional<AppInfoDO> findByIdWithCache(Long appId);
}

View File

@ -1,13 +1,19 @@
package tech.powerjob.server.core.service.impl;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.utils.DigestUtils;
import tech.powerjob.server.core.service.AppInfoService;
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import tech.powerjob.server.persistence.remote.repository.AppInfoRepository;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
/**
* AppInfoServiceImpl
@ -15,10 +21,17 @@ import java.util.Objects;
* @author tjq
* @since 2023/3/4
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class AppInfoServiceImpl implements AppInfoService {
private final Cache<Long, AppInfoDO> appId2AppInfoDO = CacheBuilder.newBuilder()
.softValues()
.expireAfterWrite(3, TimeUnit.MINUTES)
.maximumSize(1024)
.build();
private final AppInfoRepository appInfoRepository;
/**
@ -36,4 +49,31 @@ public class AppInfoServiceImpl implements AppInfoService {
}
throw new PowerJobException("password error!");
}
@Override
public Long assertAppWithEncryptedPassword(String appName, String encryptedPassword) {
AppInfoDO appInfo = appInfoRepository.findByAppName(appName).orElseThrow(() -> new PowerJobException("can't find appInfo by appName: " + appName));
if (Objects.equals(DigestUtils.md5(appInfo.getPassword()), encryptedPassword)) {
return appInfo.getId();
}
throw new PowerJobException("password error!");
}
@Override
public Optional<AppInfoDO> findByIdWithCache(Long appId) {
try {
AppInfoDO appInfoDO = appId2AppInfoDO.get(appId, () -> {
Optional<AppInfoDO> appInfoOpt = appInfoRepository.findById(appId);
if (appInfoOpt.isPresent()) {
return appInfoOpt.get();
}
throw new IllegalArgumentException("can't find appInfo by appId:" + appId);
});
return Optional.of(appInfoDO);
} catch (Exception e) {
log.warn("[AppInfoService] findByIdWithCache failed,appId={}", appId, e);
}
return Optional.empty();
}
}

View File

@ -51,6 +51,11 @@
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-server-migrate</artifactId>
</dependency>
<dependency>
<groupId>tech.powerjob</groupId>
<artifactId>powerjob-client</artifactId>
</dependency>
</dependencies>
<!-- SpringBoot maven plugin -->

View File

@ -1,7 +1,9 @@
package tech.powerjob.server.web.controller;
package tech.powerjob.server.openapi;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import tech.powerjob.client.module.AppAuthRequest;
import tech.powerjob.client.module.AppAuthResult;
import tech.powerjob.common.OpenAPIConstant;
import tech.powerjob.common.PowerQuery;
import tech.powerjob.common.enums.InstanceStatus;
@ -9,16 +11,14 @@ import tech.powerjob.common.request.http.SaveJobInfoRequest;
import tech.powerjob.common.request.http.SaveWorkflowNodeRequest;
import tech.powerjob.common.request.http.SaveWorkflowRequest;
import tech.powerjob.common.request.query.JobInfoQuery;
import tech.powerjob.common.response.InstanceInfoDTO;
import tech.powerjob.common.response.JobInfoDTO;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.common.response.WorkflowInstanceInfoDTO;
import tech.powerjob.common.response.*;
import tech.powerjob.server.core.instance.InstanceService;
import tech.powerjob.server.core.service.AppInfoService;
import tech.powerjob.server.core.service.CacheService;
import tech.powerjob.server.core.service.JobService;
import tech.powerjob.server.core.workflow.WorkflowInstanceService;
import tech.powerjob.server.core.workflow.WorkflowService;
import tech.powerjob.server.openapi.security.OpenApiSecurityService;
import tech.powerjob.server.persistence.remote.model.WorkflowInfoDO;
import tech.powerjob.server.persistence.remote.model.WorkflowNodeInfoDO;
import tech.powerjob.server.web.response.WorkflowInfoVO;
@ -46,6 +46,8 @@ public class OpenAPIController {
private final WorkflowInstanceService workflowInstanceService;
private final OpenApiSecurityService openApiSecurityService;
private final CacheService cacheService;
@ -54,6 +56,16 @@ public class OpenAPIController {
return ResultDTO.success(appInfoService.assertApp(appName, password));
}
/**
* APP 鉴权
* @param appAuthRequest 鉴权请求
* @return 鉴权响应
*/
@PostMapping(OpenAPIConstant.AUTH_APP)
public PowerResultDTO<AppAuthResult> auth(@RequestBody AppAuthRequest appAuthRequest) {
return PowerResultDTO.s(openApiSecurityService.authAppByParam(appAuthRequest));
}
/* ************* Job 区 ************* */
@PostMapping(OpenAPIConstant.SAVE_JOB)

View File

@ -0,0 +1,46 @@
package tech.powerjob.server.openapi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import tech.powerjob.server.openapi.security.OpenApiSecurityService;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* OpenAPI 拦截器
*
* @author 程序帕鲁
* @since 2024/2/19
*/
@Slf4j
@Component
public class OpenApiInterceptor implements HandlerInterceptor {
@Resource
private OpenApiSecurityService openApiSecurityService;
/**
* 4.x 及前序版本的 OpenAPI 均为携带 auth 的必要参数直接开启鉴权功能会导致之前的服务全部报错
* 因此提供功能开关给到使用者若无安全影响可展示关闭鉴权功能 client 升级完毕后再打开鉴权
*/
@Value("${oms.auth.openapi.enable:false}")
private boolean enableOpenApiAuth;
@Override
public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
if (!enableOpenApiAuth) {
return true;
}
openApiSecurityService.authAppByToken(request);
return true;
}
}

View File

@ -0,0 +1,28 @@
package tech.powerjob.server.openapi.security;
import tech.powerjob.client.module.AppAuthRequest;
import tech.powerjob.client.module.AppAuthResult;
import javax.servlet.http.HttpServletRequest;
/**
* OPENAPI 安全服务
*
* @author tjq
* @since 2024/2/19
*/
public interface OpenApiSecurityService {
/**
* APP 纬度请求的鉴权 & 验证
* @param appAuthRequest 请求参数
* @return token
*/
AppAuthResult authAppByParam(AppAuthRequest appAuthRequest);
/**
* APP 纬度请求的鉴权 & 验证
* @param httpServletRequest http 原始请求
*/
void authAppByToken(HttpServletRequest httpServletRequest);
}

View File

@ -0,0 +1,98 @@
package tech.powerjob.server.openapi.security;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import tech.powerjob.client.module.AppAuthRequest;
import tech.powerjob.client.module.AppAuthResult;
import tech.powerjob.common.OpenAPIConstant;
import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.auth.common.utils.HttpServletUtils;
import tech.powerjob.server.auth.jwt.JwtService;
import tech.powerjob.common.utils.DigestUtils;
import tech.powerjob.server.core.service.AppInfoService;
import tech.powerjob.server.persistence.remote.model.AppInfoDO;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Optional;
/**
* OpenApiSecurityService
*
* @author tjq
* @since 2024/2/19
*/
@Slf4j
@Service
public class OpenApiSecurityServiceImpl implements OpenApiSecurityService {
@Resource
private JwtService jwtService;
@Resource
private AppInfoService appInfoService;
private static final String JWT_KEY_APP_ID = "appId";
private static final String JWT_KEY_APP_PASSWORD = "password";
@Override
public void authAppByToken(HttpServletRequest httpServletRequest) {
String token = HttpServletUtils.fetchFromHeader(OpenAPIConstant.HEADER_ACCESS_TOKEN, httpServletRequest);
String appIdFromHeader = HttpServletUtils.fetchFromHeader(OpenAPIConstant.HEADER_APP_ID, httpServletRequest);
if (StringUtils.isEmpty(appIdFromHeader)) {
throw new IllegalArgumentException("can't find appId in HTTP header");
}
if (StringUtils.isEmpty(token)) {
throw new PowerJobAuthException(AuthErrorCode.OPEN_API_AUTH_FAILED);
}
Map<String, Object> jwtResult = jwtService.parse(token, null);
Long appIdFromJwt = MapUtils.getLong(jwtResult, JWT_KEY_APP_ID);
String passwordFromJwt = MapUtils.getString(jwtResult, JWT_KEY_APP_PASSWORD);
// 校验 appId 一致性
if (!StringUtils.equals(appIdFromHeader, String.valueOf(appIdFromJwt))) {
throw new IllegalArgumentException("Inconsistent appId from header and token");
}
// 此处不考虑改密码后的缓存时间毕竟只要改了密码一定会报错换言之 OpenAPI 模式下密码不可更改
Optional<AppInfoDO> appInfoOpt = appInfoService.findByIdWithCache(appIdFromJwt);
if (!appInfoOpt.isPresent()) {
throw new IllegalArgumentException("can't find app by appId: " + appIdFromJwt);
}
String dbOriginPassword = appInfoOpt.get().getPassword();
if (!StringUtils.equals(passwordFromJwt, DigestUtils.md5(dbOriginPassword))) {
throw new PowerJobAuthException(AuthErrorCode.OPEN_API_AUTH_FAILED);
}
}
@Override
public AppAuthResult authAppByParam(AppAuthRequest appAuthRequest) {
String appName = appAuthRequest.getAppName();
String encryptedPassword = appAuthRequest.getEncryptedPassword();
Long appId = appInfoService.assertAppWithEncryptedPassword(appName, encryptedPassword);
Map<String, Object> jwtBody = Maps.newHashMap();
jwtBody.put(JWT_KEY_APP_ID, appId);
jwtBody.put(JWT_KEY_APP_PASSWORD, encryptedPassword);
AppAuthResult appAuthResult = new AppAuthResult();
appAuthResult.setAppId(appId);
appAuthResult.setToken(jwtService.build(jwtBody, null));
return appAuthResult;
}
}

View File

@ -9,8 +9,7 @@ import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import tech.powerjob.common.exception.PowerJobException;
import tech.powerjob.common.response.ResultDTO;
import tech.powerjob.server.web.response.WebResultDTO;
import tech.powerjob.common.response.PowerResultDTO;
/**
* 统一处理 web 层异常信息
@ -24,9 +23,9 @@ public class ControllerExceptionHandler {
@ResponseBody
@ExceptionHandler(Exception.class)
public WebResultDTO<Void> exceptionHandler(Exception e) {
public PowerResultDTO<Void> exceptionHandler(Exception e) {
WebResultDTO<Void> ret = new WebResultDTO<>(ResultDTO.failed(ExceptionUtils.getMessage(e)));
PowerResultDTO<Void> ret = PowerResultDTO.f(ExceptionUtils.getMessage(e));
// 不是所有异常都需要打印完整堆栈后续可以定义内部的Exception便于判断
if (e instanceof PowerJobException) {

View File

@ -1,27 +0,0 @@
package tech.powerjob.server.web.response;
import lombok.Getter;
import lombok.Setter;
import tech.powerjob.common.response.ResultDTO;
/**
* WEB 请求结果
*
* @author tjq
* @since 2024/2/18
*/
@Getter
@Setter
public class WebResultDTO<T> extends ResultDTO<T> {
private String code;
public WebResultDTO() {
}
public WebResultDTO(ResultDTO<T> res) {
this.success = res.isSuccess();
this.data = res.getData();
this.message = res.getMessage();
}
}

View File

@ -8,7 +8,7 @@ import tech.powerjob.common.serialize.JsonUtils;
import tech.powerjob.common.utils.CommonUtils;
import tech.powerjob.server.auth.common.AuthErrorCode;
import tech.powerjob.server.auth.common.PowerJobAuthException;
import tech.powerjob.server.common.utils.DigestUtils;
import tech.powerjob.common.utils.DigestUtils;
import tech.powerjob.server.persistence.remote.model.PwjbUserInfoDO;
import tech.powerjob.server.persistence.remote.repository.PwjbUserInfoRepository;
import tech.powerjob.server.web.request.ChangePasswordRequest;

View File

@ -23,3 +23,4 @@ oms.table-prefix=
###### PowerJob User and Permission Configuration Configuration ######
oms.auth.initiliaze.admin.password=powerjob_admin
oms.auth.openapi.enable=false