diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ee52dba
--- /dev/null
+++ b/README.md
@@ -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
\ No newline at end of file
diff --git a/gateway/.idea/workspace.xml b/gateway/.idea/workspace.xml
index 27d6df4..0c4a32d 100644
--- a/gateway/.idea/workspace.xml
+++ b/gateway/.idea/workspace.xml
@@ -5,7 +5,17 @@
+
+
+
+
+
+
+
+
+
+
@@ -105,7 +115,7 @@
-
+
diff --git a/gateway/target/classes/com/tuoheng/gateway/GatewayApplication.class b/gateway/target/classes/com/tuoheng/gateway/GatewayApplication.class
index 0e4fc1f..7bac476 100644
Binary files a/gateway/target/classes/com/tuoheng/gateway/GatewayApplication.class and b/gateway/target/classes/com/tuoheng/gateway/GatewayApplication.class differ
diff --git a/gateway/target/classes/com/tuoheng/gateway/config/SecurityConfig.class b/gateway/target/classes/com/tuoheng/gateway/config/SecurityConfig.class
index e26ff99..6a0a8e2 100644
Binary files a/gateway/target/classes/com/tuoheng/gateway/config/SecurityConfig.class and b/gateway/target/classes/com/tuoheng/gateway/config/SecurityConfig.class differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..6852f7a
--- /dev/null
+++ b/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+ OAuth2 登录
+
+
+
+
+
+
\ No newline at end of file
diff --git a/install-key.pem b/install-key.pem
new file mode 100644
index 0000000..d83c48b
--- /dev/null
+++ b/install-key.pem
@@ -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-----
diff --git a/install.pem b/install.pem
new file mode 100644
index 0000000..3879475
--- /dev/null
+++ b/install.pem
@@ -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-----
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index fd03845..1c4d050 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -26,6 +26,12 @@ http {
'$status $body_bytes_sent "$http_referer" '
'"$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;
@@ -42,10 +48,10 @@ http {
gzip_min_length 1024;
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 {
listen 443 ssl;
- server_name a.com;
+ server_name a.local.com;
# SSL配置
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";
}
+ # 防止前端server拦截OIDC登录页,/login直接返回404
+ location = /login {
+ return 404;
+ }
+
# 处理OPTIONS预检请求
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
@@ -82,14 +93,14 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
# 处理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-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";
# 处理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-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
add_header Access-Control-Max-Age 1728000;
@@ -108,14 +119,14 @@ http {
proxy_set_header X-Forwarded-Proto $scheme;
# 处理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-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";
# 处理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-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
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 500 502 503 504 /50x.html;
}
- # OIDC服务器代理 (oidc.com) - HTTPS
+ # OIDC服务器代理 (oidc.local.com) - HTTPS
server {
listen 443 ssl;
- server_name oidc.com;
+ server_name oidc.local.com;
# SSL配置
ssl_certificate /Users/sunpeng/workspace/remote/oauth2/ssl/certificate.crt;
@@ -144,25 +185,21 @@ http {
# 代理到OIDC服务器
location / {
- proxy_pass http://localhost:9000;
+ proxy_pass http://localhost:9000/;
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 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;
-
- # 处理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-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";
-
- # 移除CSP限制 (开发环境)
- add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' data: http: https:;" always;
-
- # 处理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-Headers "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization";
add_header Access-Control-Max-Age 1728000;
@@ -180,7 +217,7 @@ http {
# HTTP重定向到HTTPS
server {
listen 80;
- server_name a.com oidc.com;
+ server_name a.local.com oidc.local.com;
return 301 https://$server_name$request_uri;
}
diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/config/SecurityConfig.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/config/SecurityConfig.java
index 3e5fd6f..3b37e4a 100644
--- a/oidc/src/main/java/com/tuoheng/oauth/oidc/config/SecurityConfig.java
+++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/config/SecurityConfig.java
@@ -80,6 +80,7 @@ public class SecurityConfig {
.requestMatchers("/oauth2/jwks").permitAll()
.requestMatchers("/logout").permitAll()
.requestMatchers("/login").permitAll()
+ .requestMatchers("/oidc-logout").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2.jwt()) // 新增,支持JWT
@@ -112,14 +113,14 @@ public class SecurityConfig {
// 注册客户端(内存存储)
@Bean
public RegisteredClientRepository registeredClientRepository(PasswordEncoder passwordEncoder) {
- // a.com 前端应用的 client
+ // a.sun.com 前端应用的 client
RegisteredClient aClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("a-client")
.clientSecret(passwordEncoder.encode("a-secret"))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
- .redirectUri("https://a.com/callback")
+ .redirectUri("https://a.local.com/callback")
.scope(OidcScopes.OPENID)
.scope("read")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
diff --git a/oidc/src/main/java/com/tuoheng/oauth/oidc/controller/OidcLogoutController.java b/oidc/src/main/java/com/tuoheng/oauth/oidc/controller/OidcLogoutController.java
new file mode 100644
index 0000000..9ce3f56
--- /dev/null
+++ b/oidc/src/main/java/com/tuoheng/oauth/oidc/controller/OidcLogoutController.java
@@ -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";
+ }
+}
\ No newline at end of file
diff --git a/oidc/src/main/resources/application.properties b/oidc/src/main/resources/application.properties
index d95aee0..2d9b20f 100644
--- a/oidc/src/main/resources/application.properties
+++ b/oidc/src/main/resources/application.properties
@@ -1,4 +1,6 @@
spring.application.name=oidc
server.port=9000
-spring.security.oauth2.authorization-server.issuer-url: https://oidc.com
-server.servlet.session.timeout=1m
\ No newline at end of file
+spring.security.oauth2.authorization-server.issuer-url: https://oidc.local.com
+server.servlet.session.timeout=1m
+server.servlet.session.cookie.domain=local.com
+server.forward-headers-strategy=framework
\ No newline at end of file
diff --git a/resourceservice/target/classes/application.properties b/resourceservice/target/classes/application.properties
deleted file mode 100644
index bafddce..0000000
--- a/resourceservice/target/classes/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-server.port=8081
\ No newline at end of file
diff --git a/resourceservice/target/classes/com/tuoheng/resourceservice/HelloController.class b/resourceservice/target/classes/com/tuoheng/resourceservice/HelloController.class
deleted file mode 100644
index f5ffdcf..0000000
Binary files a/resourceservice/target/classes/com/tuoheng/resourceservice/HelloController.class and /dev/null differ
diff --git a/resourceservice/target/classes/com/tuoheng/resourceservice/ResourceServiceApplication.class b/resourceservice/target/classes/com/tuoheng/resourceservice/ResourceServiceApplication.class
deleted file mode 100644
index c558287..0000000
Binary files a/resourceservice/target/classes/com/tuoheng/resourceservice/ResourceServiceApplication.class and /dev/null differ
diff --git a/resourceservicehtml/callback.html b/resourceservicehtml/callback.html
index 422c1c0..8c6e5f9 100644
--- a/resourceservicehtml/callback.html
+++ b/resourceservicehtml/callback.html
@@ -63,9 +63,9 @@
const oidcConfig = {
clientId: 'a-client',
clientSecret: 'a-secret',
- redirectUri: 'https://a.com/callback',
- tokenEndpoint: 'https://oidc.com/oauth2/token',
- userInfoEndpoint: 'https://oidc.com/userinfo'
+ redirectUri: 'https://a.local.com/callback',
+ tokenEndpoint: 'https://oidc.local.com/oauth2/token',
+ userInfoEndpoint: 'https://oidc.local.com/userinfo'
};
// 页面加载时执行
diff --git a/resourceservicehtml/index.html b/resourceservicehtml/index.html
index c25bec4..be70237 100644
--- a/resourceservicehtml/index.html
+++ b/resourceservicehtml/index.html
@@ -74,10 +74,10 @@
const oidcConfig = {
clientId: 'a-client',
clientSecret: 'a-secret',
- redirectUri: 'https://a.com/callback',
- authorizationEndpoint: 'https://oidc.com/oauth2/authorize',
- tokenEndpoint: 'https://oidc.com/oauth2/token',
- userInfoEndpoint: 'https://oidc.com/userinfo',
+ redirectUri: 'https://a.local.com/callback',
+ authorizationEndpoint: 'https://oidc.local.com/oauth2/authorize',
+ tokenEndpoint: 'https://oidc.local.com/oauth2/token',
+ userInfoEndpoint: 'https://oidc.local.com/userinfo',
scope: 'openid read'
};
@@ -110,7 +110,7 @@
// 验证token
function validateToken(token) {
- fetch('https://oidc.com/userinfo', {
+ fetch('https://oidc.local.com/userinfo', {
headers: {
'Authorization': `Bearer ${token}`
}
@@ -266,13 +266,21 @@
// 退出登录
function logout() {
- // 通过nginx代理调用logout
// 清理本地token
localStorage.removeItem('access_token');
localStorage.removeItem('refresh_token');
localStorage.removeItem('oauth_state');
- // 跳转到Spring Security默认的退出页面(GET)
- window.location.href = 'https://oidc.com/logout';
+ // 获取id_token(如果有)
+ 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;
}
// 生成随机字符串
diff --git a/resourceservicehtml/test.html b/resourceservicehtml/test.html
index ec990c6..95988f6 100644
--- a/resourceservicehtml/test.html
+++ b/resourceservicehtml/test.html
@@ -76,11 +76,11 @@
const oidcConfig = {
clientId: 'a-client',
clientSecret: 'a-secret',
- redirectUri: 'https://a.com/callback',
- authorizationEndpoint: 'https://oidc.com/oauth2/authorize',
- tokenEndpoint: 'https://oidc.com/oauth2/token',
- userInfoEndpoint: 'https://oidc.com/userinfo',
- jwksEndpoint: 'https://oidc.com/oauth2/jwks',
+ redirectUri: 'https://a.local.com/callback',
+ authorizationEndpoint: 'https://oidc.local.com/oauth2/authorize',
+ tokenEndpoint: 'https://oidc.local.com/oauth2/token',
+ userInfoEndpoint: 'https://oidc.local.com/userinfo',
+ jwksEndpoint: 'https://oidc.local.com/oauth2/jwks',
scope: 'openid read'
};
diff --git a/ssl/certificate.crt b/ssl/certificate.crt
index 7967223..5c0093f 100644
--- a/ssl/certificate.crt
+++ b/ssl/certificate.crt
@@ -1,23 +1,26 @@
-----BEGIN CERTIFICATE-----
-MIID5jCCAs6gAwIBAgIUKxk4umxo+aYVDQ483sMNIjyV3iwwDQYJKoZIhvcNAQEL
-BQAwZDELMAkGA1UEBhMCQ04xEDAOBgNVBAgMB0JlaWppbmcxEDAOBgNVBAcMB0Jl
-aWppbmcxFDASBgNVBAoMC0RldmVsb3BtZW50MQswCQYDVQQLDAJJVDEOMAwGA1UE
-AwwFKi5jb20wHhcNMjUwNzE4MDEyNTQzWhcNMjYwNzE4MDEyNTQzWjBkMQswCQYD
-VQQGEwJDTjEQMA4GA1UECAwHQmVpamluZzEQMA4GA1UEBwwHQmVpamluZzEUMBIG
-A1UECgwLRGV2ZWxvcG1lbnQxCzAJBgNVBAsMAklUMQ4wDAYDVQQDDAUqLmNvbTCC
-ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANmJXA4sFsmdGU36bN5ThBAJ
-JZTY1vUaewmM41+IbSClPQEiO2/gK/tmDWBE82+IG7TEqbbLqUkORWFmp3bn0wgE
-nW3JamZMfPo43rr+3i9OmxPzQUMzIn5HTh9+QPxAPGUafbHJTcIc3ixCXHu150+6
-Mh4v/fkRk6Yog93VIwpADYEyocVn8SkTzGn2FsF5R+hu/t+bGAO+e5iYiwlaheCj
-yLfgzOuNo0jkxr1wmV2juN1CJ0nntqHaajMDqszQZXeBQ1K/UMgt2Ln2CRiQoatl
-UfeFBF/urK1VI8TrvayeMyMkXiAOxxvKLqbvLmvwKlr7iRtrE0SUsOdfjA4h788C
-AwEAAaOBjzCBjDAdBgNVHQ4EFgQUZqqbZSCLhcMWDsRQYZ5/vYjPVFMwHwYDVR0j
-BBgwFoAUZqqbZSCLhcMWDsRQYZ5/vYjPVFMwDwYDVR0TAQH/BAUwAwEB/zA5BgNV
-HREEMjAwggUqLmNvbYIFYS5jb22CBWIuY29tgghvaWRjLmNvbYIJbG9jYWxob3N0
-hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQC78BfqEOofJRjECXRLspkTRfz8LsN2
-EYu3bMvSmRjgbApY6Sh4Yg6LQUBRNkw17GMQ1WQTKVdygqZeQWYZvD1FgBHjoWwk
-3yfi9tfuWiD5XMWri7gpuF3xlizvSIgc0dR14J3w++1xB/jq030/wMPaYbgBqgIR
-8z2KtnvpwJvLnifBJLJ3a3g24hyKkZ+Ye0/0f8aLMZtUBB99QY6PMe9E3GDnQbQi
-4u4hPVzhWdxUQsoZ26NqgvobITx/hl7uKbr36NkGHUGgo9Vo+JEmy56HqZZjuwUK
-w8a4/EK0wjjPR9ooj4TWfYWfTr6Bde6v0awLENqDGJpFNacxgXGesoA4
+MIIEaTCCAtGgAwIBAgIRAO0/byq8GJJoBReBXr16yJUwDQYJKoZIhvcNAQELBQAw
+gZMxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTE0MDIGA1UECwwrc3Vu
+cGVuZ0BzdW5wZW5nZGVNYWNCb29rLVByby5sb2NhbCAo5a2Z6bmPKTE7MDkGA1UE
+AwwybWtjZXJ0IHN1bnBlbmdAc3VucGVuZ2RlTWFjQm9vay1Qcm8ubG9jYWwgKOWt
+mem5jykwHhcNMjUwNzE4MDc1ODM2WhcNMjcxMDE4MDc1ODM2WjBfMScwJQYDVQQK
+Ex5ta2NlcnQgZGV2ZWxvcG1lbnQgY2VydGlmaWNhdGUxNDAyBgNVBAsMK3N1bnBl
+bmdAc3VucGVuZ2RlTWFjQm9vay1Qcm8ubG9jYWwgKOWtmem5jykwggEiMA0GCSqG
+SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDgP4hfcsywJ9SbioGTODJDSG8yx1djhPp4
+yQZZ2jBqDHviF8rM+JcryTfR2Giuy9bi2ygNr18R8DDotFAp+S6LJRaS82rusiY4
++uvfptlBKF4/E967GHtPGN59LanP8d3k3aPevpdCnAKw94doPJFmEm328h28AQYE
+v+VeQEmYbYkpuOVT5eAjDKp+GFDx+zzXQN+U4domZPHZe0rCKFtPTHzk18yXuSTZ
+q2ssAcxP27QwPPusccND3oESyF46lEbVjxRvDIYXLOHxahN7RKsSXTHaGqpGsiUH
+Si2ppRRQmTzOMxkQB9ooW1Q2yatz5gzAq0dc8ft3VLowmloas6lPAgMBAAGjazBp
+MA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAfBgNVHSMEGDAW
+gBRe+VENNEgDa5/rKTEh9XpUTvjzfTAhBgNVHREEGjAYggsqLmxvY2FsLmNvbYIJ
+bG9jYWwuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQAL7KrS2TMcOKIoMzTnFLv0skL9
+evEf+9rERDPVb/J/iplaV5y1RqZjQDU9vCvsjT2igf5Xcqipu0p33pqpHwnMehDx
+M37bxonJMLOYz91PLyPZPUt76YF5NoySBshXGo6o4+PmCHpMChrspXWT36hJmfX2
+8mMwkFA6iafHy0bV9Chy6h7lzymWd7XR21xMIVT25faf8hRbx/xZ/xUf0/tWlY8Q
+wp01RWFVcP2OWgeZxttnALvgXnJPFH29S2seB2TmkuZcz6ogcuCnYH2CQCC/yL0d
+Y5YFzUqsmDaF5YUdFcwb6MTfIQOXyGLbTgBdqPgRVtxp76BTVV4nDP5IxjET6++C
+QzhJ1teqZTLdPvXIa+yqXQ/EI1NKNNRAYtmduH5kHgR1vKrQDMSHEhTaIevjN037
+2dJEdRYi47OQIocqd80StGuRNL51dAYYjB3hj0WpQpKlzc8TurB8JeVwjzn5H9pv
+A0k1s/OvborcCUguZOwVglAaVlHadmANhoel+q0=
-----END CERTIFICATE-----
diff --git a/ssl/private.key b/ssl/private.key
index 9d7389b..c743963 100644
--- a/ssl/private.key
+++ b/ssl/private.key
@@ -1,28 +1,28 @@
-----BEGIN PRIVATE KEY-----
-MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDZiVwOLBbJnRlN
-+mzeU4QQCSWU2Nb1GnsJjONfiG0gpT0BIjtv4Cv7Zg1gRPNviBu0xKm2y6lJDkVh
-Zqd259MIBJ1tyWpmTHz6ON66/t4vTpsT80FDMyJ+R04ffkD8QDxlGn2xyU3CHN4s
-Qlx7tedPujIeL/35EZOmKIPd1SMKQA2BMqHFZ/EpE8xp9hbBeUfobv7fmxgDvnuY
-mIsJWoXgo8i34MzrjaNI5Ma9cJldo7jdQidJ57ah2mozA6rM0GV3gUNSv1DILdi5
-9gkYkKGrZVH3hQRf7qytVSPE672snjMjJF4gDscbyi6m7y5r8Cpa+4kbaxNElLDn
-X4wOIe/PAgMBAAECggEABECEFR7VfzFb6kNH13yoayvSmTs30GipGQGw/BANmgLA
-04HYyZIHKg3Pmx8d5wMxD3J8or8OWwg1YPcBtPhJDrIQZbH3K3K5SqbL67nJnAEc
-VOJ/VxHrza4VH9Z27LdQtuUyqcP2iiHIUfMmHaDrmYpZKm/jtfea/Dd0hGSDH9Mh
-dXJPKRQuQ/7ugDBJnIRYoGvdTzDSWFIzWWsC+nfeokK5paNMjkcJMFJGDVsP3lq0
-V4oH3eO2po5c/Mq4wXu+fTZthNIoQx2bkoUdG2x2aaPjNmMh6Lb6NbmBy7CBQH53
-sHVX6MRWireJMVRSJvhVrOilqDXYz90k4J8F8l0MSQKBgQDz7zGsNEQagSGF9k0p
-xpHUSU2hit6rJ/QgToKixm9ED0984zcaXDHpCcYH6oLI+YTzfGx960zgPpmrJKvc
-41FqZogGeP1GF/ZRylAz4E8OSib5LsWyzPzbczGAi+WKj0cfJHXZsXMBwL+AfLHk
-q0j/71S8qW4D5vBSyk9BHgugowKBgQDkS+eoWYPlAm+tjBVH8rgPWK/oNiI9ET1V
-K6MeH5O0gIVGwxZftZwvFXXNfJLXGkNnM/6/kXL16L7wYu9gW06fvw3gEsc4mQ+e
-5FHF7JnwfI/pnbZENwoDco1AQsGqOMZawR+OM84R4oUz4zEzgwD3aWOJudTjDumQ
-uI/hJKWq5QKBgQC3yRKyvNpG4d3BEbZHcF11BRmhSYDEkaCkKqLQQxOXwrVP0d01
-VhsgigWS90Q8aYqa7LbNFFhiZ6fdww5dqUMxGDkKL2QbyHgEXZqZyzmk+YdtnKjF
-Mx6btKmqQTzbbWHXe9/y+Xg97Nwb0Vcygz7H3akJT9ocxIVyywx1ck6uYwKBgQCJ
-aocOdpNFjanbNK66mAbideesRqllSLM6SQHuZ+NoitOuPE+DXLWeQbSe85UPlOdt
-f4afmNUx397Ooz6jKVKyJTYc4jC4iKk2Ywg1sq0WbGPTovLLLLYCTTlorMYVyAbd
-KdHsrpIjgc3b5az/7KLwSad4hzr1UUyVqAIy6vQtYQKBgQCJSsEM5hRhbQjQFBY6
-MjeAE9Y2jfZu3LYh2w/3SgO5FwvkBzc0s2Rg7d301sYryJoF720O5dwhI3p0qMru
-WoGWR7Pn3MmdDKubH8fnYhk8QDC0HLR5+7yMPNeHNfRk0VCjUkWJvAjYo/p/r3TE
-2fBQ0cw3/nOQ3QRZpfP5XRxdWQ==
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDgP4hfcsywJ9Sb
+ioGTODJDSG8yx1djhPp4yQZZ2jBqDHviF8rM+JcryTfR2Giuy9bi2ygNr18R8DDo
+tFAp+S6LJRaS82rusiY4+uvfptlBKF4/E967GHtPGN59LanP8d3k3aPevpdCnAKw
+94doPJFmEm328h28AQYEv+VeQEmYbYkpuOVT5eAjDKp+GFDx+zzXQN+U4domZPHZ
+e0rCKFtPTHzk18yXuSTZq2ssAcxP27QwPPusccND3oESyF46lEbVjxRvDIYXLOHx
+ahN7RKsSXTHaGqpGsiUHSi2ppRRQmTzOMxkQB9ooW1Q2yatz5gzAq0dc8ft3VLow
+mloas6lPAgMBAAECggEBAIfhF2I2rp7C08oX6CHruFEar/6F2Yb9CcRsksOZOSLZ
+Q6uhHQqMSxWGDKPDzNK1wxSdFS0Nqb612vz2XWjBi5lWtNIAWzgdjJmUOZ7Ae/5G
+Vq1D/f9Ce11XRWF2bOIKvZizUFtlA0SiQeM3ab4YjUXbPvSWirvjpuDz4ij1LWMF
+QeT+GM763+f+otBN3fMXxj0B2OXu2s8qxNj+iY98hGRqPs9xYYSoYDflptrJBCTw
+wLw9U8v/Lv4uglEMbNc2JvwvmqUQ30eDnrrMzlPZdlSHQyBubO0AbYtIhFQKZFvD
+sK+YCjDTu7n+WiTEolSFS563eQo5fbDFXL1G9dc/YLECgYEA7Bv+2WZ2AmvPZAGQ
+REWYq0U41Ue/JqgIXaDRR+cCHPV4aOd+GHOxVmK3Py0xB/D7Yo76+RJeJB4Vo/zp
+YsT0xSoSzgXPJIAMEjNv0NKiBqj/ayvxd/tKxXGlduyVSCfgIV8mNSCv88nf9MA1
+Mh7R4X7C/zKQkZ2LwL0ZZgr5hTkCgYEA8yO8Q/eSspoXRH6uIl3KR24FMQOw+OkC
+lAeLj3ZRZ3e0RpJNw85AxpQoWh/PVh12p7YKjM7UQHvUjfM6kcoeTzXYMqeK58/g
+xqzt67qXI/AVQsJx2oCXnA4nwyhQsU7d/deYpAFN06Zkmks8EKHq/miWEf2PfNqB
+uTuTPdWv6scCgYAkCaXdYuEyP2hZOE/fy8ugoKErFJddfBpCyDAJTH4rE2B8ipDZ
+hJcVu12C3A/2yVZlVbOC3sXVt23QKOMqeyttCJ30KjjStmShRo6TjgLDB3pszjk9
++fIQrub1fujOKZ/xGAfJ5iJVEIQJZGj6LHAWffWfZAVi5GwXUAWXaKdrKQKBgFta
+ofIno0bX/sYNkv/2nXoZLHouGOBtLDrSWu2cVxm5MFMTxYQ8iroSENdL/GsuxtZc
+37noPHe+Dy8GpIsClkDMyl699MMEqD/92acohIFMQ7DBvmWKy2wnJWl+TFNSfrZR
+u1hj5QoRCtuuSPM240STp087Jh6TOwqOB9TD5UUhAoGARrBidxer/nmMisoVMlby
+9oHNsZt9BrrzHVW/g5AeyRobaKZObYFsZlleLu6k6amfjU8c8fU4vCePSdOuhDWg
+uJgClKZawtDV2LS2oOOcTRIhLdwDzT8XZcrrhCLskCcdSJSAVIC2JmhsLtjCuPvi
+5t/fBrOuubfljuu9ULZjMc0=
-----END PRIVATE KEY-----
diff --git a/token.txt b/token.txt
new file mode 100644
index 0000000..de908b9
--- /dev/null
+++ b/token.txt
@@ -0,0 +1 @@
+eyJraWQiOiIyMjYyZjU4MS0wNzRiLTRmZDctOGM2OC1mNGY3ODU1OGEyZWYiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyIiwiYXVkIjoiY2xpZW50IiwibmJmIjoxNzUyNzM5MjUyLCJzY29wZSI6WyJvcGVuaWQiXSwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo5MDAwIiwiZXhwIjoxNzUyNzM5ODUyLCJpYXQiOjE3NTI3MzkyNTIsImp0aSI6ImMwNTZkNjAxLTBlZWYtNDBjYy04ODdiLTdjNjg1NDZlMzY2MSJ9.YifGLvHLe2QMInBgdJmW_us3m-Q41XX8RyaUfCv7J9gKT22wFAFtXUeXhF1bHXn9XUoPd3I33uBFclsYSs5XPqWlBhpiE-drRsZT-ZzbZ-x0YEELTkBEabYj-wivzHZZ9sVOxdV3yV7dN8-GpGJGyDglbX8tcWziZ4hCmSWIVCipb9cezBZdQYJiXe5SKb4qSmsQzF8F1CbBHuckfwXkggwVitW2YVKaeFDkV99RZRmcNzcvwVc6VU59cPr7VeirRTvjUyZKzpy9D9qtexKkSp0Fp0S4-J1NRHYh5mfaOBGSIQ52LE7c5Vz0lg23E0qCG3QvFkGF2i01fAIbEIpGLA