域名 AXUM.RS 将于2025年10月到期。我们无意再对其进行续费,我们希望你能够接续这个域名,让更多 AXUM 开发者继续受益。
  • 方案1️⃣AXUM.RS 域名 = 3000
  • 方案2️⃣方案1️⃣ + 本站所有专题原始 Markdown 文档 = 5000
  • 方案3️⃣方案2️⃣ + 本站原始数据库 = 5500
如果你有意接续这份 AXUM 情怀,请与我们取得联系。
说明:
  1. 如果有人购买 AXUM.RS 域名(方案1️⃣),或者该域名到期,本站将启用新的免费域名继续提供服务。
  2. 如果有人购买了 AXUM.RS 域名,且同时购买了内容和/或数据库(方案2️⃣/方案3️⃣),本站将关闭。届时我们或许会以另一种方式与你再相遇。

安全与鉴权

本章将讨论使用jwt进行鉴权。

本章涉及的 crate 有:

  • blog-auth:jwt实现
  • blog-backend:上一章的后台管理web服务

本站的《漫游axum》曾经讨论过 JWT 以及在axum集成JWT

本站的《漫游axum》曾经讨论过 JWT 以及在axum集成JWT

handler::login 登录

pub async fn login(
    Extension(state): Extension<Arc<AppState>>,
    Form(frm): Form<form::Login>,
) -> Result<(StatusCode, HeaderMap), String> {
    let condition = blog_proto::get_admin_request::Condition::ByAuth(ByAuth {
        email: frm.email,
        password: frm.password,
    });
    let mut admin = state.admin.clone();
    let resp = admin
        .get_admin(tonic::Request::new(blog_proto::GetAdminRequest {
            condition: Some(condition),
        }))
        .await
        .map_err(|err| err.to_string())?;
    let repl = resp.into_inner();
    let logined_admin = match repl.admin {
        Some(la) => la,
        None => return Err("登录失败".to_string()),
    };
    let claims = state.jwt.new_claims(logined_admin.id, logined_admin.email);
    let token = state.jwt.token(&claims).map_err(|err| err.to_string())?;
    let cookie = format!("axum_rs_token={}", &token);
    Ok(redirect_with_cookie("/m/cate", Some(&cookie)))
}

登录成功后,生成 jwt token,并保存在 cookie 中。

pub struct Auth(Claims);

#[async_trait]
impl<B> FromRequest<B> for Auth
where
    B: Send,
{
    type Rejection = String;
    async fn from_request(
        req: &mut axum::extract::RequestParts<B>,
    ) -> Result<Self, Self::Rejection> {
        let state = req.extensions().get::<Arc<model::AppState>>().unwrap();
        let headers = req.headers();
        let claims = match cookie::get(headers, "axum_rs_token") {
            Some(token) => state
                .jwt
                .verify_and_get(&token)
                .map_err(|err| err.to_string())?,
            None => return Err("请登录".to_string()),
        };
        Ok(Self(claims))
    }
}
  • 从 cookie 中获取 jwt token
  • 验证此token

main()

#[tokio::main]
async fn main() {
    let addr = env::var("ADDR").unwrap_or("0.0.0.0:59527".to_string());
    let jwt_secret =
        env::var("JWT_SECRET").unwrap_or("PRFw6DQuWfFSQZjuUCnCeLhLXfWetA3r".to_string());
    let jwt_iss = env::var("JWT_ISS").unwrap_or("axum.rs".to_string());
    let jwt_exp = env::var("JWT_EXP").unwrap_or("120".to_string());
    let jwt_exp = jwt_exp.parse().unwrap_or(120);

    let cate = CategoryServiceClient::connect("http://127.0.0.1:19527")
        .await
        .unwrap();
    let topic = TopicServiceClient::connect("http://127.0.0.1:29527")
        .await
        .unwrap();
    let admin = AdminServiceClient::connect("http://127.0.0.1:49527")
        .await
        .unwrap();
    let tera = Tera::new("blog-backend/templates/**/*.html").unwrap();
    let jwt = Jwt::new(jwt_secret, jwt_exp, jwt_iss);

    let m_router = Router::new()
        .route("/cate", get(handler::list_cate))
        .route(
            "/cate/add",
            get(handler::add_cate_ui).post(handler::add_cate),
        )
        .layer(axum::middleware::from_extractor::<middleware::Auth>());

    let app = Router::new()
        .nest("/m", m_router)
        .route("/", get(handler::index))
        .route("/login", get(handler::login_ui).post(handler::login))
        .route("/logout", get(handler::logout))
        .layer(Extension(Arc::new(model::AppState {
            tera,
            admin,
            cate,
            topic,
            jwt,
        })));

    axum::Server::bind(&addr.parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

现在,main() 函数添加了几个 jwt 相关的配置,它们都是从环境变量中读取的。

特别地,监听地址也变成了从环境变量中读取

作业

将分类服务、文章服务和管理员服务的地址从硬编码改成从环境变量读取。

gRPC 服务的安全与鉴权

对于本专题而言,gPRC 不是必须的,因为所有 gRPC 都监听 127.0.0.1,同时,能假设确保所有服务都是自己开发的,知道哪些是后台管理才可以调用的。

也正是因为以上假设,所以 gPRC 安全问题暴露了。

安全

本专题中所有服务的通讯都是使用非安全的协议,为了数据传输的安全,你可能需要 TLS

鉴权

本专题中,gRPC 没有任何鉴权,都是建立在web开发者知道哪些 gRPC 可以公开调用,哪些需要后台验证才能调用。试想,如果有程序员在前台WEB中调用 gPRC 创建管理员,结果将是灾难性的。

gRPC 提供了拦截器metadata,利用它们,可以将web 服务的 jwt token 传递到 gPRC 服务,实现 gRPC 鉴权。

要查看完整内容,请先登录