oauth + tauri 登录
场景
进行登录时,会向后端登录接口传入一个 redirectUrl,在在登录成功后访问该页面。
redirectURL := c.DefaultQuery("redirect_url", viper.GetString("_sso.redirect_url"))
example:
当redirectUrl=localhost:3030时
则后端接口返回的认证地址为
http://privilege.test.vvv:8080/ssoChoice?RequestURL=http%3A%2F%2Flocalhost%3A3030%2Fapi%2Flogin
需要在登陆成功后,获取在 redirectURL 后拼接的校验信息 token,并将其写入 tauri 应用程序的 localStorage。
实现方案
- 在本地使用 rust 开启一个服务,并将该服务地址作为 redirectUrl 传给后端。
- 通过 tauri::api::shell 唤起默认浏览器打开认证地址。登录成功后,则会访问这个本地服务。
- 当访问本地 rust 服务时,通过 emit 传递一个事件,并将 req.uri 中的 token 取出作为事件信息传输。
- 前端 listen 这个事件,取出其中的信息(token)写入 localStorage
启动 rust 服务- actix_web 框架
cargo.toml 添加依赖
[dependencies]
actix-web = "4"
搭建服务,新建 server.rs HttpServer接受一个AppHandle(application factory)作为参数,并且为每个线程创建一个应用程序实例, 因此必须多次构建应用程序
如果需要在多个线程中共享数据,需要使用共享对象,eg:Mutex,Arc
这里我们需要emit,使用Mutex包装AppHandle
使用app_data存储该数据,可以在路由中使用提取器获取数据。
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use std::{sync::Mutex};
use actix_web::{middleware, web, App, HttpServer};
use tauri::AppHandle;
struct TauriAppState {
app: Mutex<AppHandle>,
}
#[get("/")]
async fn hello() -> impl Responder {
HttpResponse::Ok().body("Hello world!")
}
#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
HttpResponse::Ok().body(req_body)
}
<!-- 根据需求login函数接受两个参数req和data-->
async fn login(req:HttpRequest,data:web::Data<TauriAppState>) -> impl Responder {
let app_handle = data.app.lock().unwrap();
app_handle.emit_all("get_token", Some(req.uri().to_string())).unwrap();
HttpResponse::Ok().body("login success!")
}
#[actix_web::main]
pub async fn inti(app:AppHandle) -> std::io::Result<()> {
let tauri_app = web::Data::new(TauriAppState {
app: Mutex::new(app_handle),
});
HttpServer::new(|| {
App::new()
.app_data(tauri_app.clone())
.service(hello)
.service(echo)
<!-- 这里不太清楚为什么将login封装为一个函数,仅仅能在初始化的时候会emit,但是后续不会再传递信息-->
.route("/login", web::get().to(login))
<!-- 这样写成闭包的形式就不会出现上述情况-->
.route("/login", web::get().to(
|req: actix_web::HttpRequest, data: web::Data<TauriAppState>| async move {
let app_handle = data.app.lock().unwrap();
app_handle.emit_all("get_token", Some(req.uri().to_string())).unwrap();
actix_web::HttpResponse::Ok()
.body("Login success,Please close the webpage and return to the application")
},
))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
可以使用 scope 或 resource 为路由挂载基本路径
在tauri程序中启动服务,为了防止程序主进程终端,使用thread模块在一个单独的线程中运行服务器
fn main() {
tauri::Builder::default()
.setup(|app| {
let handle = app.handle();
let boxed_handle = Box::new(handle);
thread::spawn(move || {
server::init(*boxed_handle).unwrap();
});
Ok(())
})
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
shell打开login_webview
#[tauri::command]
fn open_login_webview<R: Runtime>(app_handle: AppHandle<R>, url: &str) -> Result<(), String> {
shell::open(&app_handle.shell_scope(), format!("{}", url), None).unwrap();
Ok(())
}
前端
await invoke('plugin:webview|open_login_webview', {
url: webviewUrl,
});
const unlisten = await listen('get_token', (event) => {
const url = (event.payload as string).split('token=');
if (url[1].length > 0) {
localStg.set('token', url[1]);
location.reload();
}
});