控制器:
class Token
{
public function getToken($code='')
{
//返回的是json的数组,不是返回的字符串
(new TokenGet())->goCheck();
$ut = new UserToken($code);
$token = $ut->get();
return [
'token'=>$token
];
}
//第三方获取令牌
public function getAppToken($ac='',$se='')
{
(new AppTokenGet())->goCheck();
$app = new AppToken();
$token = $app->get($ac,$se);
return [
'token'=>$token
];
}
//验证token是否过期
public function verifyToken($token='')
{
if (!$token){
throw new ParameterException([
'Token不能为空'
]);
}
$valid = TokenService::verifyToken($token);
return [
'isValid'=>$valid
];
}
}
UserToken:
/**
* 微信登录
* 如果担心频繁被恶意调用,请限制ip
* 以及访问频率
*/
class UserToken extends Token
{
protected $code;
protected $wxLoginUrl;
protected $wxAppID;
protected $wxAppSecret;
function __construct($code)
{
$this->code = $code;
$this->wxAppID = config('wx.app_id');
$this->wxAppSecret = config('wx.app_secret');
$this->wxLoginUrl = sprintf(
config('wx.login_url'), $this->wxAppID, $this->wxAppSecret, $this->code);
}
/**
* 登陆
* 思路1:每次调用登录接口都去微信刷新一次session_key,生成新的Token,不删除久的Token
* 思路2:检查Token有没有过期,没有过期则直接返回当前Token
* 思路3:重新去微信刷新session_key并删除当前Token,返回新的Token
*/
public function get()
{
$result = curl_get($this->wxLoginUrl);
// 注意json_decode的第一个参数true
// 这将使字符串被转化为数组而非对象
$wxResult = json_decode($result, true);
if (empty($wxResult)) {
// 为什么以empty判断是否错误,这是根据微信返回
// 规则摸索出来的
// 这种情况通常是由于传入不合法的code
throw new Exception('获取session_key及openID时异常,微信内部错误');
}
else {
// 建议用明确的变量来表示是否成功
// 微信服务器并不会将错误标记为400,无论成功还是失败都标记成200
// 这样非常不好判断,只能使用errcode是否存在来判断
$loginFail = array_key_exists('errcode', $wxResult);
if ($loginFail) {
$this->processLoginError($wxResult);
}
else {
return $this->grantToken($wxResult);
}
}
}
// 判断是否重复获取
private function duplicateFetch(){
//TODO:目前无法简单的判断是否重复获取,还是需要去微信服务器去openid
//TODO: 这有可能导致失效行为
}
// 处理微信登陆异常
// 那些异常应该返回客户端,那些异常不应该返回客户端
// 需要认真思考
private function processLoginError($wxResult)
{
throw new WeChatException(
[
'msg' => $wxResult['msg'],
'errorCode' => $wxResult['errorCode']
]);
}
// 写入缓存
private function saveToCache($wxResult)
{
$key = self::generateToken();
$value = json_encode($wxResult);
$expire_in = config('setting.token_expire_in');
$result = cache($key, $value, $expire_in);
if (!$result){
throw new TokenException([
'msg' => '服务器缓存异常',
'errorCode' => 10005
]);
}
return $key;
}
// 颁发令牌
// 只要调用登陆就颁发新令牌
// 但旧的令牌依然可以使用
// 所以通常令牌的有效时间比较短
// 目前微信的express_in时间是7200秒
// 在不设置刷新令牌(refresh_token)的情况下
// 只能延迟自有token的过期时间超过7200秒(目前还无法确定,在express_in时间到期后
// 还能否进行微信支付
// 没有刷新令牌会有一个问题,就是用户的操作有可能会被突然中断
private function grantToken($wxResult)
{
// 此处生成令牌使用的是TP5自带的令牌
// 如果想要更加安全可以考虑自己生成更复杂的令牌
// 比如使用JWT并加入盐,如果不加入盐有一定的几率伪造令牌
// $token = Request::instance()->token('token', 'md5');
$openid = $wxResult['openid'];
$user = User::getByOpenID($openid);
if (!$user)
// 借助微信的openid作为用户标识
// 但在系统中的相关查询还是使用自己的uid
{
$uid = $this->newUser($openid);
}
else {
$uid = $user->id;
}
$cachedValue = $this->prepareCachedValue($wxResult, $uid);
$token = $this->saveToCache($cachedValue);
return $token;
}
private function prepareCachedValue($wxResult, $uid)
{
$cachedValue = $wxResult;
$cachedValue['uid'] = $uid;
$cachedValue['scope'] = ScopeEnum::User;
// $cachedValue['scope'] = 15;
return $cachedValue;
}
// 创建新用户
private function newUser($openid)
{
// 有可能会有异常,如果没有特别处理
// 这里不需要try——catch
// 全局异常处理会记录日志
// 并且这样的异常属于服务器异常
// 也不应该定义BaseException返回到客户端
$user = User::create(
[
'openid' => $openid
]);
return $user->id;
}
}
Token:
// 生成令牌
public static function generateToken()
{
$randChar = getRandChar(32);
$timestamp = $_SERVER['REQUEST_TIME_FLOAT'];
$tokenSalt = config('secure.token_salt');
return md5($randChar . $timestamp . $tokenSalt);
}
//验证token是否合法或者是否过期
//验证器验证只是token验证的一种方式
//另外一种方式是使用行为拦截token,根本不让非法token
//进入控制器
public static function needPrimaryScope()
{
$scope = self::getCurrentTokenVar('scope');
if ($scope) {
if ($scope >= ScopeEnum::User) {
return true;
}
else{
throw new ForbiddenException();
}
} else {
throw new TokenException();
}
}
// 用户专有权限
public static function needExclusiveScope()
{
$scope = self::getCurrentTokenVar('scope');
if ($scope){
if ($scope == ScopeEnum::User) {
return true;
} else {
throw new ForbiddenException();
}
} else {
throw new TokenException();
}
}
public static function needSuperScope()
{
$scope = self::getCurrentTokenVar('scope');
if ($scope){
if ($scope == ScopeEnum::Super) {
return true;
} else {
throw new ForbiddenException();
}
} else {
throw new TokenException();
}
}
public static function getCurrentTokenVar($key)
{
$token = Request::instance()
->header('token');
$vars = Cache::get($token);
if (!$vars)
{
throw new TokenException();
}
else {
if(!is_array($vars))
{
$vars = json_decode($vars, true);
}
if (array_key_exists($key, $vars)) {
return $vars[$key];
}
else{
throw new Exception('尝试获取的Token变量并不存在');
}
}
}
/**
* 从缓存中获取当前用户指定身份标识
* @param array $keys
* @return array result
* @throws \app\lib\exception\TokenException
*/
public static function getCurrentIdentity($keys)
{
$token = Request::instance()
->header('token');
$identities = Cache::get($token);
//cache 助手函数有bug
// $identities = cache($token);
if (!$identities)
{
throw new TokenException();
}
else
{
$identities = json_decode($identities, true);
$result = [];
foreach ($keys as $key)
{
if (array_key_exists($key, $identities))
{
$result[$key] = $identities[$key];
}
}
return $result;
}
}
/**
* 当需要获取全局UID时,应当调用此方法
*而不应当自己解析UID
*
*/
public static function getCurrentUid()
{
$uid = self::getCurrentTokenVar('uid');
$scope = self::getCurrentTokenVar('scope');
if ($scope == ScopeEnum::Super)
{
// 只有Super权限才可以自己传入uid
// 且必须在get参数中,post不接受任何uid字段
$userID = input('get.uid');
if (!$userID)
{
throw new ParameterException(
[
'msg' => '没有指定需要操作的用户对象'
]);
}
return $userID;
}
else
{
return $uid;
}
}
/**
* 检查操作UID是否合法
* @param $checkedUID
* @return bool
* @throws Exception
* @throws ParameterException
*/
public static function isValidOperate($checkedUID)
{
if(!$checkedUID){
throw new Exception('检查UID时必须传入一个被检查的UID');
}
$currentOperateUID = self::getCurrentUid();
if($currentOperateUID == $checkedUID){
return true;
}
return false;
}
public static function verifyToken($token)
{
$exist = Cache::get($token);
if($exist){
return true;
}
else{
return false;
}
}
}
extra下的secure:
//为了随机生成
return [
'token_salt'=>'HH12TieBs377mJtKr',
'pay_back_url'=>'http://www.zerg.com/v1/pay/notify'
];
微信小程序: