流程大览
在lumen中使用auth认证
- 注册AuthServiceProvider服务提供中,框架已经注册好了,只需要解开
bootstrap/app.php
中AuthServiceProvider
的调用取消代码注释; - 支持ORM,用于获取认证用户,解开
$app->withEloquent();
的调用注释,如果需要通过Auth::User
的方式获取认证用户,需要解开$app->withFacades()
的调用; - 支持路由中间件,把
bootstrap/app.php
里对$app->routeMiddleware()
的调用 「取消代码注释」,$app->routeMiddleware([
「取消代码注释;
'auth' => App\Http\Middleware\Authenticate::class,
]); - 还是在
bootstrap/app.php
添加自定义配置文件role.php
定义$app->configure('role');
,用于路由权限规则定义。
流程说明
登录流程:用户登录 —> 登录验证成功—>生成用户token并保存在数据库 ;
除登录外的其他访问:在AuthServiceProvider
服务提供中验证token的有效性与时效性,验证通过返回认证用户信息,在auth中间件中获取当前访问URI,通过当前认证用户的信息从数据库获取当前用户所拥有的权限,通过权限列表(具体的权限和路由具有对应关系)与当前路由验证用户是否拥有访问权限。
数据表设计
- 用户表:
Schema::create('staff_users', function (Blueprint $table) {
$table->increments('id');
$table->string('name',30)->comment('用户名称');
$table->string('alias',30)->nullable()->comment('用户别名');
$table->char('pwd',32)->comment('密码');
$table->enum('status',[0,1])->default(1)->comment('状态 0 禁用 1 启用');
$table->string('phone',11)->comment('手机');
$table->json('extend')->nullable()->comment('用户扩展信息,如登录token等');
$table->softDeletes();
$table->timestamps();
$table->index('status');
});
- 权限表:
Schema::create('staff_roles', function (Blueprint $table) {
$table->increments('id');
$table->string('rule_name',20)->comment('权限名称');
$table->string('rule',20)->comment('具体权限');
$table->enum('status',[1,0])->comment('状态 0 禁用 1,启用');
$table->softDeletes();
$table->timestamps();
$table->index('status');
$table->unique('rule');
});
*其中rule字段就是记录具体的访问路由,比如一个获取订单列表的URI是order/list
就记录成rule.list
‘;
- 权限小组表
Schema::create('staff_group', function (Blueprint $table) {
$table->increments('id');
$table->string('name',30)->comment('组名');
$table->string('desc','200')->comment('简单描述');
$table->enum('status',[0,1])->defaule(1)->comment('状态,0 禁用 1 启用');
$table->softDeletes();
$table->timestamps();
});
该表用于权限分组
- 小组权限对应表
Schema::create('staff_group_rules', function (Blueprint $table) {
$table->increments('id');
$table->integer('rule_id')->unsigned();
$table->integer('group_id')->unsigned();
$table->timestamps();
$table->foreign('rule_id')->references('id')->on('staff_roles')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('group_id')->references('id')->on('staff_group')->onDelete('cascade')->onUpdate('cascade');
});
该表用于记录规则与规则组对应关系
- 用户小组表
Schema::create('staff_users_group', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->integer('group_id')->unsigned();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('staff_users')->onDelete('cascade')->onUpdate('cascade');
$table->foreign('group_id')->references('id')->on('staff_group')->onDelete('cascade')->onUpdate('cascade');
});
该表用于记录用户所属权限小组
代码实现:
- 登录:
public function login(Request $request)
{
$account = $request->input('account', '');
$pwd = $request->input('password', '');
if (!$account || !$pwd) {
return Code::_620();
}
//注入服务类获取单个用户信息
$info = $this->accountService->getInfoByName($account);
if (!$info || $info->password != Assistants::getPwd($pwd)) {
//自定义的json返回方式
return Code::_620();
}
//自定义的服务容器
$helper = app('helper');
//产生token的自定义函数
$pro = createToken($info->id);
//将token按时间加密
$token = $helper->getToken($pro);
/**
以下是其他业务,代码到这里登录流程就差不多了,产生了token,更新数据库的原token,如果没有存入token。
**/
$seller = $info->seller()->where('status', SellerAssistants::ACTIVE_STATUS)->get()->toArray();
if(!$seller){
return Code::_500('该账号已被停用');
}
$auth = env('AUTH');
$seller = array_map(function ($item) use ($auth) {
if ($item['is_master']) {
$item['permissions'] = $auth;
}
return ['store_id' => $item['seller_id'], 'auth' => $item['permissions']];
}, $seller);
$seller_ids = array_column($seller, 'store_id');
$store = $this->accountService->getStore($seller_ids);
$tempSeller = [];
foreach ($seller as $val) {
$tempSeller[$val['store_id']] = $val;
}
$returnData = ['token' => $token, 'name' => $info->name, 'store' => []];
foreach ($store as $val) {
if (isset($tempSeller[$val['id']])) {
$temp = $tempSeller[$val['id']];
$temp['store_name'] = $val['name'];
$returnData['store'][] = $temp;
}
}
//更新token
$extend = json_decode($info->extend, true);
$extend['token'] = strtoupper($pro);
if (count($store) == 1) {
$extend['lastStore'] = $store[0]['id'];
}
$info->extend = json_encode($extend, true);
if (!$info->save()) {
return Code::_620();
}
return Code::Y($returnData);
}
//以下为
- 生成时间加密的token:
//这里是对第一步的补充说明,分别是生产token,按时间加密,解密token的相关函数
//生产token
function createToken($uid = 0)
{
$time = uniqid($uid);
$rand = random_int(0, 1000);
return md5(md5($time) . md5($rand));
}
//时间加密
function getToken($token='',$str=''){
if(!$token){
$token = strtoupper(env('ACCESS_KEY','MYNAMEDIAMONDS@A!KILL^&!MING@YIQ'));
}
$str = $str?$str:'MINGYI';
$l=strlen($str);
$l=$l>8?8:$l;
$len = 0 - $l;
$time=substr(date('Ymd'),$len);
$b = $time;
while($time !==''){
$char = $time[0];
$token = substr_replace($token,$str[0],$char,0);
$time =substr_replace($time,'',0,1);
$str =substr_replace($str,'',0,1);
}
if(strlen($l)<2){
$l='A'.$l;
}
return strtoupper($l.$token.$b);
}
//解密获取原生token
function getProToken($token=''){
if(!$token){
return $token;
}
$l = substr($token,0,2);
if(intval($l) <1){
$l = substr($l,-1);
}
$len= 0 - $l;
if($len >= 0){
return $token;
}
$token = substr($token,2);
$time = substr($token,$len);
$now = date('Ymd');
$pre = substr_replace($now,$time,$len,$l);
$str=[];
while ($time !==''){
$index= strlen($time) - 1;
$s = $time[$index];
array_unshift($str,$token[$s]);
$token = substr_replace($token,'',$s,1);
$time = substr_replace($time,'',$index,1);
}
$token =substr_replace($token,'',$len);
return ['token'=>$token,'time'=>$pre,'str'=>join('',$str)];
}
- AuthServiceProvider服务提供者token验证
/** AuthServiceProvider服务提供者位置: app/Providers/AuthServiceProvider.php
**/
//用户信息我是存在这个模型中的,默认是use的User
use App\Models\Assistants;
.........
public function boot()
{
// Here you may define how you wish users to be authenticated for your Lumen
// application. The callback which receives the incoming request instance
// should return either a User instance or null. You're free to obtain
// the User instance via an API token or any other method necessary.
$this->app['auth']->viaRequest('api', function ($request) {
if(env('AUTH_DEBUG',false)){ //调试模式不验证
return Assistants::first();
}
//从消息头获取传入的token
$token = $request->headers->get('token');
if(!$token){
die("token不能为空!");
}
//解密原声token,获取token,时间,过期时间
$sign = app('helper')->getProToken($token);
$date = date("Ymd");
$timeout = env('TOKEN_TIMEOUT',0);
//验证token是否过期
if ((!$timeout || ($date - (int)$sign['time']) < $timeout) && $sign['token']) {
//token验证通过返回当前认证用户
return Assistants::where('extend->token',$sign['token'])->first();
}
return null;
});
}
- auth中间件路由权限检测
/**
auth中间件的位置:app/Http/Middleware/Authenticate.php
**/
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
if ($this->auth->guard($guard)->guest()) {
return Code::_401();
}
//获取认证用户信息$request->user()
$userExtend = json_decode($request->user()->extend, true);
if (!isset($userExtend['lastStore'])) {
return Code::_401();
}
/** 获取权限begin
以下代码根据设计的数据库,结合当前用户信息获取用户拥有的权限(或所在权限组拥有的权限,权限可以理解成拥有哪些路由)
**/
$sellerUser = SellerAssistants::where([['seller_id', $userExtend['lastStore']], ['assistant_id', $request->user()->id]])->first();
if (!$sellerUser->is_master) {
$permissions = explode(',', $sellerUser->permissions);
$commonRole = config('role.common');
$role = config('role.auth');
$userRole = array_flip($permissions);
$vaild = array_merge(array_flatten(array_intersect_key($role, $userRole)), $commonRole);
//获取权限 end
//获取当前URI
$path = $request->path();
//获取以“/”开始后面的路由,例如:http://www.xxx.com/order/list 就获取order.list
$method = str_replace('/', '.', substr($path, 0, strpos($path, '/')));
//判断当前处理过后的路由是否在所拥有的权限中
if (!in_array($method, $vaild)) {
return Code::_402();
}
}
//验证通过
return $next($request);
}