This commit is contained in:
孙小云 2025-07-18 16:11:31 +08:00
parent 12a5550688
commit e9208eb51b
20 changed files with 451 additions and 88 deletions

190
README.md Normal file
View File

@ -0,0 +1,190 @@
# OAuth2/OIDC 统一认证系统
这是一个基于Spring Boot的多服务架构使用OAuth2和OIDC实现统一认证。
## 系统架构
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ 前端应用 │ │ 网关服务 │ │ 资源服务 │
│ (a.sun.com) │───▶│ (8080) │───▶│ (8081) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ OIDC服务器 │ │ JWT验证 │ │ 受保护资源 │
│ (9000) │ │ Token转发 │ │ API端点 │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
## 服务组件
### 1. OIDC授权服务器 (oidc/)
- **端口**: 9000
- **功能**: OAuth2/OIDC授权服务器
- **客户端配置**:
- 客户端ID: `a-client`
- 客户端密钥: `a-secret`
- 重定向URI: `http://a.sun.com/callback`
- 授权类型: `authorization_code`, `refresh_token`
- 作用域: `openid`, `read`
### 2. 网关服务 (gateway/)
- **端口**: 8080
- **功能**: API网关JWT验证Token转发
- **特性**:
- JWT Token验证
- 路由转发到后端服务
- Token Relay功能
### 3. 资源服务 (resourceservice/)
- **端口**: 8081
- **功能**: 后端API服务
- **端点**: `/api/resource` (GET)
### 4. 前端应用 (resourceservicehtml/)
- **域名**: a.sun.com
- **功能**: 用户界面OIDC登录流程
- **页面**:
- `index.html`: 主页面包含登录和API调用功能
- `callback.html`: OIDC回调处理页面
- `test.html`: 配置测试页面
## 认证流程
### 1. 用户登录流程
1. 用户访问 `http://a.sun.com`
2. 前端检查本地Token
3. 如果没有Token重定向到OIDC登录页面
4. 用户在OIDC服务器登录
5. OIDC服务器重定向回 `http://a.sun.com/callback`
6. 前端用授权码交换Token
7. Token保存到本地存储
### 2. API调用流程
1. 前端使用Token调用 `/api/resource`
2. 请求经过nginx代理到网关
3. 网关验证JWT Token
4. 网关转发请求到资源服务
5. 资源服务返回数据
## 配置说明
### OIDC服务器配置
- 使用内存存储用户和客户端
- 默认用户: `user/password`
- RSA密钥对用于JWT签名
### 网关配置
- WebFlux架构
- JWT Token验证
- 路由配置指向资源服务
### 前端配置
- OIDC客户端配置
- Token本地存储
- 状态参数防CSRF攻击
## 启动顺序
1. **启动OIDC服务器**:
```bash
cd oidc
mvn spring-boot:run
```
2. **启动网关服务**:
```bash
cd gateway
mvn spring-boot:run
```
3. **启动资源服务**:
```bash
cd resourceservice
mvn spring-boot:run
```
4. **配置nginx**:
```bash
sudo cp resourceservicehtml/nginx.conf /opt/homebrew/etc/nginx/nginx.conf
sudo nginx -s reload
```
5. **配置hosts**:
```
127.0.0.1 a.sun.com
127.0.0.1 oidc.sun.com
```
## 测试
### 1. 访问前端
- 主页: `http://a.sun.com`
- 测试页面: `http://a.sun.com/test.html`
### 2. 测试OIDC端点
- JWKS: `http://localhost:9000/oauth2/jwks`
- 授权端点: `http://localhost:9000/oauth2/authorize`
- Token端点: `http://localhost:9000/oauth2/token`
### 3. 测试API
- 资源API: `http://a.sun.com/api/resource`
## 安全特性
- JWT Token验证
- 状态参数防CSRF攻击
- HTTPS重定向支持
- Token过期处理
- 刷新Token机制
## 故障排除
### 常见问题
1. **OIDC发现端点无法访问**
- 检查OIDC服务器是否启动
- 验证安全配置
2. **Token验证失败**
- 检查Token是否过期
- 验证JWT签名
3. **nginx权限问题**
- 确保nginx用户有读取权限
- 检查目录权限设置
4. **CORS问题**
- 检查前端域名配置
- 验证重定向URI设置
## 开发说明
### 添加新客户端
`oidc/src/main/java/com/tuoheng/oauth/oidc/config/SecurityConfig.java` 中添加新的 `RegisteredClient`
### 添加新API端点
`resourceservice` 中添加新的Controller和端点。
### 修改前端配置
`resourceservicehtml/index.html` 中修改 `oidcConfig` 对象。
## 技术栈
- **后端**: Spring Boot 3.x, Spring Security, Spring Cloud Gateway
- **前端**: HTML5, JavaScript, CSS3
- **认证**: OAuth2, OpenID Connect, JWT
- **代理**: nginx
- **构建**: Maven
## 版本信息
- Spring Boot: 3.5.3
- Spring Security: 6.5.1
- Spring Cloud Gateway: 4.1.1
- Java: 17
mkcert -uninstall && mkcert -install && mkcert -key-file ssl/private.key -cert-file ssl/certificate.crt "*.local.com" local.com

