当前位置: 首页 > 工具软件 > phpCAS > 使用案例 >

基于laravel 5.7 集成 phpCAS 1.3.8 单点登出踩坑记。

沈弘盛
2023-12-01

很神奇,宛宛转转我居然跑去帮忙解决PHP的项目了....一个基于国内的THINKPHP + 项目的二次开发项目。

Laravel 不知道那个版本开始,框架的作者认为php本身session机制太过于.. 唔. 所以自己写了一套session的处理机制,于是乎,项目组果断掉坑。

不做太多介绍,在读此文前,读者需要了解cas登出的标准流程。

1. 路由:这里使用的cas服务端是基于APACHE的CAS项目,版本为4.2.7,PHP注册到CAS的地址需要支持POST请求,因为CAS会使用POST登出请求。

2.权限:注册到CAS的地址需要没有权限限制。

3.处理流程:

a. 配置在登录CAS成功时候的回调函数,该回调函数将把票据保存在session

    	cas()->setPostAuthenticateCallback(function ($ticket){
    		//获取用户票据,并保存到缓存,对应到session;
            session()->put('casticket',$ticket);
            session()->save();
    	},array());

b.配置在接收到CAS登出请求时的回调函数,该函数将从CACHE中获取登出请求传过来的票据对应的sessionid,然后根据sessionid将用户踢掉。

踢用户要做三件事:把用户表里的remember_token置空;把laravel 的session重制;把casphp使用的session重置。

其中把phpcas使用的session重置只需要配置phpcas的 CAS_CONTROL_SESSIONS为true使其自动删除session即可。

cas()->setSingleSignoutCallback(function ($ticket){
    		$sessionId=Cache::pull('ticket:'.$ticket);
    		if($sessionId==null||$sessionId==""){
    			return;
    		}
    		$remember_token=Cache::pull('remember_token'.$sessionId);
    		if ($remember_token!=null&&$remember_token!=""){
    		    DB::update("UPDATE users set remember_token=null where remember_token=:t",['t'=>$remember_token]);
    		}
    		
    		Session::setId($sessionId);
			Session::start();
			
			$this->guard()->logout();

			Session::invalidate();
			
        	//本地登出
        	Auth::logout();
        	
			
    	},array());

c.在注册cas的地址写上如下方法,手动通知cas做登出处理

   	if (isset($_REQUEST['logoutRequest'])) {
    		$_POST['logoutRequest']=$_REQUEST['logoutRequest'];
    		cas()->handleLogoutRequests(false);
    		\Log::debug("退出登录");
    		exit;
    	}

d.在登录成功,并调用Laravel 需要进行的操作,主要目的就是做casticket和session的对应关系

            //CAS已登录,AUTH未登录,获取用户未登录前的casticket,
            $ticket=session()->get('casticket');
            
            Auth::login($user, true);

            Cache::forever('ticket:'. $ticket, session()->getId());
            //把用户的remember_token扔进session 登出的时候删除相关数据
            $user = User::where('casid', cas()->user())->first();
            Cache::forever('remember_token'.session()->getId(), $user->remember_token);

以下是增加单点登出前后代码的对比:

前:

<?php

namespace Zhiyi\Plus\Http\Controllers\Auth;

use Carbon\Carbon;
use DB;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Jenssegers\Agent\Agent;
use Zhiyi\Plus\Http\Controllers\Controller;
use Zhiyi\Plus\Models\Role;
use Zhiyi\Plus\Models\User;
use function Zhiyi\Component\ZhiyiPlus\PlusComponentPc\restful;
use function Zhiyi\Plus\username;


