fix(server): 会话 Cookie 不再强制 Secure,修复 HTTP 远程访问登录后无法保持登录态 (#1669)

Co-authored-by: Noah <17682131+ransxd@users.noreply.github.com>
This commit is contained in:
NoahandNoah authored and GitHub committed 2026-08-23 15:20:11 +00:00
1 parent 6cef8afe22
commit c351c39771
5 files changed
+84 -7

No files matched your search

+11 -1
View File
@@ -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"
));
}
+3 -1
View File
@@ -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(())
+2
View File
@@ -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,
+66 -5
View File
@@ -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"
);
}
}
+2
View File
@@ -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,