View File

@ -5,7 +5,17 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="b713637a-3b19-4c5b-9e88-95e35dd83d2e" name="更改" comment=""> <list default="true" id="b713637a-3b19-4c5b-9e88-95e35dd83d2e" name="更改" comment="">
<change beforePath="$PROJECT_DIR$/../nginx/nginx.conf" beforeDir="false" afterPath="$PROJECT_DIR$/../nginx/nginx.conf" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../oidc/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../oidc/pom.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/../oidc/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/../oidc/pom.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../oidc/src/main/java/com/tuoheng/oauth/oidc/config/SecurityConfig.java" beforeDir="false" afterPath="$PROJECT_DIR$/../oidc/src/main/java/com/tuoheng/oauth/oidc/config/SecurityConfig.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../oidc/src/main/resources/application.properties" beforeDir="false" afterPath="$PROJECT_DIR$/../oidc/src/main/resources/application.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../resourceservice/target/classes/com/tuoheng/resourceservice/HelloController.class" beforeDir="false" afterPath="$PROJECT_DIR$/../resourceservice/target/classes/com/tuoheng/resourceservice/HelloController.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../resourceservice/target/classes/com/tuoheng/resourceservice/ResourceServiceApplication.class" beforeDir="false" afterPath="$PROJECT_DIR$/../resourceservice/target/classes/com/tuoheng/resourceservice/ResourceServiceApplication.class" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../resourceservicehtml/callback.html" beforeDir="false" afterPath="$PROJECT_DIR$/../resourceservicehtml/callback.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../resourceservicehtml/index.html" beforeDir="false" afterPath="$PROJECT_DIR$/../resourceservicehtml/index.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../resourceservicehtml/test.html" beforeDir="false" afterPath="$PROJECT_DIR$/../resourceservicehtml/test.html" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../ssl/certificate.crt" beforeDir="false" afterPath="$PROJECT_DIR$/../ssl/certificate.crt" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../ssl/private.key" beforeDir="false" afterPath="$PROJECT_DIR$/../ssl/private.key" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -105,7 +115,7 @@
<workItem from="1752741336600" duration="2274000" /> <workItem from="1752741336600" duration="2274000" />
<workItem from="1752745264222" duration="2832000" /> <workItem from="1752745264222" duration="2832000" />
<workItem from="1752751160098" duration="1320000" /> <workItem from="1752751160098" duration="1320000" />
<workItem from="1752798688493" duration="5384000" /> <workItem from="1752798688493" duration="8093000" />
</task> </task>
<servers /> <servers />
</component> </component>