class CasController extends Controller
{
    //...
    use AuthenticatesUsers {
        login as authenticatesUsersLogin;
    }

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware('cas.auth')->except(['logout']);
    }

    public function login(Request $request, Agent $agent)

    {
        if (cas()->isAuthenticated()) {
            //获取cas用户信息
            //...
            //根据casid查询用户
            $user = User::where('casid', cas()->user())->first();

            if ($user == null && !empty($casAttributes['email'])) {
                //根据email查询用户
                $user = User::where('email', $casAttributes['email'])->first();
            }
            if ($user == null && !empty($casAttributes['phone'])) {
                //根phone查询用户
                $user = User::where('phone', $casAttributes['phone'])->first();
            }

            date_default_timezone_set("PRC");
            //没有这个用户 要做注册用户处理
            if ($user == null) {
                $attributes['created_at'] = time();
                $attributes['updated_at'] = time();
                $user = new User($attributes);
                $user->save();
                //添加权限信息
                $this->addRes($user, $casAttributes);
            } else {
                //更新User信息
                $this->updateUserInfoWhenChange($user, $attributes);
                //更新权限信息
                $this->updateRes($user, $casAttributes);
            }
            //回调地址
            $redirect = $request->input("redirect");
            if ($redirect == null && isset($_SESSION['redirect'])) {
                $redirect = $_SESSION['redirect'];
                unset($_SESSION['redirect']);
            }
            $referer = $redirect == null ? '/' : $redirect;
            //登录
            $token = $request->session()->get('token');
            $jwt = app(\Tymon\JWTAuth\JWT::class);
            if (!$token || !$jwt->setToken($token)->check()) {
                $token = $jwt->fromUser($user);
                $request->session()->put('token', $token);
                $request->session()->save();
            }
            $config = config('http');
            $spa = $config['spa'];
            if ($agent->isMobile() && $spa['open']) {
                //$referer = "/#" . $referer;
                //获取?第一次出现的位置
                $check = strpos($referer, '?');
                //如果存在?
                if($check !== false){
                    //判断?后面有没有参数
                    if(substr($referer, $check+1) == ''){
                        $referer .= "token=" . $token;
                    }else{
                        $referer .= "&token=" . $token;
                    }
                }else{
                    $referer .= "?token=" . $token;
                }
            }
            Auth::login($user, true);
            //根据权限点进行跳转
            if (strstr($referer, 'auth/login') != false) {
                //登出账号密码再次验证
                Auth::logout();
                return redirect()->route('adminlogin');
            }
            return redirect()->route('redirect', ['target' => $referer]);
        }
        //未认证,本地登出,跳转认证
        Auth::logout();
        return cas()->authenticate();
    }


    private function addRes($user, $casAtt)
    {

        //...
    }

    private function updateRes($user, $casAtt)
    {   //  移除用户的所有角色...
        $user->roles()->detach();
        $this->addRes($user, $casAtt);
    }

    private function doCertification($user, $casRoles, $casAtt)
    {
        //...
    }

    private function updateUserInfoWhenChange($user, $attributes)
    {
        //...
        //本地用户信息和Cas信息差别更新
        $oddattributes = $user->getAttributes();
        $result = array_diff_assoc($attributes, $oddattributes);
        if ($result != null) {
            $attributes['updated_at'] = time();
            $user->update($attributes);
        }
    }


    public function logout(Request $request)
    {
        $this->guard()->logout();

        $request->session()->invalidate();

        //本地登出
        Auth::logout();
        //Cas登出
        cas()->logout();
    }


    /**
     * Get the login username to be used by the controller.
     *
     * @return string
     * @author Seven Du <shiweidu@outlook.com>
     */
    protected function username(): string
    {
        return username(
            cas()->user()
        );
    }

    /**
     * Get the post register / login redirect path.
     *
     * @return string
     * @author Seven Du <shiweidu@outlook.com>
     */
    protected function redirectTo(): string
    {
        return '/feeds';
    }

    /**
     * Get the guard to be used during authentication.
     *
     * @return \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected function guard()
    {
        return Auth::guard();
    }
}

后:

<?php

namespace Zhiyi\Plus\Http\Controllers\Auth;

use Carbon\Carbon;
use DB;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Jenssegers\Agent\Agent;
use Zhiyi\Plus\Http\Controllers\Controller;
use Zhiyi\Plus\Models\Role;
use Zhiyi\Plus\Models\User;
use function Zhiyi\Component\ZhiyiPlus\PlusComponentPc\restful;
use function Zhiyi\Plus\username;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Session;


class CasController extends Controller
{
   //...
    use AuthenticatesUsers {
        login as authenticatesUsersLogin;
    }

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        //$this->middleware('cas.auth')->except(['logout']);
    }

    public function login(Request $request, Agent $agent)

    {
    	cas()->setPostAuthenticateCallback(function ($ticket){
    		//获取用户票据,并保存到缓存,对应到session;
            session()->put('casticket',$ticket);
            session()->save();
    	},array());
    	
    	cas()->setSingleSignoutCallback(function ($ticket){
    		$sessionId=Cache::pull('ticket:'.$ticket);
    		if($sessionId==null||$sessionId==""){
    			return;
    		}
    		$remember_token=Cache::pull('remember_token'.$sessionId);
    		if ($remember_token!=null&&$remember_token!=""){
    		    DB::update("UPDATE users set remember_token=null where remember_token=:t",['t'=>$remember_token]);
    		}
    		
    		Session::setId($sessionId);
			Session::start();
			

			Session::invalidate();
			
        	//本地登出
        	Auth::logout();
        	
			
    	},array());
    	
    	
    	if (isset($_REQUEST['logoutRequest'])) {
    		$_POST['logoutRequest']=$_REQUEST['logoutRequest'];
    		cas()->handleLogoutRequests(false);
    		\Log::debug("退出登录");
    		exit;
    	}
    	
    	
        if (cas()->isAuthenticated()) {
            
            //获取cas用户信息
            $domain = config('app.account_url');
            //...

            //根据casid查询用户
            $user = User::where('casid', cas()->user())->first();

            if ($user == null && !empty($casAttributes['email'])) {
                //根据email查询用户
                $user = User::where('email', $casAttributes['email'])->first();
            }
            if ($user == null && !empty($casAttributes['phone'])) {
                //根phone查询用户
                $user = User::where('phone', $casAttributes['phone'])->first();
            }

            date_default_timezone_set("PRC");
            //没有这个用户 要做注册用户处理
            if ($user == null) {
                $attributes['created_at'] = time();
                $attributes['updated_at'] = time();
                $user = new User($attributes);
                $user->save();
                //添加权限信息
                $this->addRes($user, $casAttributes);
            } else {
                //更新User信息
                $this->updateUserInfoWhenChange($user, $attributes);
                //更新权限信息
                $this->updateRes($user, $casAttributes);
            }
            //回调地址
            $redirect = $request->input("redirect");
            if ($redirect == null && isset($_SESSION['redirect'])) {
                $redirect = $_SESSION['redirect'];
                unset($_SESSION['redirect']);
            }
            $referer = $redirect == null ? '/' : $redirect;
            //登录
            $token = $request->session()->get('token');
            $jwt = app(\Tymon\JWTAuth\JWT::class);
            if (!$token || !$jwt->setToken($token)->check()) {
                $token = $jwt->fromUser($user);
                $request->session()->put('token', $token);
                $request->session()->save();
            }
            $config = config('http');
            $spa = $config['spa'];
            if ($agent->isMobile() && $spa['open']) {
                //$referer = "/#" . $referer;
                //获取?第一次出现的位置
                $check = strpos($referer, '?');
                //如果存在?
                if($check !== false){
                    //判断?后面有没有参数
                    if(substr($referer, $check+1) == ''){
                        $referer .= "token=" . $token;
                    }else{
                        $referer .= "&token=" . $token;
                    }
                }else{
                    $referer .= "?token=" . $token;
                }
            }
            
            //CAS已登录,AUTH未登录,获取用户未登录前的casticket,
            $ticket=session()->get('casticket');
            
            Auth::login($user, true);

            Cache::forever('ticket:'. $ticket, session()->getId());
            //把用户的remember_token扔进session 登出的时候删除相关数据
            $user = User::where('casid', cas()->user())->first();
            Cache::forever('remember_token'.session()->getId(), $user->remember_token);
                
                
            //根据权限点进行跳转
            if (strstr($referer, 'auth/login') != false) {
                //登出账号密码再次验证
                Auth::logout();
                return redirect()->route('adminlogin');
            }
            return redirect()->route('redirect', ['target' => $referer]);
        }
        //未认证,本地登出,跳转认证
        Auth::logout();
        return cas()->authenticate();
    }


    private function addRes($user, $casAtt)
    {
        //...
    }

    private function updateRes($user, $casAtt)
    {   //  移除用户的所有角色...
        $user->roles()->detach();
        $this->addRes($user, $casAtt);
    }

    private function doCertification($user, $casRoles, $casAtt)
    {
        //判断账户类型
       //...
    }

    private function updateUserInfoWhenChange($user, $attributes)
    {
        //本地用户信息和Cas信息差别更新
        $oddattributes = $user->getAttributes();
        $result = array_diff_assoc($attributes, $oddattributes);
        if ($result != null) {
            $attributes['updated_at'] = time();
            $user->update($attributes);
        }
    }


    public function logout(Request $request)
    {
        $this->guard()->logout();

        $request->session()->invalidate();

        //本地登出
        Auth::logout();
        //Cas登出
        cas()->logout();
    }


    /**
     * Get the login username to be used by the controller.
     *
     * @return string
     * @author Seven Du <shiweidu@outlook.com>
     */
    protected function username(): string
    {
        return username(
            cas()->user()
        );
    }

    /**
     * Get the post register / login redirect path.
     *
     * @return string
     * @author Seven Du <shiweidu@outlook.com>
     */
    protected function redirectTo(): string
    {
        return '/feeds';
    }

    /**
     * Get the guard to be used during authentication.
     *
     * @return \Illuminate\Contracts\Auth\StatefulGuard
     */
    protected function guard()
    {
        return Auth::guard();
    }
}

 类似资料: