fix: remove unfinished web oauth surface
This commit is contained in:
parent
21846dba8b
commit
339c793855
@ -20,7 +20,6 @@
|
|||||||
| `MYSQL_PASSWORD` | **是** | 空 | 数据库密码 |
|
| `MYSQL_PASSWORD` | **是** | 空 | 数据库密码 |
|
||||||
| `WECHAT_APPID` | 生产必填 | 空 | 微信小程序 AppID |
|
| `WECHAT_APPID` | 生产必填 | 空 | 微信小程序 AppID |
|
||||||
| `WECHAT_APPSECRET` | 生产必填 | 空 | 微信小程序 AppSecret |
|
| `WECHAT_APPSECRET` | 生产必填 | 空 | 微信小程序 AppSecret |
|
||||||
| `WECHAT_REDIRECT_URI` | 生产必填 | `http://localhost:8080/api/wechat/callback` | 微信回调;生产必须为真实 HTTPS 地址 |
|
|
||||||
| `PETSTORE_SESSION_SECRET` | **生产必填** | `dev-change-me` | HMAC session token 签名密钥;改密会使所有已签发 token 立即失效 |
|
| `PETSTORE_SESSION_SECRET` | **生产必填** | `dev-change-me` | HMAC session token 签名密钥;改密会使所有已签发 token 立即失效 |
|
||||||
| `PETSTORE_SESSION_TTL_SECONDS` | 否 | `604800`(7 天) | session token 有效期 |
|
| `PETSTORE_SESSION_TTL_SECONDS` | 否 | `604800`(7 天) | session token 有效期 |
|
||||||
| `APP_BASE_URL` | 生产必填 | `http://localhost:8080` | 后端对外可访问 base URL(用于生成媒体绝对 URL) |
|
| `APP_BASE_URL` | 生产必填 | `http://localhost:8080` | 后端对外可访问 base URL(用于生成媒体绝对 URL) |
|
||||||
|
|||||||
@ -9,7 +9,6 @@ MYSQL_PASSWORD=
|
|||||||
|
|
||||||
WECHAT_APPID=
|
WECHAT_APPID=
|
||||||
WECHAT_APPSECRET=
|
WECHAT_APPSECRET=
|
||||||
WECHAT_REDIRECT_URI=https://api.petstore.invalid/api/wechat/callback
|
|
||||||
PETSTORE_SESSION_SECRET=
|
PETSTORE_SESSION_SECRET=
|
||||||
SMS_UNIVERSAL_CODE=
|
SMS_UNIVERSAL_CODE=
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ require_env() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for name in MYSQL_URL MYSQL_USERNAME MYSQL_PASSWORD WECHAT_APPID WECHAT_APPSECRET \
|
for name in MYSQL_URL MYSQL_USERNAME MYSQL_PASSWORD WECHAT_APPID WECHAT_APPSECRET \
|
||||||
WECHAT_REDIRECT_URI PETSTORE_SESSION_SECRET APP_BASE_URL UPLOAD_PATH CORS_ALLOWED_ORIGINS; do
|
PETSTORE_SESSION_SECRET APP_BASE_URL UPLOAD_PATH CORS_ALLOWED_ORIGINS; do
|
||||||
require_env "$name"
|
require_env "$name"
|
||||||
done
|
done
|
||||||
|
|
||||||
@ -27,11 +27,6 @@ done
|
|||||||
[[ "$APP_BASE_URL" != *.invalid* ]] || fail "APP_BASE_URL must not use a placeholder domain"
|
[[ "$APP_BASE_URL" != *.invalid* ]] || fail "APP_BASE_URL must not use a placeholder domain"
|
||||||
[[ "$APP_BASE_URL" != *.example* && "$APP_BASE_URL" != *.test* ]] \
|
[[ "$APP_BASE_URL" != *.example* && "$APP_BASE_URL" != *.test* ]] \
|
||||||
|| fail "APP_BASE_URL must not use a reserved example/test domain"
|
|| fail "APP_BASE_URL must not use a reserved example/test domain"
|
||||||
[[ "$WECHAT_REDIRECT_URI" == https://* ]] || fail "WECHAT_REDIRECT_URI must use https"
|
|
||||||
[[ "$WECHAT_REDIRECT_URI" != *localhost* && "$WECHAT_REDIRECT_URI" != *.invalid* ]] \
|
|
||||||
|| fail "WECHAT_REDIRECT_URI must use a production domain"
|
|
||||||
[[ "$WECHAT_REDIRECT_URI" != *.example* && "$WECHAT_REDIRECT_URI" != *.test* ]] \
|
|
||||||
|| fail "WECHAT_REDIRECT_URI must not use a reserved example/test domain"
|
|
||||||
[[ ${#PETSTORE_SESSION_SECRET} -ge 32 ]] || fail "PETSTORE_SESSION_SECRET must be at least 32 characters"
|
[[ ${#PETSTORE_SESSION_SECRET} -ge 32 ]] || fail "PETSTORE_SESSION_SECRET must be at least 32 characters"
|
||||||
[[ "$PETSTORE_SESSION_SECRET" != "dev-change-me" ]] || fail "PETSTORE_SESSION_SECRET must not use the default"
|
[[ "$PETSTORE_SESSION_SECRET" != "dev-change-me" ]] || fail "PETSTORE_SESSION_SECRET must not use the default"
|
||||||
[[ -z "${SMS_UNIVERSAL_CODE:-}" ]] || fail "SMS_UNIVERSAL_CODE must be empty in production"
|
[[ -z "${SMS_UNIVERSAL_CODE:-}" ]] || fail "SMS_UNIVERSAL_CODE must be empty in production"
|
||||||
|
|||||||
@ -61,10 +61,6 @@ public class AuthInterceptor implements HandlerInterceptor {
|
|||||||
"POST /api/report/*/testimonial",
|
"POST /api/report/*/testimonial",
|
||||||
"POST /api/report/unsubscribe",
|
"POST /api/report/unsubscribe",
|
||||||
"POST /api/report/open-track",
|
"POST /api/report/open-track",
|
||||||
// 微信网页授权 OAuth 回调链(与 wechat.redirect_uri 配置一致;callback 当前为 TODO demo,
|
|
||||||
// 真实微信登录走 /api/user/wx-phone-login,此处放行仅为兼容 redirect_uri 配置)
|
|
||||||
"GET /api/wechat/authorize",
|
|
||||||
"GET /api/wechat/callback",
|
|
||||||
"GET /api/upload/image/**",
|
"GET /api/upload/image/**",
|
||||||
"GET /api/upload/legacy/**",
|
"GET /api/upload/legacy/**",
|
||||||
"GET /uploads/**"
|
"GET /uploads/**"
|
||||||
|
|||||||
@ -20,7 +20,6 @@ public class ProductionConfigurationValidator {
|
|||||||
private final String mysqlPassword;
|
private final String mysqlPassword;
|
||||||
private final String wechatAppId;
|
private final String wechatAppId;
|
||||||
private final String wechatAppSecret;
|
private final String wechatAppSecret;
|
||||||
private final String wechatRedirectUri;
|
|
||||||
private final String sessionSecret;
|
private final String sessionSecret;
|
||||||
private final String smsUniversalCode;
|
private final String smsUniversalCode;
|
||||||
private final String appBaseUrl;
|
private final String appBaseUrl;
|
||||||
@ -34,7 +33,6 @@ public class ProductionConfigurationValidator {
|
|||||||
@Value("${spring.datasource.password:}") String mysqlPassword,
|
@Value("${spring.datasource.password:}") String mysqlPassword,
|
||||||
@Value("${wechat.appid:}") String wechatAppId,
|
@Value("${wechat.appid:}") String wechatAppId,
|
||||||
@Value("${wechat.appsecret:}") String wechatAppSecret,
|
@Value("${wechat.appsecret:}") String wechatAppSecret,
|
||||||
@Value("${wechat.redirect_uri:}") String wechatRedirectUri,
|
|
||||||
@Value("${auth.session-secret:}") String sessionSecret,
|
@Value("${auth.session-secret:}") String sessionSecret,
|
||||||
@Value("${app.demo.sms-universal-code:}") String smsUniversalCode,
|
@Value("${app.demo.sms-universal-code:}") String smsUniversalCode,
|
||||||
@Value("${app.base-url:}") String appBaseUrl,
|
@Value("${app.base-url:}") String appBaseUrl,
|
||||||
@ -46,7 +44,6 @@ public class ProductionConfigurationValidator {
|
|||||||
this.mysqlPassword = mysqlPassword;
|
this.mysqlPassword = mysqlPassword;
|
||||||
this.wechatAppId = wechatAppId;
|
this.wechatAppId = wechatAppId;
|
||||||
this.wechatAppSecret = wechatAppSecret;
|
this.wechatAppSecret = wechatAppSecret;
|
||||||
this.wechatRedirectUri = wechatRedirectUri;
|
|
||||||
this.sessionSecret = sessionSecret;
|
this.sessionSecret = sessionSecret;
|
||||||
this.smsUniversalCode = smsUniversalCode;
|
this.smsUniversalCode = smsUniversalCode;
|
||||||
this.appBaseUrl = appBaseUrl;
|
this.appBaseUrl = appBaseUrl;
|
||||||
@ -70,7 +67,6 @@ public class ProductionConfigurationValidator {
|
|||||||
require(mysqlPassword, "MYSQL_PASSWORD", failures);
|
require(mysqlPassword, "MYSQL_PASSWORD", failures);
|
||||||
require(wechatAppId, "WECHAT_APPID", failures);
|
require(wechatAppId, "WECHAT_APPID", failures);
|
||||||
require(wechatAppSecret, "WECHAT_APPSECRET", failures);
|
require(wechatAppSecret, "WECHAT_APPSECRET", failures);
|
||||||
requireHttpsUrl(wechatRedirectUri, "WECHAT_REDIRECT_URI", failures);
|
|
||||||
if (text(sessionSecret).length() < 32 || "dev-change-me".equals(sessionSecret)) {
|
if (text(sessionSecret).length() < 32 || "dev-change-me".equals(sessionSecret)) {
|
||||||
failures.add("PETSTORE_SESSION_SECRET(至少32字符且非默认值)");
|
failures.add("PETSTORE_SESSION_SECRET(至少32字符且非默认值)");
|
||||||
}
|
}
|
||||||
@ -97,13 +93,6 @@ public class ProductionConfigurationValidator {
|
|||||||
if (text(value).isEmpty()) failures.add(name);
|
if (text(value).isEmpty()) failures.add(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void requireHttpsUrl(String value, String name, List<String> failures) {
|
|
||||||
String normalized = text(value);
|
|
||||||
if (!isSafeHttpsUrl(normalized)) {
|
|
||||||
failures.add(name + "必须为生产https地址");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void requireHttpsOrigin(String value, String name, List<String> failures) {
|
private static void requireHttpsOrigin(String value, String name, List<String> failures) {
|
||||||
if (!isSafeHttpsOrigin(text(value))) {
|
if (!isSafeHttpsOrigin(text(value))) {
|
||||||
failures.add(name + "必须为生产https源");
|
failures.add(name + "必须为生产https源");
|
||||||
@ -124,14 +113,6 @@ public class ProductionConfigurationValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isSafeHttpsUrl(String value) {
|
|
||||||
try {
|
|
||||||
return isSafeHttpsUri(URI.create(value));
|
|
||||||
} catch (IllegalArgumentException ignored) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isSafeHttpsUri(URI uri) {
|
private static boolean isSafeHttpsUri(URI uri) {
|
||||||
String host = text(uri.getHost()).toLowerCase();
|
String host = text(uri.getHost()).toLowerCase();
|
||||||
return "https".equalsIgnoreCase(uri.getScheme())
|
return "https".equalsIgnoreCase(uri.getScheme())
|
||||||
|
|||||||
@ -3,31 +3,20 @@ package com.petstore.config;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/** 微信小程序服务端凭据;不承载已下线的网页 OAuth redirect 配置。 */
|
||||||
@Configuration
|
@Configuration
|
||||||
public class WechatConfig {
|
public class WechatConfig {
|
||||||
// TODO: 替换为实际的微信开放平台 AppID 和 AppSecret
|
@Value("${wechat.appid:}")
|
||||||
@Value("${wechat.appid:YOUR_APPID}")
|
|
||||||
private String appid;
|
private String appid;
|
||||||
|
|
||||||
@Value("${wechat.appsecret:YOUR_APPSECRET}")
|
@Value("${wechat.appsecret:}")
|
||||||
private String appsecret;
|
private String appsecret;
|
||||||
|
|
||||||
@Value("${wechat.redirect_uri:http://localhost:8080/api/wechat/callback}")
|
public String getAppid() {
|
||||||
private String redirectUri;
|
return appid;
|
||||||
|
}
|
||||||
|
|
||||||
public String getAppid() { return appid; }
|
public String getAppsecret() {
|
||||||
public String getAppsecret() { return appsecret; }
|
return appsecret;
|
||||||
public String getRedirectUri() { return redirectUri; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取微信授权跳转地址
|
|
||||||
*/
|
|
||||||
public String getAuthorizeUrl() {
|
|
||||||
return "https://open.weixin.qq.com/connect/qrconnect" +
|
|
||||||
"?appid=" + appid +
|
|
||||||
"&redirect_uri=" + redirectUri +
|
|
||||||
"&response_type=code" +
|
|
||||||
"&scope=snsapi_login" +
|
|
||||||
"&state=petstore#wechat_redirect";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
package com.petstore.controller;
|
|
||||||
|
|
||||||
import com.petstore.config.WechatConfig;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/wechat")
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@CrossOrigin
|
|
||||||
public class WechatController {
|
|
||||||
private final WechatConfig wechatConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取微信授权跳转地址
|
|
||||||
*/
|
|
||||||
@GetMapping("/authorize")
|
|
||||||
public Map<String, Object> getAuthorizeUrl() {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("code", 200);
|
|
||||||
result.put("data", wechatConfig.getAuthorizeUrl());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 微信授权回调
|
|
||||||
* 通过 code 换取 access_token,返回用户信息
|
|
||||||
*/
|
|
||||||
@GetMapping("/callback")
|
|
||||||
public Map<String, Object> callback(@RequestParam String code, @RequestParam String state) {
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
|
|
||||||
// TODO: 通过 code 调用微信接口换取 openid 和 access_token
|
|
||||||
// https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
|
|
||||||
|
|
||||||
// 演示用:直接返回成功
|
|
||||||
result.put("code", 200);
|
|
||||||
result.put("message", "微信授权成功");
|
|
||||||
result.put("data", Map.of(
|
|
||||||
"openid", "demo_openid_" + code,
|
|
||||||
"nickname", "微信用户",
|
|
||||||
"avatar", ""
|
|
||||||
));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -43,7 +43,6 @@ logging:
|
|||||||
wechat:
|
wechat:
|
||||||
appid: ${WECHAT_APPID:}
|
appid: ${WECHAT_APPID:}
|
||||||
appsecret: ${WECHAT_APPSECRET:}
|
appsecret: ${WECHAT_APPSECRET:}
|
||||||
redirect_uri: ${WECHAT_REDIRECT_URI:http://localhost:8080/api/wechat/callback}
|
|
||||||
|
|
||||||
# 服务回顾短片(ffmpeg;可选 ffprobe 读时长/音轨)
|
# 服务回顾短片(ffmpeg;可选 ffprobe 读时长/音轨)
|
||||||
app:
|
app:
|
||||||
|
|||||||
@ -71,7 +71,6 @@ app:
|
|||||||
wechat:
|
wechat:
|
||||||
appid: ${WECHAT_APPID:}
|
appid: ${WECHAT_APPID:}
|
||||||
appsecret: ${WECHAT_APPSECRET:}
|
appsecret: ${WECHAT_APPSECRET:}
|
||||||
redirect_uri: ${WECHAT_REDIRECT_URI:http://localhost:8080/api/wechat/callback}
|
|
||||||
|
|
||||||
# 最小鉴权上下文:HMAC session token(不引入完整 Spring Security)
|
# 最小鉴权上下文:HMAC session token(不引入完整 Spring Security)
|
||||||
# 生产必须通过环境变量覆盖 PETSTORE_SESSION_SECRET;勿提交真实密钥。
|
# 生产必须通过环境变量覆盖 PETSTORE_SESSION_SECRET;勿提交真实密钥。
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
package com.petstore.config;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
class LegacyWechatOauthRemovalContractTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unimplementedWebOauthSurfaceDoesNotReturnToThePilotRelease() throws Exception {
|
||||||
|
assertFalse(Files.exists(Path.of(
|
||||||
|
"src/main/java/com/petstore/controller/WechatController.java"
|
||||||
|
)));
|
||||||
|
|
||||||
|
for (Path path : List.of(
|
||||||
|
Path.of("src/main/java/com/petstore/auth/AuthInterceptor.java"),
|
||||||
|
Path.of("src/main/java/com/petstore/config/WechatConfig.java"),
|
||||||
|
Path.of("src/main/resources/application.yml"),
|
||||||
|
Path.of("src/main/resources/application-example.yml"),
|
||||||
|
Path.of("deploy/release-preflight.sh"),
|
||||||
|
Path.of("deploy/petstore-backend.env.example"),
|
||||||
|
Path.of("README.md")
|
||||||
|
)) {
|
||||||
|
String content = Files.readString(path);
|
||||||
|
assertFalse(content.contains("/api/wechat/authorize"), path.toString());
|
||||||
|
assertFalse(content.contains("/api/wechat/callback"), path.toString());
|
||||||
|
assertFalse(content.contains("WECHAT_REDIRECT_URI"), path.toString());
|
||||||
|
assertFalse(content.contains("demo_openid_"), path.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -61,7 +61,6 @@ class ProductionConfigurationValidatorTest {
|
|||||||
"database-secret",
|
"database-secret",
|
||||||
"wx-app-id",
|
"wx-app-id",
|
||||||
"wx-app-secret",
|
"wx-app-secret",
|
||||||
"https://api.chongxiaota.cn/api/wechat/callback",
|
|
||||||
"01234567890123456789012345678901",
|
"01234567890123456789012345678901",
|
||||||
smsCode,
|
smsCode,
|
||||||
"https://api.chongxiaota.cn",
|
"https://api.chongxiaota.cn",
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user