27
index.html Normal file
View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>OAuth2 登录</title>
<style>
body { font-family: Arial, sans-serif; background: #f5f5f5; }
.login-box { width: 300px; margin: 100px auto; background: #fff; padding: 30px; border-radius: 8px; box-shadow: 0 2px 8px #ccc; }
.login-box h2 { text-align: center; margin-bottom: 20px; }
.login-box input { width: 100%; padding: 8px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; }
.login-box button { width: 100%; padding: 10px; background: #1976d2; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
.login-box button:disabled { background: #aaa; }
.msg { color: #d32f2f; text-align: center; }
</style>
</head>
<body>
<div class="login-box">
<h2>OAuth2 登录</h2>
<form method="post" action="/login">
<input type="text" name="username" placeholder="用户名" required autocomplete="username">
<input type="password" name="password" placeholder="密码" required autocomplete="current-password">
<button type="submit">登录</button>
</form>
<div class="msg"></div>
</div>
</body>
</html>

28
install-key.pem Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC/FXUfK+ry8hUJ
H3mYhlDukXW05kLTRjyMd8G7s8Qt3M9B1e92rxHNKcU45+lSDTF8bb1JF6DzCXmo
Clon/6H8YvHzD3RyypmqCRi17qQwch5Jtprtb8lCJVcBClDUFjoeTtPXpJMXdH5P
xjLR6Aa3Z4HpBpIKMsvp9ZaAG0XQDTUo8urcIeV16iRsRfqLRufwQ4gVlVxuOJVb
mHmKyBJZja+iBVlsiVBggMQJq2a2HQE4Lk2lv+7uWqbJ2MrL8d8aOkJIt+YtqlxV
mv2vLoLw2hAd4emnF2ijFEYJtNzepJ5X0nltzAHQd+uW44sTjWlMOopqKpqD1+R9
ZNsMsdupAgMBAAECggEAGVETenTMJTITvWixKJcrI+Cb0sLrOajFnurC/UZ9CIKH
5zYcCwJ4/lC5c6euTxO2acD0YjnCNlEcEDqG5WPGJ3VIjyaODCNxpoicAIbEtDJ6
dtO9xRWZea0O0PF38hGb06YoBRsl7eaeUZ114D+4nBYXrTMUqEtAnxfNv91dK4pJ
byV1Wp+bHgXzIH4kM3dPjOsDwAUIgtmUFrSXdsG+OzlTZxUiCShAWFXe9W5KiLR0
fZJkCu2qhLg7QQvgrGmp4PRov97dJqbh0XXCyDLHmVbjqrkCD8AcdBqtRI45B2TA
STbNcq6nlXf2VOsVjHnDoIEDwpDDeCDesDfnSVPzgQKBgQDE75cWIjH7/DXBJ0rN
x81v2MwEZw3zLdq5GNENPitbHRgPZ8jvEsABg5hJButucjyxOPopdYQg+c6QhkMG
qumig3k9aOVonVOrF3+iXGoMWjp3yZypS15hJzx1M7WneY1ctE3RQ2tIwOk8xKPB
BXSOsbAlVIlUfRqHO9h0m2JTeQKBgQD4ZI3oXAkKAYfGlZRY+FJ+As/p9PhQovv6
nq/F1BwF9NvLjihzt71pYVbEcZkkUA2QHxQEMKerVLpRIhaschmnCIyqh8w1wfyj
QfO5fi3e3amtkXyRU1CnXPh4ywtWmpvZtmoDqol13Zjjzjose2GGt4hpZsBggVHQ
6jPgrAENsQKBgHMYi7aV2ZyptEjky+UkZr59eA8Co7aCEBipllQlB3XCtTMbtuVy
keDQpgnYD3SHM01oPVxJoCUdmkoBDd8xuEYQjKUFTz4q5KFTpHahiCEcApvLqtGO
iORC6CSfSgVNFv8dKXWp72OfyzCGxCWlKI/U7VuD4pcMXpq2sTTFM1wRAoGBANZW
rr2a7ZHc0DTkTiaX4VcrRg40fTHX8mfJFxQ2fBgHusJj4TQ5kRCmFiFdhTB4g7uh
lbwn4AdQDZaFO9uCefBQyFE+7VBWHJMkDhQ6dYqi7BACQuOEaUyCRUa2rwoEUAgG
CGUxe3xhw9SP2FMaBIYjSWrqZ4bfEKKd9jYhNqeBAoGBAK9adFsDFQtT2O4u25hx
Yv7m9ZG5NbvhuQuEK7skdDb+HZHK/1JIV8WG16zTe897I7IC+OYezKLA1fbBnna0
rARwY/NZL1/CA5SmPGgK5Vy9IJbyJ50tQqFUGaF0SF/5jAuzCLa82d3XWUOlUgWH
zkITyIa3HhT//7eFdH3sa+Hc
-----END PRIVATE KEY-----

26
install.pem Normal file
View File

@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEWTCCAsGgAwIBAgIQBKpJXSm53rsT3EorgGHTXjANBgkqhkiG9w0BAQsFADCB
kzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTQwMgYDVQQLDCtzdW5w
ZW5nQHN1bnBlbmdkZU1hY0Jvb2stUHJvLmxvY2FsICjlrZnpuY8pMTswOQYDVQQD
DDJta2NlcnQgc3VucGVuZ0BzdW5wZW5nZGVNYWNCb29rLVByby5sb2NhbCAo5a2Z
6bmPKTAeFw0yNTA3MTgwNzU0NTRaFw0yNzEwMTgwNzU0NTRaMF8xJzAlBgNVBAoT
Hm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTE0MDIGA1UECwwrc3VucGVu
Z0BzdW5wZW5nZGVNYWNCb29rLVByby5sb2NhbCAo5a2Z6bmPKTCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAL8VdR8r6vLyFQkfeZiGUO6RdbTmQtNGPIx3
wbuzxC3cz0HV73avEc0pxTjn6VINMXxtvUkXoPMJeagKWif/ofxi8fMPdHLKmaoJ
GLXupDByHkm2mu1vyUIlVwEKUNQWOh5O09ekkxd0fk/GMtHoBrdngekGkgoyy+n1
loAbRdANNSjy6twh5XXqJGxF+otG5/BDiBWVXG44lVuYeYrIElmNr6IFWWyJUGCA
xAmrZrYdATguTaW/7u5apsnYysvx3xo6Qki35i2qXFWa/a8ugvDaEB3h6acXaKMU
Rgm03N6knlfSeW3MAdB365bjixONaUw6imoqmoPX5H1k2wyx26kCAwEAAaNcMFow
DgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaA
FF75UQ00SANrn+spMSH1elRO+PN9MBIGA1UdEQQLMAmCB2luc3RhbGwwDQYJKoZI
hvcNAQELBQADggGBAF7D7B5a2ZZ4vSgqGx8e0p08dVfUHM3v85pHi7VO7xVaHKWh
rS09S5sTVvDiEOujHXlnrGYBQjOZ3/DwBJyNQQG83EWZ1/TWyYIrmBUUBzpaAJ+t
gLap5KaQoV+xo46PfXCG5bQmqllcsZKWvY5c8chECHXWJMX7cF8/9XBI7gywIoRN
PRVKoXjh5SAoOzI8VjnQ3gEXywnuBfaSu/76oNT9ty/Y94G6RhYLq+s0Hpj5FAi2
KbznwKAk14PfUDuZenPKac1NFNdlRnjaBoIQBtikSQjM9TaBMD1xvMhmxO779/0P
kBrtzJcYez/SOrTFGUWMaM9CQSqBrINOJah56QB0eP1o1NvnzV7L/2c0kDQVJMMM
oy+1Qfx9OhYvAv/xmORWNlGw+oF4bh71qqlWcQ3mqrYBsYIfxt/1MsYawtg3waer
h/CO5vPOlLplqW8H7hrU8e4zq9QRMSLlMNycYQs+S8JTlL42lT6a4dXDXBwx4NlP
QeQTR9ak1+yacEzE2w==
-----END CERTIFICATE-----

View File

@ -26,6 +26,12 @@ http {
'$status $body_bytes_sent "$http_referer" ' '$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"'; '"$http_user_agent" "$http_x_forwarded_for"';
# 在http块添加main_cookie日志格式
log_format main_cookie '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'cookie:"$http_cookie"';
# 访问日志 # 访问日志
access_log /tmp/nginx_access.log main; access_log /tmp/nginx_access.log main;
@ -42,10 +48,10 @@ http {
gzip_min_length 1024; gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
# 前端应用服务器 (a.com) - HTTPS # 前端应用服务器 (a.local.com) - HTTPS
server { server {
listen 443 ssl; listen 443 ssl;
server_name a.com; server_name a.local.com;
# SSL配置 # SSL配置
ssl_certificate /Users/sunpeng/workspace/remote/oauth2/ssl/certificate.crt; ssl_certificate /Users/sunpeng/workspace/remote/oauth2/ssl/certificate.crt;
@ -67,6 +73,11 @@ http {
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
} }
# 防止前端server拦截OIDC登录页/login直接返回404
location = /login {
return 404;
}
# 处理OPTIONS预检请求 # 处理OPTIONS预检请求
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y; expires 1y;
@ -82,14 +93,14 @@ http {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
# 处理CORS # 处理CORS
add_header Access-Control-Allow-Origin "https://a.com" always; add_header Access-Control-Allow-Origin "https://a.local.com" always;
add_header Access-Control-Allow-Credentials "true" always; add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
# 处理OPTIONS请求 # 处理OPTIONS请求
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "https://a.com" always; add_header Access-Control-Allow-Origin "https://a.local.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
add_header Access-Control-Max-Age 1728000; add_header Access-Control-Max-Age 1728000;
@ -108,14 +119,14 @@ http {
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
# 处理CORS # 处理CORS
add_header Access-Control-Allow-Origin "https://a.com" always; add_header Access-Control-Allow-Origin "https://a.local.com" always;
add_header Access-Control-Allow-Credentials "true" always; add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
# 处理OPTIONS请求 # 处理OPTIONS请求
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "https://a.com" always; add_header Access-Control-Allow-Origin "https://a.local.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
add_header Access-Control-Max-Age 1728000; add_header Access-Control-Max-Age 1728000;
@ -125,15 +136,45 @@ http {
} }
} }
# /oidc-logout 路径转发到 OIDC 服务
location /oidc-logout {
proxy_pass http://localhost:9000/oidc-logout;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Cookie $http_cookie;
# 记录cookie内容到专用日志
access_log /tmp/nginx_oidc_logout.log main_cookie;
# 处理CORS
add_header Access-Control-Allow-Origin "https://a.local.com" always;
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
# 处理OPTIONS请求
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "https://a.local.com" always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
add_header Access-Control-Max-Age 1728000;
add_header Content-Type "text/plain; charset=utf-8";
add_header Content-Length 0;
return 204;
}
}
# 错误页面 # 错误页面
error_page 404 /404.html; error_page 404 /404.html;
error_page 500 502 503 504 /50x.html; error_page 500 502 503 504 /50x.html;
} }
# OIDC服务器代理 (oidc.com) - HTTPS # OIDC服务器代理 (oidc.local.com) - HTTPS
server { server {
listen 443 ssl; listen 443 ssl;
server_name oidc.com; server_name oidc.local.com;
# SSL配置 # SSL配置
ssl_certificate /Users/sunpeng/workspace/remote/oauth2/ssl/certificate.crt; ssl_certificate /Users/sunpeng/workspace/remote/oauth2/ssl/certificate.crt;
@ -144,25 +185,21 @@ http {
# 代理到OIDC服务器 # 代理到OIDC服务器
location / { location / {
proxy_pass http://localhost:9000; proxy_pass http://localhost:9000/;
proxy_set_header Host $host; proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header Cookie $http_cookie; proxy_set_header Cookie $http_cookie;
add_header Access-Control-Allow-Origin "https://a.local.com" always;
# 处理CORS
add_header Access-Control-Allow-Origin "https://a.com" always;
add_header Access-Control-Allow-Credentials "true" always; add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
# 移除CSP限制 (开发环境)
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: http: https:;" always;
# 处理OPTIONS请求
if ($request_method = 'OPTIONS') { if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin "https://a.com" always; add_header Access-Control-Allow-Origin "https://a.local.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"; add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"; add_header Access-Control-Allow-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
add_header Access-Control-Max-Age 1728000; add_header Access-Control-Max-Age 1728000;
@ -180,7 +217,7 @@ http {
# HTTP重定向到HTTPS # HTTP重定向到HTTPS
server { server {
listen 80; listen 80;
server_name a.com oidc.com; server_name a.local.com oidc.local.com;
return 301 https://$server_name$request_uri; return 301 https://$server_name$request_uri;
} }

View File

@ -80,6 +80,7 @@ public class SecurityConfig {
.requestMatchers("/oauth2/jwks").permitAll() .requestMatchers("/oauth2/jwks").permitAll()
.requestMatchers("/logout").permitAll() .requestMatchers("/logout").permitAll()
.requestMatchers("/login").permitAll() .requestMatchers("/login").permitAll()
.requestMatchers("/oidc-logout").permitAll()
.anyRequest().authenticated() .anyRequest().authenticated()
) )
.oauth2ResourceServer(oauth2 -> oauth2.jwt()) // 新增支持JWT .oauth2ResourceServer(oauth2 -> oauth2.jwt()) // 新增支持JWT
@ -112,14 +113,14 @@ public class SecurityConfig {
// 注册客户端内存存储 // 注册客户端内存存储
@Bean @Bean
public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) { public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
// a.com 前端应用的 client // a.sun.com 前端应用的 client
RegisteredClient aClient = RegisteredClient.withId(UUID.randomUUID().toString()) RegisteredClient aClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("a-client") .clientId("a-client")
.clientSecret(passwordEncoder.encode("a-secret")) .clientSecret(passwordEncoder.encode("a-secret"))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("https://a.com/callback") .redirectUri("https://a.local.com/callback")
.scope(OidcScopes.OPENID) .scope(OidcScopes.OPENID)
.scope("read") .scope("read")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build()) .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())

View File

@ -0,0 +1,31 @@
package com.tuoheng.oauth.oidc.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class OidcLogoutController {
@GetMapping("/oidc-logout")
public String oidcLogout(
@RequestParam(value = "id_token_hint", required = false) String idTokenHint,
@RequestParam(value = "post_logout_redirect_uri", required = false) String redirectUri,
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) {
// 调用Spring Security的logout
new SecurityContextLogoutHandler().logout(request, response, authentication);
// 重定向到指定页面
if (redirectUri != null && !redirectUri.isEmpty()) {
return "redirect:" + redirectUri;
}
return "redirect:/login?logout";
}
}

View File

@ -1,4 +1,6 @@
spring.application.name=oidc spring.application.name=oidc
server.port=9000 server.port=9000
spring.security.oauth2.authorization-server.issuer-url: https://oidc.com spring.security.oauth2.authorization-server.issuer-url: https://oidc.local.com
server.servlet.session.timeout=1m server.servlet.session.timeout=1m
server.servlet.session.cookie.domain=local.com
server.forward-headers-strategy=framework

View File

@ -1 +0,0 @@
server.port=8081

View File

@ -63,9 +63,9 @@
const oidcConfig = { const oidcConfig = {
clientId: 'a-client', clientId: 'a-client',
clientSecret: 'a-secret', clientSecret: 'a-secret',
redirectUri: 'https://a.com/callback', redirectUri: 'https://a.local.com/callback',
tokenEndpoint: 'https://oidc.com/oauth2/token', tokenEndpoint: 'https://oidc.local.com/oauth2/token',
userInfoEndpoint: 'https://oidc.com/userinfo' userInfoEndpoint: 'https://oidc.local.com/userinfo'
}; };
// 页面加载时执行 // 页面加载时执行

View File

@ -74,10 +74,10 @@
const oidcConfig = { const oidcConfig = {
clientId: 'a-client', clientId: 'a-client',
clientSecret: 'a-secret', clientSecret: 'a-secret',
redirectUri: 'https://a.com/callback', redirectUri: 'https://a.local.com/callback',
authorizationEndpoint: 'https://oidc.com/oauth2/authorize', authorizationEndpoint: 'https://oidc.local.com/oauth2/authorize',
tokenEndpoint: 'https://oidc.com/oauth2/token', tokenEndpoint: 'https://oidc.local.com/oauth2/token',
userInfoEndpoint: 'https://oidc.com/userinfo', userInfoEndpoint: 'https://oidc.local.com/userinfo',
scope: 'openid read' scope: 'openid read'
}; };
@ -110,7 +110,7 @@
// 验证token // 验证token
function validateToken(token) { function validateToken(token) {
fetch('https://oidc.com/userinfo', { fetch('https://oidc.local.com/userinfo', {
headers: { headers: {
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
} }
@ -266,13 +266,21 @@
// 退出登录 // 退出登录
function logout() { function logout() {
// 通过nginx代理调用logout
// 清理本地token // 清理本地token
localStorage.removeItem('access_token'); localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token'); localStorage.removeItem('refresh_token');
localStorage.removeItem('oauth_state'); localStorage.removeItem('oauth_state');
// 跳转到Spring Security默认的退出页面GET // 获取id_token如果有
window.location.href = 'https://oidc.com/logout'; const idToken = localStorage.getItem('id_token'); // 登录时保存id_token
// 退出后跳转到首页
const redirectUri = encodeURIComponent('https://a.local.com');
// 拼接logout url
let logoutUrl = `https://a.local.com/oidc-logout?post_logout_redirect_uri=${redirectUri}`;
if (idToken) {
logoutUrl += `&id_token_hint=${idToken}`;
}
// 跳转到OIDC logout端点
window.location.href = logoutUrl;
} }
// 生成随机字符串 // 生成随机字符串

View File

@ -76,11 +76,11 @@
const oidcConfig = { const oidcConfig = {
clientId: 'a-client', clientId: 'a-client',
clientSecret: 'a-secret', clientSecret: 'a-secret',
redirectUri: 'https://a.com/callback', redirectUri: 'https://a.local.com/callback',
authorizationEndpoint: 'https://oidc.com/oauth2/authorize', authorizationEndpoint: 'https://oidc.local.com/oauth2/authorize',
tokenEndpoint: 'https://oidc.com/oauth2/token', tokenEndpoint: 'https://oidc.local.com/oauth2/token',
userInfoEndpoint: 'https://oidc.com/userinfo', userInfoEndpoint: 'https://oidc.local.com/userinfo',
jwksEndpoint: 'https://oidc.com/oauth2/jwks', jwksEndpoint: 'https://oidc.local.com/oauth2/jwks',
scope: 'openid read' scope: 'openid read'
}; };

View File

@ -1,23 +1,26 @@
-----BEGIN CERTIFICATE----- -----BEGIN CERTIFICATE-----
MIID5jCCAs6gAwIBAgIUKxk4umxo+aYVDQ483sMNIjyV3iwwDQYJKoZIhvcNAQEL MIIEaTCCAtGgAwIBAgIRAO0/byq8GJJoBReBXr16yJUwDQYJKoZIhvcNAQELBQAw
BQAwZDELMAkGA1UEBhMCQ04xEDAOBgNVBAgMB0JlaWppbmcxEDAOBgNVBAcMB0Jl gZMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE0MDIGA1UECwwrc3Vu
aWppbmcxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQLDAJJVDEOMAwGA1UE cGVuZ0BzdW5wZW5nZGVNYWNCb29rLVByby5sb2NhbCAo5a2Z6bmPKTE7MDkGA1UE
AwwFKi5jb20wHhcNMjUwNzE4MDEyNTQzWhcNMjYwNzE4MDEyNTQzWjBkMQswCQYD AwwybWtjZXJ0IHN1bnBlbmdAc3VucGVuZ2RlTWFjQm9vay1Qcm8ubG9jYWwgKOWt
VQQGEwJDTjEQMA4GA1UECAwHQmVpamluZzEQMA4GA1UEBwwHQmVpamluZzEUMBIG mem5jykwHhcNMjUwNzE4MDc1ODM2WhcNMjcxMDE4MDc1ODM2WjBfMScwJQYDVQQK
A1UECgwLRGV2ZWxvcG1lbnQxCzAJBgNVBAsMAklUMQ4wDAYDVQQDDAUqLmNvbTCC Ex5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxNDAyBgNVBAsMK3N1bnBl
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANmJXA4sFsmdGU36bN5ThBAJ bmdAc3VucGVuZ2RlTWFjQm9vay1Qcm8ubG9jYWwgKOWtmem5jykwggEiMA0GCSqG
JZTY1vUaewmM41+IbSClPQEiO2/gK/tmDWBE82+IG7TEqbbLqUkORWFmp3bn0wgE SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgP4hfcsywJ9SbioGTODJDSG8yx1djhPp4
nW3JamZMfPo43rr+3i9OmxPzQUMzIn5HTh9+QPxAPGUafbHJTcIc3ixCXHu150+6 yQZZ2jBqDHviF8rM+JcryTfR2Giuy9bi2ygNr18R8DDotFAp+S6LJRaS82rusiY4
Mh4v/fkRk6Yog93VIwpADYEyocVn8SkTzGn2FsF5R+hu/t+bGAO+e5iYiwlaheCj +uvfptlBKF4/E967GHtPGN59LanP8d3k3aPevpdCnAKw94doPJFmEm328h28AQYE
yLfgzOuNo0jkxr1wmV2juN1CJ0nntqHaajMDqszQZXeBQ1K/UMgt2Ln2CRiQoatl v+VeQEmYbYkpuOVT5eAjDKp+GFDx+zzXQN+U4domZPHZe0rCKFtPTHzk18yXuSTZ
UfeFBF/urK1VI8TrvayeMyMkXiAOxxvKLqbvLmvwKlr7iRtrE0SUsOdfjA4h788C q2ssAcxP27QwPPusccND3oESyF46lEbVjxRvDIYXLOHxahN7RKsSXTHaGqpGsiUH
AwEAAaOBjzCBjDAdBgNVHQ4EFgQUZqqbZSCLhcMWDsRQYZ5/vYjPVFMwHwYDVR0j Si2ppRRQmTzOMxkQB9ooW1Q2yatz5gzAq0dc8ft3VLowmloas6lPAgMBAAGjazBp
BBgwFoAUZqqbZSCLhcMWDsRQYZ5/vYjPVFMwDwYDVR0TAQH/BAUwAwEB/zA5BgNV MA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAW
HREEMjAwggUqLmNvbYIFYS5jb22CBWIuY29tgghvaWRjLmNvbYIJbG9jYWxob3N0 gBRe+VENNEgDa5/rKTEh9XpUTvjzfTAhBgNVHREEGjAYggsqLmxvY2FsLmNvbYIJ
hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQC78BfqEOofJRjECXRLspkTRfz8LsN2 bG9jYWwuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQAL7KrS2TMcOKIoMzTnFLv0skL9
EYu3bMvSmRjgbApY6Sh4Yg6LQUBRNkw17GMQ1WQTKVdygqZeQWYZvD1FgBHjoWwk evEf+9rERDPVb/J/iplaV5y1RqZjQDU9vCvsjT2igf5Xcqipu0p33pqpHwnMehDx
3yfi9tfuWiD5XMWri7gpuF3xlizvSIgc0dR14J3w++1xB/jq030/wMPaYbgBqgIR M37bxonJMLOYz91PLyPZPUt76YF5NoySBshXGo6o4+PmCHpMChrspXWT36hJmfX2
8z2KtnvpwJvLnifBJLJ3a3g24hyKkZ+Ye0/0f8aLMZtUBB99QY6PMe9E3GDnQbQi 8mMwkFA6iafHy0bV9Chy6h7lzymWd7XR21xMIVT25faf8hRbx/xZ/xUf0/tWlY8Q
4u4hPVzhWdxUQsoZ26NqgvobITx/hl7uKbr36NkGHUGgo9Vo+JEmy56HqZZjuwUK wp01RWFVcP2OWgeZxttnALvgXnJPFH29S2seB2TmkuZcz6ogcuCnYH2CQCC/yL0d
w8a4/EK0wjjPR9ooj4TWfYWfTr6Bde6v0awLENqDGJpFNacxgXGesoA4 Y5YFzUqsmDaF5YUdFcwb6MTfIQOXyGLbTgBdqPgRVtxp76BTVV4nDP5IxjET6++C
QzhJ1teqZTLdPvXIa+yqXQ/EI1NKNNRAYtmduH5kHgR1vKrQDMSHEhTaIevjN037
2dJEdRYi47OQIocqd80StGuRNL51dAYYjB3hj0WpQpKlzc8TurB8JeVwjzn5H9pv
A0k1s/OvborcCUguZOwVglAaVlHadmANhoel+q0=
-----END CERTIFICATE----- -----END CERTIFICATE-----

View File

@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
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
2fBQ0cw3/nOQ3QRZpfP5XRxdWQ== 5t/fBrOuubfljuu9ULZjMc0=
-----END PRIVATE KEY----- -----END PRIVATE KEY-----

1
token.txt Normal file
View File

@ -0,0 +1 @@
eyJraWQiOiIyMjYyZjU4MS0wNzRiLTRmZDctOGM2OC1mNGY3ODU1OGEyZWYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoiY2xpZW50IiwibmJmIjoxNzUyNzM5MjUyLCJzY29wZSI6WyJvcGVuaWQiXSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAwIiwiZXhwIjoxNzUyNzM5ODUyLCJpYXQiOjE3NTI3MzkyNTIsImp0aSI6ImMwNTZkNjAxLTBlZWYtNDBjYy04ODdiLTdjNjg1NDZlMzY2MSJ9.YifGLvHLe2QMInBgdJmW_us3m-Q41XX8RyaUfCv7J9gKT22wFAFtXUeXhF1bHXn9XUoPd3I33uBFclsYSs5XPqWlBhpiE-drRsZT-ZzbZ-x0YEELTkBEabYj-wivzHZZ9sVOxdV3yV7dN8-GpGJGyDglbX8tcWziZ4hCmSWIVCipb9cezBZdQYJiXe5SKb4qSmsQzF8F1CbBHuckfwXkggwVitW2YVKaeFDkV99RZRmcNzcvwVc6VU59cPr7VeirRTvjUyZKzpy9D9qtexKkSp0Fp0S4-J1NRHYh5mfaOBGSIQ52LE7c5Vz0lg23E0qCG3QvFkGF2i01fAIbEIpGLA