9 个不稳定版本 (3 个破坏性版本)

使用旧的 Rust 2015

0.4.3 2018年7月30日
0.4.2 2018年7月22日
0.3.1 2018年7月12日
0.2.1 2018年7月1日
0.1.2 2018年6月30日

#2579 in 数据库接口

27 每月下载量

BSD-3-Clause

39KB
627 代码行

Actix 数据库身份提供者

Build Status Crates.io docs.rs

Actix 框架的 SQL 数据库(diesel)集成

描述

实现了 Actix-Web 身份中间件的 SQL 后端。不执行任何身份验证,仅在需要时“记住”用户。可以通过 SqlIdentityBuilder::response_header() 方法配置包含授权令牌的返回头。

正常服务器应用程序流程

  • 应用程序验证用户身份
  • 应用程序使用用户可识别的字符串调用 remember()
  • SQL 身份中间件生成新令牌并将其嵌入响应头中
  • 应用程序返回响应(包含头信息)

从这里,可以按照身份中间件指南继续正常流程

正常客户端应用程序流程

  • 客户端向登录端点发送 POST 请求,并由服务器授权
  • 客户端接收响应,从指定的头信息中提取授权令牌
  • 在未来的请求(包括注销)中,客户端使用返回的令牌构建带有身份验证头的载体认证

支持的 SQL 变体

  • SQLite
  • MySQL
  • Postgres

特性

默认: SQLite, MySQL, Postgres

sqlite: 包含 SQLite 支持

mysql: 包含 MySQL 支持

postgres: 包含 PostgreSQL 支持

数据库要求

此crate需要一个名为 identities 的表,包含以下字段

字段 类型 约束 描述
id BIGINT PRIMARY KEY, AUTO INCREMENT A unique id that is not the token, for revoking sessions
token CHAR(32) NOT NULL, UNIQUE The auto-generated token field, will be used to lookup user
userid TEXT NOT NULL The user id to remember, probably a key in another table
ip TEXT The IP the user most recently connected from
useragent TEXT The user-agent of the most recent connection
created TIMESTAMP NOT NULL Timestamp (w/out timezone) this token was created
modified TIMESTAMP NOT NULL Timestamp (w/out timezone) this token was last used

SQLite、MySQL 和 PostgreSQL 的示例 SQL 文件可在存储库的 sql/ 文件夹中找到

服务器示例

extern crate actix_web;
extern crate actix_web_sql_identity;

use actix_web::{http, server, App, HttpRequest, Responder};
use actix_web::middleware::identity::{IdentityService, RequestIdentity};
use actix_web_sql_identity::SqlIdentityBuilder;

const POOL_SIZE: usize = 3;     // Number of connections per pool

fn login(mut req: HttpRequest) -> impl Responder {
    // Should pull username/id from request
    req.remember("username_or_id".to_string());
    "Logged in!".to_string()
}

fn profile(req: HttpRequest) -> impl Responder {
    if let Some(user) = req.identity() {
        format!("Hello, {}!", user)
    } else {
        "Hello, anonymous user!".to_string()
    }
}

fn logout(mut req: HttpRequest) -> impl Responder {
    req.forget();
    "Logged out!".to_string()
}

fn main() {
    server::new(|| {
		// Construct our policy, passing the address and any options
        let policy = SqlIdentityBuilder::new("sqlite://my.db")
            .pool_size(POOL_SIZE);

        App::new()
            .route("/login", http::Method::POST, login)
            .route("/profile", http::Method::GET, profile)
            .route("/logout", http::Method::POST, logout)
            .middleware(IdentityService::new(
                    policy.finish()
                        .expect("failed to connect to database")))
    })
    .bind("127.0.0.1:7070").unwrap()
    .run();
}

客户端示例

extern crate reqwest;

#[macro_use]
extern crate hyper;

use hyper::header::{Authorization, Bearer, Headers};
use reqwest::{Client, Response};

// Build our custom header that will contain our returned token
header! { (XActixAuth, "X-Actix-Auth") => [String] }

/// Builds a GET request to send to the server, with optional authentication
///
/// # Arguments
///
/// * `client` - Client to build request with
/// * `uri` - Endpoint to target (e.g., /index, /profile, etc)
/// * `token` - An optional authentication token
fn build_get(client: &Client, uri: &str, token: Option<&str>) -> Response {
    let mut req = client
        .get(format!("http://127.0.0.1:7070{}", uri).as_str());

    if let Some(token) = token {
        let mut headers = Headers::new();
        headers.set(Authorization(Bearer {
            token: token.to_owned(),
        }));
        req.headers(headers);
    }

    let req = req.build()
        .expect("failed to build request");

    client.execute(req).expect("failed to send request")
}

/// Builds a POST request to send to the server, with optional authentication
///
/// # Arguments
///
/// * `client` - Client to build request with
/// * `uri` - Endpoint to target (e.g., /login, /logout)
/// * `token` - An optional authentication token
fn build_post(client: &Client, uri: &str, token: Option<&str>) -> Response {
    let mut req = client
        .post(format!("http://127.0.0.1:7070{}", uri).as_str());

    if let Some(token) = token {
        let mut headers = Headers::new();
        headers.set(Authorization(Bearer {
            token: token.to_owned(),
        }));
        req.headers(headers);
    }

    let req = req.build()
        .expect("failed to build request");

    client.execute(req).expect("failed to send request")
}

/// Pretty print a response
///
/// # Arguments
///
/// * `resp` - Response to print
fn print_response(resp: &mut Response) {
    let uri = resp.url().clone();
    println!("[{0: <15}] {1: <30} {2}", resp.status(), uri.as_str(), resp.text().expect("failed to read response"));
}

fn main() {
    let client = Client::new();

    // Get Index
    let mut resp = build_get(&client, "/", None); 
    print_response(&mut resp);

    // Get Profile (no auth)
    let mut resp = build_get(&client, "/profile", None);
    print_response(&mut resp);

    // Login
    let mut resp = build_post(&client, "/login", None);
    print_response(&mut resp);

    // Extract the auth token from the header
    // (Header field can be changed on server, default is used here)
    let hdrs = resp.headers();
    let token = hdrs.get::<XActixAuth>().unwrap();
    //println!("[token]: {:?}", token.0);

    // Get Profile (auth)
    let mut resp = build_get(&client, "/profile", Some(token.0.as_ref()));
    print_response(&mut resp);

    // Logout
    let mut resp = build_post(&client, "/logout", Some(token.0.as_str()));
    print_response(&mut resp);

    // Get Profile (auth)
    let mut resp = build_get(&client, "/profile", Some(token.0.as_ref()));
    print_response(&mut resp);
}

许可

BSD 3-Clause 许可协议

作者

Kevin Allison kvnallsn AT gmail.com

依赖项

~26–38MB
~613K SLoC