我试图为我的Angular JS样板和用PHP构建的API组建一个用户登录/访问控制的身份验证过程。
我已经阅读了很多关于JWS令牌,oAuth,Sessions,Cookies等各种方法/技术的信息。
似乎有很多选项和方法,其中一些似乎是多余的。
我拼凑的方法如下:
(所有代码都是为了清楚起见而缩写 - 依赖项,支持代码和错误处理已被省略)
AUTHENTICATION:
客户端
1)用户在登录表单中输入用户名/密码凭证并提交数据。
2)包含登录数据的请求将在提交时发送到我的API:
$http.post('https://my.api/login', loginData).then(function(res){ // send request
if(res.data.response.status > 0){ // success
$localStorage.session = { // create session object
uid: res.data.session.user_id,
urole: res.data.session.user_role,
token: res.data.session.token
};
} else console.log('login failed'); // failed
});
服务器端
// check that user exists
$user = db_select('SELECT * FROM users WHERE username=?',array($username));
if(count($user)>0){
if(validate_str($pw, $user['password'])) { // validate hashed password
$res['session'] = sessionCreate($user[0]['id']);
}
}
sessionCreate()
基本上创建了DB记录,用于存储生成的令牌,到期时间和用户ID。
$expiry = add2Date(now(),'+2 hours'); // session expires in 2 hours
$token = rando(50); // token is a random alpha-numeric 50 char in length
$query = compileINSERT(array('user_id'=>$user_id,'token'=>$token,'expiry'=>$expiry),'user_session');
$session_id = db_process($query['sql'],$query['val']);
if($session_id>0) return(validateSession($token));
validateSession()
稍后用于验证会话,但此处用于返回新创建的会话。
如上所示(客户端),成功的响应会导致返回的会话保存为$localStorage
中的对象。
访问控制:
客户端
用户登录后,使用服务在resolve
UI-Router
对象中处理访问控制。
访问控制有两个组件:验证会话是否存在且有效,并验证尝试访问的$stateParams
页面是否与用户会话匹配。
.state('base.user', {
url:'/user/:id',
templateUrl: 'views/user.html',
controller: 'userCtrl',
resolve: { // authenticate user and check access control
authenticate: function ($q, $state, $timeout, $localStorage, $stateParams, AuthService){
AuthService.validate($localStorage.session.token).then(function(res){
if(res.data.response.status!==1
|| !AuthService.access($localStorage.session.uid, $stateParams.id)) {
$timeout(function() { $state.go('base.notfound') });
return $q.reject();
} else return $q.resolve();
});
}
}
})
AuthService.validate()
和AuthService.access()
都在服务中定义:
.service('AuthService', function($http){
this.validate = function(token){
// send token to API
$http.get('http://my.api/validate/'+token);
}
this.access = function(uid, stateparam){
// make sure session user_id matches URI slug being accessed
if(Number(uid)===Number(stateparam)) return true;
else return false;
}
})
服务器端
在后端,令牌用于验证会话:
$token = $this->req['slug'][0];
if(!empty($token)){
$result = validateSession($token);
if($result['status']>0) $res['session'] = $result['session'];
else if($result['status']<0) $_response['error'][] = 'session expired';
else if($result['status']===0) $_response['error'][] = 'session does not exist';
} else $_response['error'][] = 'token not set';
最后,validateSession()
如下:
function validateSession($token){
$response = array('status'=>0,'session'=>array());
// fetch the session using token
$session = db_select('SELECT * FROM user_session WHERE token=? ORDER BY expiry',array($token))[0];
if(!empty($session)) {
$time_to_expiry = datetimeDiff($session['expiry'],now());
if($time_to_expiry['invert']==0) $response['status'] = -1; // session exists but is expired
else $response['status'] = 1; // session valid
$response['session'] = $session;
}
return($response); // return session or an error
}
概要
总而言之,我所写的认证和验证如下:
身份验证:
1)用户登录
2)登录数据发送到API
3)验证用户名/密码对
4)创建包含用户ID,到期和唯一生成令牌的数据库记录
5)会话数据在响应JSON中返回并存储在本地存储
中访问控制:
1)用户尝试访问受限制的页面
2)来自本地存储会话数据的令牌被发送到API
3)API检查DB中是否存在带令牌的会话且未过期
4)API返回有效/无效状态
另外,
5)根据受限页面的URI slug检查来自本地存储会话数据的用户ID(以防止任何经过身份验证的用户访问任何其他经过身份验证的用户的内容)
我的问题(最后)
我意识到上述所有内容都有点天真,只是看起来过于简单,所以这里有一些问题:
1)我在URL中而不是在请求标头中发送令牌,这样做有什么缺点(如果有的话)。为什么要在请求标头中发送令牌?
2)我没有使用cookie,但是所有的部分似乎都在那里用于功能认证/访问控制协议。在上面的例子中,cookie适合哪里?
3)有哪些漏洞可用于绕过上述系统?
4)在上述系统中,我没有采取哪些最佳做法?
5)任何输入&amp;批评将不胜感激。