1
This commit is contained in:
@@ -14,6 +14,13 @@ import org.springframework.http.MediaType;
|
|||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 金山文档 OAuth 回调(独立路径,避免前端路由拦截)
|
* 金山文档 OAuth 回调(独立路径,避免前端路由拦截)
|
||||||
* 回调地址示例:https://your-domain/kdocs-callback
|
* 回调地址示例:https://your-domain/kdocs-callback
|
||||||
@@ -30,26 +37,57 @@ public class KdocsCallbackController extends BaseController {
|
|||||||
|
|
||||||
@Anonymous
|
@Anonymous
|
||||||
@GetMapping
|
@GetMapping
|
||||||
public ResponseEntity<?> oauthCallbackGet(@RequestParam(value = "code", required = false) String code,
|
public ResponseEntity<?> oauthCallbackGet(HttpServletRequest request,
|
||||||
|
@RequestParam(value = "code", required = false) String code,
|
||||||
@RequestParam(value = "state", required = false) String state,
|
@RequestParam(value = "state", required = false) String state,
|
||||||
@RequestParam(value = "error", required = false) String error,
|
@RequestParam(value = "error", required = false) String error,
|
||||||
@RequestParam(value = "error_description", required = false) String errorDescription) {
|
@RequestParam(value = "error_description", required = false) String errorDescription) {
|
||||||
return handleOAuthCallback(code, state, error, errorDescription);
|
return handleOAuthCallback(request, code, state, error, errorDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 部分开放平台校验可能使用 POST。
|
* 部分开放平台校验可能使用 POST;JSON body 时需回显 challenge 等字段。
|
||||||
*/
|
*/
|
||||||
@Anonymous
|
@Anonymous
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public ResponseEntity<?> oauthCallbackPost(@RequestParam(value = "code", required = false) String code,
|
public ResponseEntity<?> oauthCallbackPost(HttpServletRequest request) throws IOException {
|
||||||
@RequestParam(value = "state", required = false) String state,
|
String ct = StringUtils.defaultString(request.getContentType()).toLowerCase();
|
||||||
@RequestParam(value = "error", required = false) String error,
|
if (ct.contains("application/json")) {
|
||||||
@RequestParam(value = "error_description", required = false) String errorDescription) {
|
StringBuilder sb = new StringBuilder();
|
||||||
return handleOAuthCallback(code, state, error, errorDescription);
|
try (BufferedReader r = request.getReader()) {
|
||||||
|
char[] buf = new char[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = r.read(buf)) != -1) {
|
||||||
|
sb.append(buf, 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String raw = sb.toString();
|
||||||
|
if (StringUtils.isNotBlank(raw)) {
|
||||||
|
try {
|
||||||
|
JSONObject o = JSON.parseObject(raw);
|
||||||
|
if (o != null && o.containsKey("code")) {
|
||||||
|
Object cv = o.get("code");
|
||||||
|
if (cv != null) {
|
||||||
|
String c = String.valueOf(cv);
|
||||||
|
if (StringUtils.isNotBlank(c) && !"null".equals(c)) {
|
||||||
|
return handleOAuthCallback(request, c, o.getString("state"), o.getString("error"), o.getString("error_description"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("解析 OAuth POST JSON: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return KdocsCallbackProbeResponses.callbackReadyJson(request, raw);
|
||||||
|
}
|
||||||
|
String code = request.getParameter("code");
|
||||||
|
String state = request.getParameter("state");
|
||||||
|
String error = request.getParameter("error");
|
||||||
|
String errorDescription = request.getParameter("error_description");
|
||||||
|
return handleOAuthCallback(request, code, state, error, errorDescription);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ResponseEntity<?> handleOAuthCallback(String code, String state, String error, String errorDescription) {
|
private ResponseEntity<?> handleOAuthCallback(HttpServletRequest request, String code, String state, String error, String errorDescription) {
|
||||||
try {
|
try {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
String msg = errorDescription != null ? errorDescription : error;
|
String msg = errorDescription != null ? errorDescription : error;
|
||||||
@@ -58,7 +96,7 @@ public class KdocsCallbackController extends BaseController {
|
|||||||
}
|
}
|
||||||
// 无 code:多为平台校验回调可达性,或用户直接打开本地址(非授权失败)
|
// 无 code:多为平台校验回调可达性,或用户直接打开本地址(非授权失败)
|
||||||
if (StringUtils.isBlank(code)) {
|
if (StringUtils.isBlank(code)) {
|
||||||
return callbackEndpointInfoPage();
|
return callbackEndpointInfoPage(request);
|
||||||
}
|
}
|
||||||
log.info("金山文档授权回调 code 已收到 state={}", state);
|
log.info("金山文档授权回调 code 已收到 state={}", state);
|
||||||
KdocsTokenInfo tokenInfo = kdocsOAuthService.getAccessTokenByCode(code);
|
KdocsTokenInfo tokenInfo = kdocsOAuthService.getAccessTokenByCode(code);
|
||||||
@@ -100,7 +138,7 @@ public class KdocsCallbackController extends BaseController {
|
|||||||
/**
|
/**
|
||||||
* 无授权参数时的占位页:HTTP 200,避免被误判为「回调不可用」,也不向 opener 误发失败消息。
|
* 无授权参数时的占位页:HTTP 200,避免被误判为「回调不可用」,也不向 opener 误发失败消息。
|
||||||
*/
|
*/
|
||||||
private ResponseEntity<String> callbackEndpointInfoPage() {
|
private ResponseEntity<String> callbackEndpointInfoPage(HttpServletRequest request) {
|
||||||
return KdocsCallbackProbeResponses.callbackReadyPage();
|
return KdocsCallbackProbeResponses.callbackReadyJson(request, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,31 @@
|
|||||||
package com.ruoyi.web.controller.jarvis;
|
package com.ruoyi.web.controller.jarvis;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSON;
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开放平台校验回调 URL 时多为 GET、无 code,需直接 200;勿对校验请求返回 302。
|
* 金山文档 ChallengeURLValidator 等校验会解析响应为 JSON;返回 HTML 会报 unmarshal challenge response json invalid。
|
||||||
|
* 无 OAuth code 时返回与开放平台风格接近的 JSON,并回显 query / JSON body 中的字段(如 challenge)。
|
||||||
*/
|
*/
|
||||||
public final class KdocsCallbackProbeResponses {
|
public final class KdocsCallbackProbeResponses {
|
||||||
|
|
||||||
private KdocsCallbackProbeResponses() {
|
private KdocsCallbackProbeResponses() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final MediaType JSON_UTF8 = MediaType.parseMediaType("application/json;charset=UTF-8");
|
||||||
|
|
||||||
private static final MediaType HTML_UTF8 = MediaType.parseMediaType("text/html;charset=UTF-8");
|
private static final MediaType HTML_UTF8 = MediaType.parseMediaType("text/html;charset=UTF-8");
|
||||||
|
|
||||||
private static final String BODY = "<!DOCTYPE html><html lang='zh-CN'><head><meta charset='UTF-8'><meta name='robots' content='noindex'>"
|
private static final String HTML_BODY = "<!DOCTYPE html><html lang='zh-CN'><head><meta charset='UTF-8'><meta name='robots' content='noindex'>"
|
||||||
+ "<title>金山文档授权回调</title></head>"
|
+ "<title>金山文档授权回调</title></head>"
|
||||||
+ "<body style='font-family:sans-serif;text-align:center;padding:40px;color:#333'>"
|
+ "<body style='font-family:sans-serif;text-align:center;padding:40px;color:#333'>"
|
||||||
+ "<h2>金山文档授权回调</h2>"
|
+ "<h2>金山文档授权回调</h2>"
|
||||||
@@ -23,9 +33,70 @@ public final class KdocsCallbackProbeResponses {
|
|||||||
+ "<p>请在系统中点击「连接金山文档」或「授权」后,由金山文档页面自动跳转到此处。</p>"
|
+ "<p>请在系统中点击「连接金山文档」或「授权」后,由金山文档页面自动跳转到此处。</p>"
|
||||||
+ "</body></html>";
|
+ "</body></html>";
|
||||||
|
|
||||||
public static ResponseEntity<String> callbackReadyPage() {
|
/**
|
||||||
|
* 浏览器直接打开回调页时使用(Accept 偏 HTML)。
|
||||||
|
*/
|
||||||
|
public static ResponseEntity<String> callbackReadyHtmlPage() {
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setContentType(HTML_UTF8);
|
headers.setContentType(HTML_UTF8);
|
||||||
return new ResponseEntity<>(BODY, headers, HttpStatus.OK);
|
return new ResponseEntity<>(HTML_BODY, headers, HttpStatus.OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台 URL 校验:合法 JSON,兼容 WebOffice/开放平台常见 envelope,并回显校验参数。
|
||||||
|
*
|
||||||
|
* @param jsonBody POST application/json 时的原始 body,可为 null
|
||||||
|
*/
|
||||||
|
public static ResponseEntity<String> callbackReadyJson(HttpServletRequest request, String jsonBody) {
|
||||||
|
JSONObject data = new JSONObject();
|
||||||
|
|
||||||
|
Enumeration<String> names = request.getParameterNames();
|
||||||
|
while (names.hasMoreElements()) {
|
||||||
|
String n = names.nextElement();
|
||||||
|
data.put(n, request.getParameter(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeJsonPrimitivesIntoData(data, jsonBody);
|
||||||
|
|
||||||
|
JSONObject root = new JSONObject();
|
||||||
|
root.put("code", 0);
|
||||||
|
root.put("message", "");
|
||||||
|
root.put("result", "ok");
|
||||||
|
root.put("data", data);
|
||||||
|
|
||||||
|
if (data.containsKey("challenge")) {
|
||||||
|
root.put("challenge", data.get("challenge"));
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(JSON_UTF8);
|
||||||
|
return new ResponseEntity<>(root.toJSONString(), headers, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mergeJsonPrimitivesIntoData(JSONObject data, String jsonBody) {
|
||||||
|
if (StringUtils.isBlank(jsonBody)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
JSONObject in = JSON.parseObject(jsonBody);
|
||||||
|
if (in == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String k : in.keySet()) {
|
||||||
|
Object v = in.get(k);
|
||||||
|
if (v == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (v instanceof JSONObject || v instanceof JSONArray) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!data.containsKey(k)) {
|
||||||
|
data.put(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// 非 JSON 则忽略
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,11 +43,35 @@ public class Wps365ToKdocsCallbackRedirectController {
|
|||||||
|
|
||||||
private ResponseEntity<?> handleWps365(HttpServletRequest request, String code, String error) {
|
private ResponseEntity<?> handleWps365(HttpServletRequest request, String code, String error) {
|
||||||
if (StringUtils.isBlank(code) && StringUtils.isBlank(error)) {
|
if (StringUtils.isBlank(code) && StringUtils.isBlank(error)) {
|
||||||
return KdocsCallbackProbeResponses.callbackReadyPage();
|
String jsonBody = readJsonBodyIfPost(request);
|
||||||
|
return KdocsCallbackProbeResponses.callbackReadyJson(request, jsonBody);
|
||||||
}
|
}
|
||||||
String q = request.getQueryString();
|
String q = request.getQueryString();
|
||||||
HttpHeaders headers = new HttpHeaders();
|
HttpHeaders headers = new HttpHeaders();
|
||||||
headers.setLocation(URI.create(KdocsCallbackUrlBuilder.absoluteKdocsCallback(request, q)));
|
headers.setLocation(URI.create(KdocsCallbackUrlBuilder.absoluteKdocsCallback(request, q)));
|
||||||
return new ResponseEntity<>(null, headers, HttpStatus.FOUND);
|
return new ResponseEntity<>(null, headers, HttpStatus.FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String readJsonBodyIfPost(HttpServletRequest request) {
|
||||||
|
if (!"POST".equalsIgnoreCase(request.getMethod())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String ct = StringUtils.defaultString(request.getContentType()).toLowerCase();
|
||||||
|
if (!ct.contains("application/json")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (BufferedReader r = request.getReader()) {
|
||||||
|
char[] buf = new char[4096];
|
||||||
|
int n;
|
||||||
|
while ((n = r.read(buf)) != -1) {
|
||||||
|
sb.append(buf, 0, n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user