rust初学记录: cas验证

原创
03/12 10:35
阅读数 141

最近github很难访问的说, 没有找到用于actix 的cas验证的现成的代码,先自己手写一次cas验证的过程

总体依赖

[dependencies]
actix-web = { version = "3", features = ["rustls"] }
uuid = { version = "0.8", features = ["serde", "v4"] }
actix-files = "0.5.0"
env_logger = "0.8"
#延迟加载全局变量
lazy_static = "1.4"
#json
serde_derive = "1.0"
#json
serde_json = "1.0"
#redis连接池
deadpool-redis = { version = "0.6", features = ["config"] }
#URI 编码
urlencoding ="1.1.1"
futures="0.3.13"
qstring = "0.7.2"
reqwest="0.9.18"
[profile.release]
lto = true
opt-level = 'z'

1.   封装一些redis的操作代码

   pool.rs

use deadpool_redis::{Pool, Config};
use std::env;
//获取redis连接池
pub fn get_pool() -> Pool {
    let mut cfg = Config::default();
    cfg.url = Some(format!("redis://:{}@{}:{}",
       env::var("REDIS_PASSWORD").unwrap(),
       env::var("REDIS_HOST").unwrap(),
       env::var("REDIS_PORT").unwrap()   
    ));
    cfg.create_pool().unwrap()
}

//全局连接池
lazy_static! { 
    pub static ref REDISPOOL: Pool= {
       
        get_pool()
    };
}

oper.rs

use crate::rediswrapper::pool::REDISPOOL;
use crate::deadpool_redis::{cmd};
//设置值
pub async fn set_key_val(key : &String ,val : &String){
    let mut r = REDISPOOL.get().await.unwrap();
    cmd("SET").arg(&[key.to_owned(), val.to_owned(), "EX".to_owned(),"60".to_owned()]).execute_async(&mut r).await.unwrap();
  
}


//获取值
pub async fn get_key_val(key : &String ) -> Option<String> {
    let mut r = REDISPOOL.get().await.unwrap();
    let v = cmd("GET").arg(&[key.to_owned()]).query_async::<String>(&mut r).await;
    match v{
        Ok(s)=>{ Some(s)}
        Err(_e)=>{None}
    }
}

2. 写 middleware ,如果参数没有ticket 或者cookie没有ticket的跳转到cas登录页面验证 ,获取ticket后获取 验证信息并存储到 redis (分布式环境下的需求)

   cas验证相关的封装

    auth.rs

   

use crate::rediswrapper::oper;
use actix_web::{  HttpMessage};

pub const TICKET_COOKIE_NAME: &'static str = "cas-ticket";
pub const REDIS_KEY_PREFIX: &'static str = "cas::cas-ticket";
use std::env;
use urlencoding::encode;
//是否已登录
pub async fn is_logined(req: &actix_web::dev::ServiceRequest) -> bool {
    if let Some(cookie) = req.cookie(&TICKET_COOKIE_NAME) {//先看cookie有没有ticket
        let ticket = cookie.value();
        let key = format!("{}-{}",REDIS_KEY_PREFIX, &ticket);
        //看看redis有没有存储验证信息
        if let Some(info) = oper::get_key_val(&key).await {
            true
        } else {
            false
        }
    } else {
       false
    }
}
//通过ticket从redis获取已登录信息
pub async fn get_logined_info_from_redis_by_ticket(ticket: &str) -> Option<String> {
    let key = format!("{}-{}",REDIS_KEY_PREFIX, &ticket);
    oper::get_key_val(&key).await
}

//根据ticket获取信息存入redis
pub async fn set_logined_info_into_redis_by_ticket(
    ticket: &str, /*令牌*/
    url: &String,    /*访问地址*/
) {
    //cas验证地址
    let cas_url = env::var("CAS_VALID_URL").unwrap();
    //获取验证信息的URL
    let validate_url = format!(
        "{}/validate?service={}&ticket={}&renew=false",
        cas_url.to_owned(),
        encode(url),
        ticket.to_owned()
    );

    println!("validate_url={}",&validate_url);

    let validate_response = reqwest::Client::builder()
    .danger_accept_invalid_certs(true)
    .build()
    .expect("client builder").get(&validate_url).send()
    .unwrap().text().unwrap();

    let key = format!("{}-{}",REDIS_KEY_PREFIX, &ticket);

    println!("validate_response={}",&validate_response);
    oper::set_key_val(&key, &validate_response).await
}
//验证跳转链接
pub fn get_redirect_url(  url: &String) -> String {
    //cas登录地址
    let cas_url = env::var("CAS_LOGIN_URL").unwrap();
    //来源系统标记
    let cas_srcsys = env::var("CAS_SRCSYS").unwrap();
    format!(
        "{}?srcsys={}&service={}",
        cas_url.to_owned(),
        cas_srcsys.to_owned(),
        encode(url)
    )

}

    middleware.rs

   

