如何创建具有基于会话身份验证的 actix-web HttpServer?

pic*_*ser 2 authentication session rust actix-web

我正在开发一个内部 API,经过批准的用户可以使用该 API 读取数据库并将其插入数据库。我的目的是让这个程序在我们的本地网络上运行,并且让多个用户或应用程序能够访问它。

\n

在当前状态下,只要用户运行客户端的本地实例并在其下完成所有工作,它就会起作用localhost. 但是,当同一用户尝试从其 IP 地址登录时,会话不会被存储,并且无法访问任何内容。尝试从网络上的另一台计算机连接到运行客户端的计算机时,结果是相同的。

\n

在发表评论或回答之前,请注意,这是我第一次实现身份验证。我的任何错误或严重错误都只是出于无知。

\n

我的Cargo.toml文件包含以下依赖项:

\n
actix-session = { version = "0.7.1", features = ["cookie-session"] }\nactix-web = "^4"\nargon2 = "0.4.1"\nrand_core = "0.6.3"\nreqwest = "0.11.11"\nserde = { version = "1.0.144", features = ["derive"] }\nserde_json = "1.0.85"\nsqlx = { version = "0.6.1", features = ["runtime-actix-rustls", "mysql", "macros"] }\n
Run Code Online (Sandbox Code Playgroud)\n

以下是 的内容main.rs

\n
use actix_session::storage::CookieSessionStore;\nuse actix_session::SessionMiddleware;\nuse actix_web::cookie::Key;\nuse actix_web::web::{get, post, Data, Path};\nuse actix_web::{HttpResponse, Responder};\n\n#[actix_web::main]\nasync fn main() -> std::io::Result<()> {\n    let secret_key = Key::generate();\n\n    // Load or create a new config file.\n    // let settings = ...\n\n    // Create a connection to the database.\n    let pool = sqlx::mysql::MySqlPoolOptions::new()\n        .connect(&format!(\n            "mysql://{}:{}@{}:{}/mydb",\n            env!("DB_USER"),\n            env!("DB_PASS"),\n            env!("DB_HOST"),\n            env!("DB_PORT"),\n        ))\n        .await\n        .unwrap();\n\n    println!(\n        "Application listening on {}:{}",\n        settings.host,\n        settings.port,\n    );\n\n    // Instantiate the application and add routes for each handler.\n    actix_web::HttpServer::new(move || {\n        let logger = actix_web::middleware::Logger::default();\n        actix_web::App::new()\n            .wrap(SessionMiddleware::new(\n                CookieSessionStore::default(),\n                secret_key.clone(),\n            ))\n            .wrap(logger)\n            .app_data(Data::new(pool.clone()))\n            /*\n                Routes that return all rows from a database table.\n            */\n            /*\n                Routes that return a webpage.\n            */\n            .route("/new", get().to(new))\n            .route("/login", get().to(login))\n            .route("/register", get().to(register))\n            /*\n                Routes that deal with authentication.\n            */\n            .route("/register", post().to(register_user))\n            .route("/login", post().to(login_user))\n            .route("/logout", get().to(logout_user))\n            /*\n                Routes that handle POST requests.\n            */\n    })\n    .bind(format!("{}:{}", settings.host, settings.port))?\n    .run()\n    .await\n}\n
Run Code Online (Sandbox Code Playgroud)\n

涉及认证的代码如下:

