// JWT 解析在计时前完成;本场景测 Provider 数据库复核及随后实际角色读取。 use dever_runtime::api::{Request, Response}; struct AuthInput { service: Arc, requests: Vec, claims: Vec, now: i64, } fn prepare(path: &Path) -> (Arc, AuthInput) { let config = Arc::new(dever_runtime::api::Config::load(path, &["admin"]).unwrap()); let service = Arc::new( auth::Service::new( config.clone(), &[auth::Verification { name: "bench.account.verify", user_model: AUTH_USER_MODEL, tenant_model: AUTH_TENANT_MODEL, }], &[auth::Route { topic: "admin", required: true, user_models: &[AUTH_USER_MODEL], tenant_models: &[AUTH_TENANT_MODEL], }], ) .unwrap(), ); let now = auth::now().unwrap(); let claims = (0..17) .map(|index| Claims { subject: format!("account-{index:02} 中文\""), session: format!("session-{index}"), site: "admin".into(), tenant: None, }) .collect::>(); let requests = claims .iter() .map(|claims| { // 只用公开 Cookie 接口准备凭据,不读取 Secret 的私有字节。 let mut bootstrap = Request::new(Default::default()); *bootstrap.method_mut() = "POST".parse().unwrap(); *bootstrap.uri_mut() = "/login".parse().unwrap(); bootstrap .headers_mut() .insert("host", "benchmark.test".parse().unwrap()); bootstrap .headers_mut() .insert("origin", "https://benchmark.test".parse().unwrap()); let (mut context, _) = service.prepare("admin", &bootstrap, true, now).unwrap(); context .issue_cookie(&claims.subject, &claims.session, None, now) .unwrap(); let mut response = Response::new(Default::default()); context.publish(&mut response); let cookie = response.headers().get("set-cookie").unwrap(); let cookie = std::str::from_utf8(cookie.as_bytes()) .unwrap() .split(';') .next() .unwrap() .as_bytes() .to_vec(); let mut request = Request::new(Default::default()); *request.uri_mut() = "/bench/account/admin/permissions".parse().unwrap(); request .headers_mut() .insert("host", "benchmark.test".parse().unwrap()); request .headers_mut() .insert("cookie", cookie.try_into().unwrap()); request }) .collect(); ( config, AuthInput { service, requests, claims, now, }, ) } fn measure_auth( runtime: &Runtime, registry: &Arc, input: &AuthInput, generated: bool, roles: bool, count: usize, ) -> (u128, u64) { runtime .run(async |scope| { let mut contexts = input.requests[..16] .iter() .map(|request| { input .service .prepare("admin", request, false, input.now) .unwrap() .0 }) .collect::>(); let mut checksum = 0u64; let start = Instant::now(); for index in 0..count { let position = index % contexts.len(); let context = &mut contexts[position]; verify( generated, context, black_box(&input.claims[position]).clone(), scope, registry, ) .await .map_err(|error| error.0) .unwrap(); checksum += black_box( context.user_id(None).unwrap().unwrap() as u64 + context.id().unwrap().len() as u64, ); if roles { let mut database = DbContext::new(registry.clone()); let values = black_box(permissions(generated, context, scope, &mut database).await); for value in &values { for field in value { checksum += field.bytes().map(u64::from).sum::(); } } } } (start.elapsed().as_nanos(), checksum) }) .unwrap() } fn check_auth(runtime: &Runtime, registry: &Arc, input: &AuthInput) { runtime .run(async |scope| { let expected = vec![ [ "bench.account.admin.permissions", "bench", "account", "admin", "permissions", "GET", "http", ] .map(String::from), ]; for generated in [true, false] { for index in 0..16 { let (mut context, claims) = input .service .prepare("admin", &input.requests[index], false, input.now) .unwrap(); verify(generated, &mut context, claims.unwrap(), scope, registry) .await .map_err(|error| error.0) .unwrap(); assert_eq!(context.id().unwrap(), input.claims[index].subject); assert_eq!( context.user_id(Some(AUTH_USER_MODEL)).unwrap(), Some(index as i64 + 1) ); assert!( context .tenant_id(Some(AUTH_TENANT_MODEL)) .unwrap() .is_none() ); let mut database = DbContext::new(registry.clone()); assert_eq!( permissions(generated, &mut context, scope, &mut database).await, expected ); } for (index, wrong_session) in [(16, false), (0, true)] { let (mut context, claims) = input .service .prepare("admin", &input.requests[index], false, input.now) .unwrap(); let mut claims = claims.unwrap(); if wrong_session { claims.session.push_str("-revoked"); } assert_eq!( verify(generated, &mut context, claims, scope, registry) .await .unwrap_err() .0, 401 ); assert!(context.id().is_err()); } } }) .unwrap(); } fn main() { let (directory, path) = directory( "auth", r#""http":{"host":"127.0.0.1","port":18001},"sites":{"admin":{"path":"admin","hosts":["benchmark.test"],"auth":"session","origin":"https://benchmark.test"}},"auth":{"providers":{"session":{"verify":"bench.account.verify","jwtSecret":"benchmark-only-key-not-for-any-deployment","ttlSeconds":3600,"cookie":"session"}}}"#, ); let runtime = runtime(); let registry = runtime .run(async |scope| database_registry(&path, scope).await) .unwrap() .unwrap(); let (config, input) = prepare(&path); runtime .run(async |scope| { let mut database = DbContext::new(registry.clone()); for (index, claims) in input.claims.iter().enumerate() { let account = GENERATED_SEED( &claims.subject, &claims.session, index < 16, scope, &mut database, ) .await .unwrap(); assert_eq!(account.field0.0, index as i64 + 1); } let (mut context, claims) = input .service .prepare("admin", &input.requests[0], false, input.now) .unwrap(); drop(database); api_verify(&mut context, claims.unwrap(), scope, ®istry) .await .map_err(|error| error.0) .unwrap(); let mut database = DbContext::new(registry.clone()); let executor = database.on(AUTH_BINDING, scope).await.unwrap(); dever_runtime::authorization::synchronize( executor, scope, &config, &[dever_runtime::authorization::Declaration { topic: "admin", component: "bench", domain: "account", action: "permissions", method: "GET", }], ) .await .unwrap(); dever_runtime::authorization::save( &mut database, AUTH_BINDING, scope, &context, "reader", "Benchmark reader", false, &["bench.account.admin.permissions".into()], ) .await .unwrap(); for user in 1..=16 { let executor = database.on(AUTH_BINDING, scope).await.unwrap(); dever_runtime::authorization::grant(executor, scope, &context, user, "reader") .await .unwrap(); } }) .unwrap(); check_auth(&runtime, ®istry, &input); header(); if HTTP_BENCH { protected_http::run(&runtime, ®istry, &config, &input); } else { for (name, roles) in [ ("auth_provider_verify", false), ("auth_provider_role_read", true), ] { samples(name, |generated, count| { measure_auth(&runtime, ®istry, &input, generated, roles, count) }); } } check_auth(&runtime, ®istry, &input); drop(registry); drop(runtime); drop(directory); }