sa_token_core/sso/
checker.rs1use async_trait::async_trait;
6
7use crate::error::SaTokenResult;
8use crate::sso::ticket_store::SsoTicketStore;
9
10#[async_trait]
13pub trait TicketChecker: Send + Sync {
14 async fn check_and_consume(&self, ticket_id: &str, service: &str) -> SaTokenResult<String>;
17}
18
19pub struct LocalTicketChecker {
22 pub store: SsoTicketStore,
25}
26
27impl std::fmt::Debug for LocalTicketChecker {
28 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29 f.write_str("LocalTicketChecker { .. }")
30 }
31}
32
33#[async_trait]
34impl TicketChecker for LocalTicketChecker {
35 async fn check_and_consume(&self, ticket_id: &str, service: &str) -> SaTokenResult<String> {
36 self.store.consume(ticket_id, service).await
37 }
38}
39
40#[cfg(feature = "sso-http")]
41use std::collections::BTreeMap;
42
43#[cfg(feature = "sso-http")]
44use serde::Deserialize;
45#[cfg(feature = "sso-http")]
46use uuid::Uuid;
47
48#[cfg(feature = "sso-http")]
49use crate::error::SaTokenError;
50#[cfg(feature = "sso-http")]
51use crate::sso::sign::RequestSign;
52
53#[cfg(feature = "sso-http")]
56pub struct HttpTicketChecker {
57 pub check_url: String,
60 pub sign: RequestSign,
63 pub service: String,
66 client: reqwest::Client,
67}
68
69#[cfg(feature = "sso-http")]
70impl HttpTicketChecker {
71 pub fn new(
74 check_url: impl Into<String>,
75 sign: RequestSign,
76 service: impl Into<String>,
77 ) -> Self {
78 Self {
79 check_url: check_url.into(),
80 sign,
81 service: service.into(),
82 client: reqwest::Client::new(),
83 }
84 }
85}
86
87#[cfg(feature = "sso-http")]
88#[derive(Deserialize)]
89struct CheckTicketHttpBody {
90 login_id: String,
91 timestamp: String,
92 nonce: String,
93 sign: String,
94}
95
96#[cfg(feature = "sso-http")]
97#[async_trait]
98impl TicketChecker for HttpTicketChecker {
99 async fn check_and_consume(&self, ticket_id: &str, service: &str) -> SaTokenResult<String> {
100 if service != self.service {
101 return Err(SaTokenError::ServiceMismatch);
102 }
103 let mut params = BTreeMap::new();
104 params.insert("ticket".into(), ticket_id.to_string());
105 params.insert("service".into(), service.to_string());
106 params.insert(
107 "timestamp".into(),
108 chrono::Utc::now().timestamp().to_string(),
109 );
110 params.insert("nonce".into(), Uuid::new_v4().simple().to_string());
111 let sign = self.sign.sign_params(¶ms)?;
112 params.insert("sign".into(), sign);
113 let qs: String = params
114 .iter()
115 .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
116 .collect::<Vec<_>>()
117 .join("&");
118 let url = format!("{}?{}", self.check_url, qs);
119 let body = self
120 .client
121 .get(&url)
122 .send()
123 .await
124 .map_err(|e| SaTokenError::StorageError(e.to_string()))?
125 .text()
126 .await
127 .map_err(|e| SaTokenError::StorageError(e.to_string()))?;
128 let parsed: CheckTicketHttpBody =
129 serde_json::from_str(&body).map_err(|_| SaTokenError::InvalidTicket)?;
130 let mut verify = BTreeMap::new();
131 verify.insert("login_id".into(), parsed.login_id.clone());
132 verify.insert("timestamp".into(), parsed.timestamp.clone());
133 verify.insert("nonce".into(), parsed.nonce.clone());
134 self.sign
135 .verify_params(&verify, &parsed.sign)
136 .await
137 .map_err(crate::sso::sign::map_sign_err_to_sso)?;
138 Ok(parsed.login_id)
139 }
140}