//cas过滤器验证
use actix_web::{  http, Error, HttpResponse};
use std::cell::RefCell;
use std::env;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};

use crate::webservice::cas::auth;
use actix_web::body::MessageBody;
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use futures::future::{ok, Ready};
use futures::Future;
use qstring::QString;

// custom request auth middleware
pub struct CasAuth;

impl<S, B> Transform<S> for CasAuth
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: MessageBody + 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Transform = CasAuthMiddleware<S>;
    type InitError = ();
    type Future = Ready<Result<Self::Transform, Self::InitError>>;

    fn new_transform(&self, service: S) -> Self::Future {
        ok(CasAuthMiddleware {
            service: Rc::new(RefCell::new(service)),
        })
    }
}

pub struct CasAuthMiddleware<S> {
    service: Rc<RefCell<S>>,
}

impl<S, B> Service for CasAuthMiddleware<S>
where
    S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
    S::Future: 'static,
    B: MessageBody + 'static,
{
    type Request = ServiceRequest;
    type Response = ServiceResponse<B>;
    type Error = Error;
    type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.service.poll_ready(cx)
    }

    fn call(&mut self, req: ServiceRequest) -> Self::Future {
        let mut svc = self.service.clone();

        Box::pin(async move {
            println!("req.path().to_string() =={}", req.path().to_string());
            if req.path().to_string() == "/sso" {
                //获取访问地址
                let redirect_scheme = env::var("CAS_REDIRECT_SCHEME").unwrap();
                let host = req.headers().get("HOST").unwrap().to_str().unwrap();
                let url = format!("{}://{}{}", &redirect_scheme, &host, req.path().to_string());

                if auth::is_logined(&req).await {
                    Ok(svc.call(req).await?)
                } else {
                    //获取ticket
                    let query_str = req.query_string();
                    let qs = QString::from(query_str);
                    println!("query_str={}", &query_str);
                    if let Some(ticket) = qs.get("ticket") {
                        println!("ticket={}", &ticket);
                        //获取验证信息并写入redis
                        auth::set_logined_info_into_redis_by_ticket(&ticket, &url).await;

                        //获取验证信息
                        let validate_response =
                            auth::get_logined_info_from_redis_by_ticket(&ticket)
                                .await
                                .unwrap();
                        if validate_response.starts_with("yes") {
                            Ok(svc.call(req).await?)
                        } else {
                            //跳转到cas验证

                            println!("redirect_url={}", auth::get_redirect_url(&url));
                            Ok(req.into_response(
                                HttpResponse::Found()
                                    .header(http::header::LOCATION, auth::get_redirect_url(&url))
                                    .finish()
                                    .into_body(),
                            ))
                        }
                    } else {
                        //跳转到cas验证
                        println!("redirect_url={}", auth::get_redirect_url(&url));
                        Ok(req.into_response(
                            HttpResponse::Found()
                                .header(http::header::LOCATION, auth::get_redirect_url(&url))
                                .finish()
                                .into_body(),
                        ))
                    }
                }
            } else {
                Ok(svc.call(req).await?)
            }
        })
    }
}

3.测试服务,输出验证信息

  sso.rs

  

use crate::webservice::cas::auth;
use actix_web::{get, http::Cookie, HttpRequest, HttpResponse, Responder,HttpMessage};
use qstring::QString;
#[get("/sso")]
pub async fn sso(req: HttpRequest) -> impl Responder {
    //获取ticket
    let query_str = req.query_string();
    let qs = QString::from(query_str);
    if let Some(ticket) = qs.get("ticket") {
        //获取验证信息
        let validate_response = auth::get_logined_info_from_redis_by_ticket(&ticket)
            .await
            .unwrap();

        HttpResponse::Ok()
            .content_type("text/plain;charset=utf-8")
            .cookie(
                Cookie::build(auth::TICKET_COOKIE_NAME.to_owned(), ticket.to_owned())
                    .path("/")
                    .secure(false)
                    .http_only(false)
                    .finish(),
            )
            .body(validate_response.to_owned())
    } else {
        if let Some(cookie) = req.cookie(auth::TICKET_COOKIE_NAME) {
            //先看cookie有没有ticket
            let ticket = cookie.value();
            //获取验证信息
            let validate_response = auth::get_logined_info_from_redis_by_ticket(&ticket)
                .await
                .unwrap();
            HttpResponse::Ok()
                .content_type("text/plain;charset=utf-8")
                .cookie(
                    Cookie::build(auth::TICKET_COOKIE_NAME.to_owned(), ticket.to_owned())
                        .path("/")
                        .secure(false)
                        .http_only(false)
                        .finish(),
                )
                .body(validate_response.to_owned())
        } else {
            HttpResponse::Ok()
                .content_type("application/json;charset=utf-8")
                .body("{code:0,msg:'ok'}")
        }
    }
}

访问  http://127.0.0.1:8080/sso

 输出  

  yes

  [cas登录的账号名]

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部