\n
use crate::model::User;\nuse actix_session::Session;\nuse actix_web::web::{Data, Form};\nuse actix_web::{error::ErrorUnauthorized, HttpResponse};\nuse argon2::password_hash::{rand_core::OsRng, PasswordHasher, SaltString};\nuse argon2::{Argon2, PasswordHash, PasswordVerifier};\nuse sqlx::{MySql, Pool};\n\n#[derive(serde::Serialize)]\npub struct SessionDetails {\n    user_id: u32,\n}\n\n#[derive(Debug, sqlx::FromRow)]\npub struct AuthorizedUser {\n    pub id: u32,\n    pub username: String,\n    pub password_hash: String,\n    pub approved: bool,\n}\n\npub fn check_auth(session: &Session) -> Result<u32, actix_web::Error> {\n    match session.get::<u32>("user_id").unwrap() {\n        Some(user_id) => Ok(user_id),\n        None => Err(ErrorUnauthorized("User not logged in.")),\n    }\n}\n\npub async fn register_user(\n    data: Form<User>,\n    pool: Data<Pool<MySql>>,\n) -> Result<String, Box<dyn std::error::Error>> {\n    let data = data.into_inner();\n    let salt = SaltString::generate(&mut OsRng);\n\n    let argon2 = Argon2::default();\n\n    let password_hash = argon2\n        .hash_password(data.password.as_bytes(), &salt)\n        .unwrap()\n        .to_string();\n\n    // Use to verify.\n    // let parsed_hash = PasswordHash::new(&hash).unwrap();\n\n    const INSERT_QUERY: &str =\n        "INSERT INTO users (username, password_hash) VALUES (?, ?) RETURNING id;";\n\n    let fetch_one: Result<(u32,), sqlx::Error> = sqlx::query_as(INSERT_QUERY)\n        .bind(data.username)\n        .bind(password_hash)\n        .fetch_one(&mut pool.acquire().await.unwrap())\n        .await;\n\n    match fetch_one {\n        Ok((user_id,)) => Ok(user_id.to_string()),\n        Err(err) => Err(Box::new(err)),\n    }\n}\n\npub async fn login_user(\n    session: Session,\n    data: Form<User>,\n    pool: Data<Pool<MySql>>,\n) -> Result<HttpResponse, Box<dyn std::error::Error>> {\n    let data = data.into_inner();\n    let fetched_user: AuthorizedUser = match sqlx::query_as(\n        "SELECT id, username, password_hash, approved FROM users WHERE username = ?;",\n    )\n    .bind(data.username)\n    .fetch_one(&mut pool.acquire().await?)\n    .await\n    {\n        Ok(fetched_user) => fetched_user,\n        Err(e) => return Ok(HttpResponse::NotFound().body(format!("{e:?}"))),\n    };\n\n    let parsed_hash = PasswordHash::new(&fetched_user.password_hash).unwrap();\n\n    match Argon2::default().verify_password(&data.password.as_bytes(), &parsed_hash) {\n        Ok(_) => {\n            if !fetched_user.approved {\n                return Ok(\n                    HttpResponse::Unauthorized().body("This account has not yet been approved.")\n                );\n            }\n\n            session.insert("user_id", &fetched_user.id)?;\n            session.renew();\n\n            Ok(HttpResponse::Ok().json(SessionDetails {\n                user_id: fetched_user.id,\n            }))\n        }\n        Err(_) => Ok(HttpResponse::Unauthorized().body("Incorrect password.")),\n    }\n}\n\npub async fn logout_user(session: Session) -> HttpResponse {\n    if check_auth(&session).is_err() {\n        return HttpResponse::NotFound().body("No user logged in.");\n    }\n\n    session.purge();\n    HttpResponse::SeeOther()\n        .append_header(("Location", "/login"))\n        .body(format!("User logged out successfully."))\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我已经将我的客户端设置为0.0.0.0在 port 上与主机一起运行80,但是凭借我所拥有的一点网络知识,这是我能想到的最好的做法 \xe2\x80\x94 我在这里迷失了。任何帮助将不胜感激。

\n

pic*_*ser 6

事实证明,cookie 没有被传输,因为我们的本地网络没有使用https.

改变这个...

.wrap(SessionMiddleware::new(
    CookieSessionStore::default(),
    secret_key.clone(),
))
Run Code Online (Sandbox Code Playgroud)

到以下...

.wrap(
    SessionMiddleware::builder(CookieSessionStore::default(), secret_key.clone())
        .cookie_secure(false)
        .build(),
)
Run Code Online (Sandbox Code Playgroud)

解决了这个问题。