fix(server): 会话 Cookie 不再强制 Secure,修复 HTTP 远程访问登录后无法保持登录态 (#1669)
Co-authored-by: Noah <17682131+ransxd@users.noreply.github.com>
This commit is contained in:
1 parent
6cef8afe22
commit
c351c39771
5 files changed
+84
-7
No files matched your search
@@ -170,6 +170,11 @@ pub enum Commands {
|
||||
#[arg(long, default_value = "false")]
|
||||
auth: bool,
|
||||
|
||||
/// 为会话 Cookie 附加 Secure 属性。仅当通过 HTTPS 反向代理访问 Web UI 时开启;
|
||||
/// 直接通过 HTTP 远程访问时开启会导致浏览器丢弃登录态
|
||||
#[arg(long, default_value = "false")]
|
||||
secure_session_cookie: bool,
|
||||
|
||||
/// 使用 biliup 1.0.7 风格配置文件启动录制
|
||||
#[arg(short, long, value_name = "FILE")]
|
||||
config: Option<PathBuf>,
|
||||
@@ -228,7 +233,12 @@ mod tests {
|
||||
assert_eq!(cli.user_cookie, Path::new("cookies.json"));
|
||||
assert!(matches!(
|
||||
cli.command,
|
||||
Commands::Server { ref bind, auth: false, .. } if bind == "127.0.0.1"
|
||||
Commands::Server {
|
||||
ref bind,
|
||||
auth: false,
|
||||
secure_session_cookie: false,
|
||||
..
|
||||
} if bind == "127.0.0.1"
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ pub async fn run(
|
||||
run_with_cookie(
|
||||
addr,
|
||||
auth,
|
||||
false,
|
||||
log_handle,
|
||||
config_path,
|
||||
PathBuf::from("cookies.json"),
|
||||
@@ -48,6 +49,7 @@ pub async fn run(
|
||||
pub async fn run_with_cookie(
|
||||
addr: (&str, u16),
|
||||
auth: bool,
|
||||
secure_session_cookie: bool,
|
||||
log_handle: LogHandle,
|
||||
config_path: Option<PathBuf>,
|
||||
user_cookie: PathBuf,
|
||||
@@ -111,7 +113,7 @@ pub async fn run_with_cookie(
|
||||
}
|
||||
|
||||
tracing::info!("migrations successfully ran, initializing axum server...");
|
||||
ApplicationController::serve(&addr, auth, service_register)
|
||||
ApplicationController::serve(&addr, auth, secure_session_cookie, service_register)
|
||||
.await
|
||||
.attach("could not initialize application routes")?;
|
||||
Ok(())
|
||||
|
||||
@@ -154,11 +154,13 @@ async fn main() -> AppResult<()> {
|
||||
bind,
|
||||
port,
|
||||
auth,
|
||||
secure_session_cookie,
|
||||
config,
|
||||
} => {
|
||||
biliup_cli::run_with_cookie(
|
||||
(&bind, port),
|
||||
auth,
|
||||
secure_session_cookie,
|
||||
console_reload_handle,
|
||||
config,
|
||||
user_cookie,
|
||||
|
||||
@@ -28,6 +28,7 @@ impl ApplicationController {
|
||||
pub async fn serve(
|
||||
addr: &SocketAddr,
|
||||
enable_login_guard: bool,
|
||||
secure_session_cookie: bool,
|
||||
service_register: ServiceRegister,
|
||||
) -> AppResult<()> {
|
||||
// 会话层配置
|
||||
@@ -50,10 +51,13 @@ impl ApplicationController {
|
||||
|
||||
// 配置会话管理层
|
||||
let session_layer = SessionManagerLayer::new(session_store)
|
||||
// Loopback development uses plain HTTP. A non-loopback bind is
|
||||
// expected to sit behind TLS, so never send its session cookie over
|
||||
// an insecure browser connection.
|
||||
.with_secure(!addr.ip().is_loopback())
|
||||
// The server itself only speaks plain HTTP, and browsers refuse to
|
||||
// store `Secure` cookies delivered over insecure remote origins, so
|
||||
// a forced `Secure` attribute on non-loopback binds silently broke
|
||||
// every direct-HTTP login. Default to a non-`Secure` cookie and let
|
||||
// deployments behind an HTTPS reverse proxy opt in explicitly via
|
||||
// `--secure-session-cookie`.
|
||||
.with_secure(secure_session_cookie)
|
||||
.with_name("biliup.sid")
|
||||
.with_expiry(Expiry::OnInactivity(Duration::days(7)));
|
||||
// .with_signed(key);
|
||||
@@ -167,11 +171,12 @@ async fn shutdown_signal(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::with_optional_auth;
|
||||
use crate::server::api::auth;
|
||||
use crate::server::infrastructure::connection_pool::ConnectionManager;
|
||||
use crate::server::infrastructure::users::Backend;
|
||||
use axum::Router;
|
||||
use axum::body::Body;
|
||||
use axum::http::{Request, StatusCode};
|
||||
use axum::http::{Request, StatusCode, header};
|
||||
use axum::routing::get;
|
||||
use axum_login::AuthManagerLayerBuilder;
|
||||
use tower::ServiceExt;
|
||||
@@ -210,4 +215,60 @@ mod tests {
|
||||
assert_eq!(request_log_route(true).await, StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(request_log_route(false).await, StatusCode::OK);
|
||||
}
|
||||
|
||||
/// Issues a real login-session `Set-Cookie` through the register endpoint
|
||||
/// and returns the raw header value.
|
||||
async fn session_set_cookie_header(secure_session_cookie: bool) -> String {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let db = dir.path().join("data.sqlite3");
|
||||
let pool = ConnectionManager::new_pool(db.to_str().unwrap())
|
||||
.await
|
||||
.unwrap();
|
||||
let session_store = SqliteStore::new(pool.clone());
|
||||
session_store.migrate().await.unwrap();
|
||||
let auth_layer = AuthManagerLayerBuilder::new(
|
||||
Backend::new(pool),
|
||||
SessionManagerLayer::new(session_store).with_secure(secure_session_cookie),
|
||||
)
|
||||
.build();
|
||||
let app = auth::router().layer(auth_layer);
|
||||
|
||||
let response = app
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/v1/users/register")
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(
|
||||
r#"{"username":"biliup","password":"test-password"}"#,
|
||||
))
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
response
|
||||
.headers()
|
||||
.get(header::SET_COOKIE)
|
||||
.expect("login must establish a session cookie")
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Direct HTTP access (the default deployment) must not mark the session
|
||||
/// cookie `Secure`, otherwise browsers on remote non-HTTPS origins drop it
|
||||
/// and every login silently bounces back to the login page.
|
||||
#[tokio::test]
|
||||
async fn session_cookie_secure_attribute_follows_configuration() {
|
||||
assert!(
|
||||
!session_set_cookie_header(false).await.contains("Secure"),
|
||||
"default session cookie must work over plain HTTP"
|
||||
);
|
||||
assert!(
|
||||
session_set_cookie_header(true).await.contains("Secure"),
|
||||
"--secure-session-cookie must mark the cookie Secure"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -284,11 +284,13 @@ pub(crate) async fn _main(args: &[String]) -> AppResult<()> {
|
||||
bind,
|
||||
port,
|
||||
auth,
|
||||
secure_session_cookie,
|
||||
config,
|
||||
} => {
|
||||
biliup_cli::run_with_cookie(
|
||||
(&bind, port),
|
||||
auth,
|
||||
secure_session_cookie,
|
||||
reload_handle,
|
||||
config,
|
||||
cli.user_cookie,
|
||||
|
||||
Reference in new issue
Block a user