很神奇,宛宛转转我居然跑去帮忙解决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();
}
}