ory kratos csrf cookie 未在 nginx ssl 后面发送
ory kratos csrf cookie not being sent behind nginx ssl
我在 docker 中将 Go 与 ory kratos 一起使用,在本地主机上我的机器上一切正常。
Auth 有效,所有 cookie 都已发送和设置,我可以从 SPA 调用我的后端并进行身份验证。
问题是在 nginx
和 ssl
后面的实时服务器上,显然没有从我的 js 客户端发送一个 cookie(只发送 ory_kratos_session
而不是 xxx_csrf_token
cookie) 并且它在下面的函数中失败并出现 cookie 丢失错误。
使用官方go sdk:kratos-client-go
Go AuthRequired中间件
func ExtractKratosCookiesFromRequest(r *http.Request) (csrf, session *http.Cookie, cookieHeader string) {
cookieHeader = r.Header.Get("Cookie")
cookies := r.Cookies()
for _, c := range cookies {
if c != nil {
if ok := strings.HasSuffix(c.Name, string("csrf_token")); ok {
csrf = c
}
}
}
sessionCookie, _ := r.Cookie("ory_kratos_session")
if sessionCookie != nil {
session = sessionCookie
}
return
}
func AuthRequired(w http.ResponseWriter, r *http.Request) error {
csrfCookie, sessionCookie, cookieHeader := ExtractKratosCookiesFromRequest(r)
if (csrfCookie == nil || sessionCookie == nil) || (csrfCookie.Value == "" || sessionCookie.Value == "") {
return errors.New("Cookie missing")
}
req := kratos.PublicApi.Whoami(r.Context()).Cookie(cookieHeader)
kratosSession, _, err := req.Execute()
if err != nil {
return errors.New("Whoami error")
}
return nil
}
我的 js http 客户端有选项:credentials: 'include'
。
在 devtools 面板中,我在 register/login.
之后只看到 1 个 cookie (ory_kratos_session
)
所以失败的是请求只发送 ory_kratos_session
而不是 xxx_csrf_token
cookie(它在 kratos --dev
模式下在 localhost
上工作,并且 cookie 是可见的在开发工具面板中)
请求信息
一般:
Request URL: https://example.com/api/v1/users/1/donations
Request Method: GET
Status Code: 401 Unauthorized
Remote Address: 217.163.23.144:443
Referrer Policy: strict-origin-when-cross-origin
请求Headers:
accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
content-type: application/json; charset=UTF-8
Cookie: ory_kratos_session=MTYyMjA0NjEyMHxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJRFo0Y2tKUFNFUmxZWFpsV21kaFdVbFZjMFU0VVZwcFkxbDNPRFpoY1ZOeXyInl242jY9c2FDQmykJrjLTNLg-sPFv2y04Qfl3uDfpA==
Host: example.com
Referer: https://example.com/dashboard/donations
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
回复Headers:
Connection: keep-alive
Content-Length: 175
Content-Type: application/json
Date: Wed, 26 May 2021 17:12:27 GMT
Server: nginx/1.18.0 (Ubuntu)
Vary: Origin
docker-compose.yml
version: "3.8"
services:
# --------------------------------------------------------------------------------
api-server:
build:
context: .
dockerfile: ./dockerfiles/app.dockerfile
container_name: api-server
restart: always
volumes:
- ./:/app
ports:
- 3001:3001
networks:
- intranet
depends_on:
- postgresd
# --------------------------------------------------------------------------------
postgresd:
image: postgres:13.3-alpine
container_name: postgresd
restart: always
environment:
- POSTGRES_DB=test
- POSTGRES_USER=test
- POSTGRES_PASSWORD=test
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- 5432:5432
networks:
- intranet
# --------------------------------------------------------------------------------
kratos-migrate:
image: oryd/kratos:v0.6.2-alpha.1
container_name: kratos-migrate
restart: on-failure
environment:
- DSN=postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
volumes:
- type: bind
source: ./kratos/config
target: /etc/config/kratos
command:
[
"migrate",
"sql",
"--read-from-env",
"--config",
"/etc/config/kratos/kratos.yml",
"--yes",
]
networks:
- intranet
depends_on:
- postgresd
# --------------------------------------------------------------------------------
kratos:
image: oryd/kratos:v0.6.2-alpha.1
container_name: kratos
restart: unless-stopped
environment:
- DSN=postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
command: ["serve", "--config", "/etc/config/kratos/kratos.yml"]
volumes:
- type: bind
source: ./kratos/config
target: /etc/config/kratos
ports:
- 4433:4433
- 4434:4434
networks:
- intranet
depends_on:
- postgress
- kratos-migrate
# --------------------------------------------------------------------------------
volumes:
postgres-data:
networks:
intranet:
driver: bridge
kratos.yml
version: v0.6.2-alpha.1
dsn: postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
serve:
public:
base_url: https://example.com/kratos/
cors:
enabled: true
debug: true
allow_credentials: true
options_passthrough: true
allowed_origins:
- https://example.com
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Cookie
- Origin
- X-Session-Token
exposed_headers:
- Content-Type
- Set-Cookie
admin:
base_url: https://example.com/kratos/
selfservice:
default_browser_return_url: https://example.com
whitelisted_return_urls:
- https://example.com
- https://example.com/dashboard
- https://example.com/auth/login
methods:
password:
enabled: true
oidc:
enabled: false
link:
enabled: true
profile:
enabled: true
flows:
error:
ui_url: https://example.com/error
settings:
ui_url: https://example.com/dashboard/profile
privileged_session_max_age: 15m
recovery:
enabled: true
ui_url: https://example.com/auth/recovery
after:
default_browser_return_url: https://example.com/auth/login
verification:
enabled: true
ui_url: https://example.com/auth/verification
after:
default_browser_return_url: https://example.com
logout:
after:
default_browser_return_url: https://example.com
login:
ui_url: https://example.com/auth/login
lifespan: 10m
registration:
lifespan: 10m
ui_url: https://example.com/auth/registration
after:
password:
hooks:
- hook: session
default_browser_return_url: https://example.com/auth/login
default_browser_return_url: https://example.com/auth/login
oidc:
hooks:
- hook: session
secrets:
cookie:
- fdwfhgwjfgwf9286f24tf29ft
session:
lifespan: 24h
cookie:
domain: example.com # i tried also with http:// and https://
same_site: Lax
hashers:
argon2:
parallelism: 1
memory: 128MB
iterations: 1
salt_length: 16
key_length: 16
identity:
default_schema_url: file:///etc/config/kratos/identity.schema.json
courier:
smtp:
connection_uri: smtp://user:pwd@smtp.mailtrap.io:2525
from_name: test
from_address: office@test.com
watch-courier: true
log:
level: debug
format: text
leak_sensitive_values: true
我的 Go rest api 有这些 cors 选项:
ALLOWED_ORIGINS=https://example.com
ALLOWED_METHODS=GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
ALLOWED_HEADERS=Content-Type,Authorization,Cookie,Origin,X-Session-Token,X-CSRF-Token,Vary
EXPOSED_HEADERS=Content-Type,Authorization,Content-Length,Cache-Control,Content-Language,Content-Range,Set-Cookie,Pragma,Expires,Last-Modified,X-Session-Token,X-CSRF-Token
MAX_AGE=86400
ALLOW_CREDENTIALS=true
nginx 默认值
upstream go-api {
server 127.0.0.1:3001;
}
upstream kratos {
server 127.0.0.1:4433;
}
upstream kratos-admin {
server 127.0.0.1:4434;
}
server {
server_name example.com www.example.com;
location / {
root /var/www/website;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://go-api;
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
location /kratos/ {
proxy_pass http://kratos/;
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
location /kratos-admin/ {
proxy_pass http://kratos-admin/;
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
certs go here...
}
我不明白为什么它不能在实时服务器上运行,它必须与 ssl 相关
这是我正在使用的 http 客户端(ky.js 但它与 fetch 一样并不重要)
const options = {
prefixUrl: 'https://example.com/api/v1',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Accept: 'application/json',
},
timeout: 5000,
mode: 'cors',
credentials: 'include',
};
export const apiClient = ky.create(options);
我只是调用受 AuthRequired 中间件保护的后端保护路由,没什么特别的:
function createTodo(data) {
return apiClient.post(`todos`, { json: data }).json();
}
ory/kratos-client(js sdk)是这样配置的:
const conf = new Configuration({
basePath: 'https://example.com/kratos',
// these are axios options (kratos js sdk uses axios under the hood)
baseOptions: {
withCredentials: true,
timeout: 5000,
},
});
export const kratos = new PublicApi(conf);
奇怪的是,在 firefox 中,我在开发工具面板中看到了 2 个 cookie,但在 chrome 中却没有。
这是一个 csrf:
aHR0cHM6Ly9hbmltb25kLnh5ei9rcmF0b3Mv_csrf_token:"Kx+PXWeoxsDNxQFGZBgvlTJScg9VIYEB+6cTrC0zsA0="
Created:"Thu, 27 May 2021 10:21:45 GMT"
Domain:".example.com"
Expires / Max-Age:"Fri, 27 May 2022 10:22:32 GMT"
HostOnly:false
HttpOnly:true
Last Accessed:"Thu, 27 May 2021 10:22:32 GMT"
Path:"/kratos/"
SameSite:"None"
Secure:true
Size: 91
这是 session cookie:
ory_kratos_session:"MTYyMjExMDk1MnxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJRFZYV25Jd05HaEpTR28xVHpaT1kzTXlSSGxxVHpaaWQyUTVRamhIY2paM3zb24EtkN6Bmv_lRZa7YSRBOYvUGYSUBmZ7RIkDsm4Oyw=="
Created:"Thu, 27 May 2021 10:22:32 GMT"
Domain:".example.com"
Expires / Max-Age:"Thu, 08 Jul 2021 01:22:32 GMT"
HostOnly:false
HttpOnly:true
Last Accessed:"Thu, 27 May 2021 10:22:32 GMT"
Path:"/"
SameSite:"Lax"
Secure:true
Size:234
我认为这与容器中的时区有关,我还在所有容器中安装了这个卷:-v /etc/localtime:/etc/localtime:ro
PS
问题是,每当我 docker-compose restart kratos
时,事情就坏了,显然使用了旧的 csrf_token。
这应该如何使用,我不能只是告诉我的用户嘿去你的浏览器并删除所有缓存和 cookie。
当我修剪它工作的一切时,但是一旦我重新启动 nginx 之后它就没有工作了(docker-compose 重启后也是如此)...很奇怪
这家伙在这里遇到了同样的问题:csrf problem after restart
我认为您的 kratos 配置不正确。
属性 serve.public.base_url
应该是 url 请求源自例如https://example.com/kratos/
而不是您的本地主机 http://127.0.0.1:4433
.
也只是一个建议,您的管理端点永远不应暴露给 public,您的后端服务应在内部网络上请求管理 url(例如 [=22= 内) ] 或在本地主机上)。您的 serve.admin.base_url
应该是 http://127.0.0.1:4434
并从 nginx 中删除。
nginx 配置对我来说似乎是正确的。我相信你只需要这个就可以工作:
location /kratos/ {
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:4433;
}
我确实设法解决了这个问题,它在生产中没有 --dev
标志的情况下工作正常,即使在重新启动服务并更改所有内容后也不会搞砸。
也许甚至是我的反应形式,我在 csrf 令牌输入中使用 defaultValue
现在每当 window.location url 改变或每当 csrf_token 改变时,它应该使用最新的值 csrf_token
const [csrf, setCsrf] = React.useState('');
useEffect(() => {
if (flowResponse !== null) {
const csrfVal = flowResponse?.ui?.nodes?.find?.(n => n.attributes.name === 'csrf_token')?.attributes.value;
setCsrf(csrfVal);
}
}, [flowResponse, csrf]);
<input type='hidden' name='csrf_token' value={csrf} readOnly required />
最糟糕的是,它也可能是一个尾部斜线或一些非常小的东西,我不确定到底是什么原因造成的。
这里是所有 post 所有对我有用的配置:
可能是我之前尝试过将这个 kratos url 设置为 http://127.0.0.1:4433 或 http://kratos:4433 但它没有用(即使我在这 3 个之间切换哈哈)
初始化 kratos 客户端
conf := kratos.NewConfiguration()
conf.Servers[0].URL = "https://example.com/kratos/"
kratosClient := kratos.NewAPIClient(conf)
kratos.yml
version: v0.6.2-alpha.1
dsn: postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
serve:
public:
base_url: https://example.com/kratos/
cors:
enabled: true
debug: true
allow_credentials: true
options_passthrough: true
max_age: 0
allowed_origins:
- https://example.com
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Cookie
- Origin
- X-Session-Token
exposed_headers:
- Content-Type
- Set-Cookie
admin:
base_url: http://127.0.0.1:4434/
selfservice:
default_browser_return_url: https://example.com
whitelisted_return_urls:
- https://example.com
- https://example.com/dashboard
- https://example.com/auth/login
methods:
password:
enabled: true
oidc:
enabled: false
link:
enabled: true
profile:
enabled: true
flows:
error:
ui_url: https://example.com/error
settings:
ui_url: https://example.com/dashboard/profile
privileged_session_max_age: 15m
recovery:
enabled: true
ui_url: https://example.com/auth/recovery
after:
default_browser_return_url: https://example.com/auth/login
verification:
enabled: true
ui_url: https://example.com/auth/verification
after:
default_browser_return_url: https://example.com
logout:
after:
default_browser_return_url: https://example.com
login:
ui_url: https://example.com/auth/login
lifespan: 10m
registration:
lifespan: 10m
ui_url: https://example.com/auth/registration
after:
password:
hooks:
- hook: session
default_browser_return_url: https://example.com/auth/login
default_browser_return_url: https://example.com/auth/login
oidc:
hooks:
- hook: session
secrets:
cookie:
- veRy_S3cRet_tHinG
session:
lifespan: 24h
cookie:
domain: example.com
same_site: Lax
path: /
// <- i didn't have path before, not sure if it changes anything but it works (before csrf cookie had path /kratos and now when it works it has path /, same as session_cookie)
hashers:
argon2:
parallelism: 1
memory: 128MB
iterations: 1
salt_length: 16
key_length: 16
identity:
default_schema_url: file:///etc/config/kratos/identity.schema.json
courier:
smtp:
connection_uri: smtp://user:pwd@smtp.mailtrap.io:2525
from_name: example
from_address: office@example.cpm
watch-courier: true
log:
level: debug
format: text
leak_sensitive_values: true
nginx.conf
server {
server_name example.com www.example.com;
location / {
root /var/www/public;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:3001; // backend api url
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
location /kratos/ {
proxy_pass http://127.0.0.1:4433/; // kratos public url
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
certs...
}
server {
if ($host = www.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80 default_server;
listen [::]:80 default_server;
server_name example.com www.example.com;
return 404; # managed by Certbot
}
我在 docker 中将 Go 与 ory kratos 一起使用,在本地主机上我的机器上一切正常。 Auth 有效,所有 cookie 都已发送和设置,我可以从 SPA 调用我的后端并进行身份验证。
问题是在 nginx
和 ssl
后面的实时服务器上,显然没有从我的 js 客户端发送一个 cookie(只发送 ory_kratos_session
而不是 xxx_csrf_token
cookie) 并且它在下面的函数中失败并出现 cookie 丢失错误。
使用官方go sdk:kratos-client-go
Go AuthRequired中间件
func ExtractKratosCookiesFromRequest(r *http.Request) (csrf, session *http.Cookie, cookieHeader string) {
cookieHeader = r.Header.Get("Cookie")
cookies := r.Cookies()
for _, c := range cookies {
if c != nil {
if ok := strings.HasSuffix(c.Name, string("csrf_token")); ok {
csrf = c
}
}
}
sessionCookie, _ := r.Cookie("ory_kratos_session")
if sessionCookie != nil {
session = sessionCookie
}
return
}
func AuthRequired(w http.ResponseWriter, r *http.Request) error {
csrfCookie, sessionCookie, cookieHeader := ExtractKratosCookiesFromRequest(r)
if (csrfCookie == nil || sessionCookie == nil) || (csrfCookie.Value == "" || sessionCookie.Value == "") {
return errors.New("Cookie missing")
}
req := kratos.PublicApi.Whoami(r.Context()).Cookie(cookieHeader)
kratosSession, _, err := req.Execute()
if err != nil {
return errors.New("Whoami error")
}
return nil
}
我的 js http 客户端有选项:credentials: 'include'
。
在 devtools 面板中,我在 register/login.
之后只看到 1 个 cookie (ory_kratos_session
)
所以失败的是请求只发送 ory_kratos_session
而不是 xxx_csrf_token
cookie(它在 kratos --dev
模式下在 localhost
上工作,并且 cookie 是可见的在开发工具面板中)
请求信息
一般:
Request URL: https://example.com/api/v1/users/1/donations
Request Method: GET
Status Code: 401 Unauthorized
Remote Address: 217.163.23.144:443
Referrer Policy: strict-origin-when-cross-origin
请求Headers:
accept: application/json
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
content-type: application/json; charset=UTF-8
Cookie: ory_kratos_session=MTYyMjA0NjEyMHxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJRFo0Y2tKUFNFUmxZWFpsV21kaFdVbFZjMFU0VVZwcFkxbDNPRFpoY1ZOeXyInl242jY9c2FDQmykJrjLTNLg-sPFv2y04Qfl3uDfpA==
Host: example.com
Referer: https://example.com/dashboard/donations
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36
回复Headers:
Connection: keep-alive
Content-Length: 175
Content-Type: application/json
Date: Wed, 26 May 2021 17:12:27 GMT
Server: nginx/1.18.0 (Ubuntu)
Vary: Origin
docker-compose.yml
version: "3.8"
services:
# --------------------------------------------------------------------------------
api-server:
build:
context: .
dockerfile: ./dockerfiles/app.dockerfile
container_name: api-server
restart: always
volumes:
- ./:/app
ports:
- 3001:3001
networks:
- intranet
depends_on:
- postgresd
# --------------------------------------------------------------------------------
postgresd:
image: postgres:13.3-alpine
container_name: postgresd
restart: always
environment:
- POSTGRES_DB=test
- POSTGRES_USER=test
- POSTGRES_PASSWORD=test
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- 5432:5432
networks:
- intranet
# --------------------------------------------------------------------------------
kratos-migrate:
image: oryd/kratos:v0.6.2-alpha.1
container_name: kratos-migrate
restart: on-failure
environment:
- DSN=postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
volumes:
- type: bind
source: ./kratos/config
target: /etc/config/kratos
command:
[
"migrate",
"sql",
"--read-from-env",
"--config",
"/etc/config/kratos/kratos.yml",
"--yes",
]
networks:
- intranet
depends_on:
- postgresd
# --------------------------------------------------------------------------------
kratos:
image: oryd/kratos:v0.6.2-alpha.1
container_name: kratos
restart: unless-stopped
environment:
- DSN=postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
command: ["serve", "--config", "/etc/config/kratos/kratos.yml"]
volumes:
- type: bind
source: ./kratos/config
target: /etc/config/kratos
ports:
- 4433:4433
- 4434:4434
networks:
- intranet
depends_on:
- postgress
- kratos-migrate
# --------------------------------------------------------------------------------
volumes:
postgres-data:
networks:
intranet:
driver: bridge
kratos.yml
version: v0.6.2-alpha.1
dsn: postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
serve:
public:
base_url: https://example.com/kratos/
cors:
enabled: true
debug: true
allow_credentials: true
options_passthrough: true
allowed_origins:
- https://example.com
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Cookie
- Origin
- X-Session-Token
exposed_headers:
- Content-Type
- Set-Cookie
admin:
base_url: https://example.com/kratos/
selfservice:
default_browser_return_url: https://example.com
whitelisted_return_urls:
- https://example.com
- https://example.com/dashboard
- https://example.com/auth/login
methods:
password:
enabled: true
oidc:
enabled: false
link:
enabled: true
profile:
enabled: true
flows:
error:
ui_url: https://example.com/error
settings:
ui_url: https://example.com/dashboard/profile
privileged_session_max_age: 15m
recovery:
enabled: true
ui_url: https://example.com/auth/recovery
after:
default_browser_return_url: https://example.com/auth/login
verification:
enabled: true
ui_url: https://example.com/auth/verification
after:
default_browser_return_url: https://example.com
logout:
after:
default_browser_return_url: https://example.com
login:
ui_url: https://example.com/auth/login
lifespan: 10m
registration:
lifespan: 10m
ui_url: https://example.com/auth/registration
after:
password:
hooks:
- hook: session
default_browser_return_url: https://example.com/auth/login
default_browser_return_url: https://example.com/auth/login
oidc:
hooks:
- hook: session
secrets:
cookie:
- fdwfhgwjfgwf9286f24tf29ft
session:
lifespan: 24h
cookie:
domain: example.com # i tried also with http:// and https://
same_site: Lax
hashers:
argon2:
parallelism: 1
memory: 128MB
iterations: 1
salt_length: 16
key_length: 16
identity:
default_schema_url: file:///etc/config/kratos/identity.schema.json
courier:
smtp:
connection_uri: smtp://user:pwd@smtp.mailtrap.io:2525
from_name: test
from_address: office@test.com
watch-courier: true
log:
level: debug
format: text
leak_sensitive_values: true
我的 Go rest api 有这些 cors 选项:
ALLOWED_ORIGINS=https://example.com
ALLOWED_METHODS=GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS
ALLOWED_HEADERS=Content-Type,Authorization,Cookie,Origin,X-Session-Token,X-CSRF-Token,Vary
EXPOSED_HEADERS=Content-Type,Authorization,Content-Length,Cache-Control,Content-Language,Content-Range,Set-Cookie,Pragma,Expires,Last-Modified,X-Session-Token,X-CSRF-Token
MAX_AGE=86400
ALLOW_CREDENTIALS=true
nginx 默认值
upstream go-api {
server 127.0.0.1:3001;
}
upstream kratos {
server 127.0.0.1:4433;
}
upstream kratos-admin {
server 127.0.0.1:4434;
}
server {
server_name example.com www.example.com;
location / {
root /var/www/website;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://go-api;
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
location /kratos/ {
proxy_pass http://kratos/;
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
location /kratos-admin/ {
proxy_pass http://kratos-admin/;
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
certs go here...
}
我不明白为什么它不能在实时服务器上运行,它必须与 ssl 相关
这是我正在使用的 http 客户端(ky.js 但它与 fetch 一样并不重要)
const options = {
prefixUrl: 'https://example.com/api/v1',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
Accept: 'application/json',
},
timeout: 5000,
mode: 'cors',
credentials: 'include',
};
export const apiClient = ky.create(options);
我只是调用受 AuthRequired 中间件保护的后端保护路由,没什么特别的:
function createTodo(data) {
return apiClient.post(`todos`, { json: data }).json();
}
ory/kratos-client(js sdk)是这样配置的:
const conf = new Configuration({
basePath: 'https://example.com/kratos',
// these are axios options (kratos js sdk uses axios under the hood)
baseOptions: {
withCredentials: true,
timeout: 5000,
},
});
export const kratos = new PublicApi(conf);
奇怪的是,在 firefox 中,我在开发工具面板中看到了 2 个 cookie,但在 chrome 中却没有。
这是一个 csrf:
aHR0cHM6Ly9hbmltb25kLnh5ei9rcmF0b3Mv_csrf_token:"Kx+PXWeoxsDNxQFGZBgvlTJScg9VIYEB+6cTrC0zsA0="
Created:"Thu, 27 May 2021 10:21:45 GMT"
Domain:".example.com"
Expires / Max-Age:"Fri, 27 May 2022 10:22:32 GMT"
HostOnly:false
HttpOnly:true
Last Accessed:"Thu, 27 May 2021 10:22:32 GMT"
Path:"/kratos/"
SameSite:"None"
Secure:true
Size: 91
这是 session cookie:
ory_kratos_session:"MTYyMjExMDk1MnxEdi1CQkFFQ180SUFBUkFCRUFBQVJfLUNBQUVHYzNSeWFXNW5EQThBRFhObGMzTnBiMjVmZEc5clpXNEdjM1J5YVc1bkRDSUFJRFZYV25Jd05HaEpTR28xVHpaT1kzTXlSSGxxVHpaaWQyUTVRamhIY2paM3zb24EtkN6Bmv_lRZa7YSRBOYvUGYSUBmZ7RIkDsm4Oyw=="
Created:"Thu, 27 May 2021 10:22:32 GMT"
Domain:".example.com"
Expires / Max-Age:"Thu, 08 Jul 2021 01:22:32 GMT"
HostOnly:false
HttpOnly:true
Last Accessed:"Thu, 27 May 2021 10:22:32 GMT"
Path:"/"
SameSite:"Lax"
Secure:true
Size:234
我认为这与容器中的时区有关,我还在所有容器中安装了这个卷:-v /etc/localtime:/etc/localtime:ro
PS
问题是,每当我 docker-compose restart kratos
时,事情就坏了,显然使用了旧的 csrf_token。
这应该如何使用,我不能只是告诉我的用户嘿去你的浏览器并删除所有缓存和 cookie。
当我修剪它工作的一切时,但是一旦我重新启动 nginx 之后它就没有工作了(docker-compose 重启后也是如此)...很奇怪
这家伙在这里遇到了同样的问题:csrf problem after restart
我认为您的 kratos 配置不正确。
属性 serve.public.base_url
应该是 url 请求源自例如https://example.com/kratos/
而不是您的本地主机 http://127.0.0.1:4433
.
也只是一个建议,您的管理端点永远不应暴露给 public,您的后端服务应在内部网络上请求管理 url(例如 [=22= 内) ] 或在本地主机上)。您的 serve.admin.base_url
应该是 http://127.0.0.1:4434
并从 nginx 中删除。
nginx 配置对我来说似乎是正确的。我相信你只需要这个就可以工作:
location /kratos/ {
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:4433;
}
我确实设法解决了这个问题,它在生产中没有 --dev
标志的情况下工作正常,即使在重新启动服务并更改所有内容后也不会搞砸。
也许甚至是我的反应形式,我在 csrf 令牌输入中使用 defaultValue
现在每当 window.location url 改变或每当 csrf_token 改变时,它应该使用最新的值 csrf_token
const [csrf, setCsrf] = React.useState('');
useEffect(() => {
if (flowResponse !== null) {
const csrfVal = flowResponse?.ui?.nodes?.find?.(n => n.attributes.name === 'csrf_token')?.attributes.value;
setCsrf(csrfVal);
}
}, [flowResponse, csrf]);
<input type='hidden' name='csrf_token' value={csrf} readOnly required />
最糟糕的是,它也可能是一个尾部斜线或一些非常小的东西,我不确定到底是什么原因造成的。
这里是所有 post 所有对我有用的配置:
可能是我之前尝试过将这个 kratos url 设置为 http://127.0.0.1:4433 或 http://kratos:4433 但它没有用(即使我在这 3 个之间切换哈哈)
初始化 kratos 客户端
conf := kratos.NewConfiguration()
conf.Servers[0].URL = "https://example.com/kratos/"
kratosClient := kratos.NewAPIClient(conf)
kratos.yml
version: v0.6.2-alpha.1
dsn: postgres://test:test@postgresd:5432/test?sslmode=disable&max_conns=20&max_idle_conns=4
serve:
public:
base_url: https://example.com/kratos/
cors:
enabled: true
debug: true
allow_credentials: true
options_passthrough: true
max_age: 0
allowed_origins:
- https://example.com
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Cookie
- Origin
- X-Session-Token
exposed_headers:
- Content-Type
- Set-Cookie
admin:
base_url: http://127.0.0.1:4434/
selfservice:
default_browser_return_url: https://example.com
whitelisted_return_urls:
- https://example.com
- https://example.com/dashboard
- https://example.com/auth/login
methods:
password:
enabled: true
oidc:
enabled: false
link:
enabled: true
profile:
enabled: true
flows:
error:
ui_url: https://example.com/error
settings:
ui_url: https://example.com/dashboard/profile
privileged_session_max_age: 15m
recovery:
enabled: true
ui_url: https://example.com/auth/recovery
after:
default_browser_return_url: https://example.com/auth/login
verification:
enabled: true
ui_url: https://example.com/auth/verification
after:
default_browser_return_url: https://example.com
logout:
after:
default_browser_return_url: https://example.com
login:
ui_url: https://example.com/auth/login
lifespan: 10m
registration:
lifespan: 10m
ui_url: https://example.com/auth/registration
after:
password:
hooks:
- hook: session
default_browser_return_url: https://example.com/auth/login
default_browser_return_url: https://example.com/auth/login
oidc:
hooks:
- hook: session
secrets:
cookie:
- veRy_S3cRet_tHinG
session:
lifespan: 24h
cookie:
domain: example.com
same_site: Lax
path: /
// <- i didn't have path before, not sure if it changes anything but it works (before csrf cookie had path /kratos and now when it works it has path /, same as session_cookie)
hashers:
argon2:
parallelism: 1
memory: 128MB
iterations: 1
salt_length: 16
key_length: 16
identity:
default_schema_url: file:///etc/config/kratos/identity.schema.json
courier:
smtp:
connection_uri: smtp://user:pwd@smtp.mailtrap.io:2525
from_name: example
from_address: office@example.cpm
watch-courier: true
log:
level: debug
format: text
leak_sensitive_values: true
nginx.conf
server {
server_name example.com www.example.com;
location / {
root /var/www/public;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://127.0.0.1:3001; // backend api url
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
location /kratos/ {
proxy_pass http://127.0.0.1:4433/; // kratos public url
proxy_http_version 1.1;
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-Port $server_port;
proxy_set_header x-forwarded-proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_cache_bypass $http_upgrade;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
certs...
}
server {
if ($host = www.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80 default_server;
listen [::]:80 default_server;
server_name example.com www.example.com;
return 404; # managed by Certbot
}