这次团队开发,我们采用PHP的CI框架来为安卓端提供后台接口,其中有个问题就是,如何实现用户认证?
所谓用户认证(Authentication),就是让用户登录,并且在接下来的一段时间内让用户访问网站时可以使用其账户,而不需要再次登录的机制。如果不使用这机制,用户的每个操作都需要进行频繁的登录。
那么应该如何进行用户认证呢?解决方案是使用token来认证,什么是token
,token可以理解为临时令牌,有了这个令牌,就相当于给了用户不用账号密码来操作,而使用token来操作的权限,那么如何生成这个令牌并且进行用户认证呢?
我这边学到了2种方法:
- 使用
SESSION
- 使用
json web token
当然应该还有其他操作,这里选择这两种方法来当作博客记录
使用SESSION
token的生成可以使用user_name+salt进行md5加密,加密是为了生成一串每个用户都不一样的token,同时也为了防止其他人伪造token,当用户登录后,便生成token,并且设置过期时间,这样可以使用户在一定时间内不需要再次登录来进行用户操作,生成token后,可以有两种方法实现用户认证。
1 在SESSION中做个映射,比如
$token = md5($user_name.'saltsaltsalt');
$_SESSION[$token] = $uid;
这样的操作,即时其他人伪造了token,我们只需要用用户的用户名进行加密,就可以判断是否被伪造了。一旦被伪造,如果对方不知道我们的salt,就无法生成相同的token。同时做映射可以记录该token的uid,以便进行其他操作。
2 将token存入数据库
将token存入数据库,但是这种方法会导致多余的数据库操作,我就想能不能有更好的方法来实现。从而学到了jwt。
使用json web token
首先,jwt
是什么呢?,可以看看这篇博客
JSON Web Token - 在Web应用间安全地传递信息,
博客中说:
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名。
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。例如
{
"alg": "HS256",
"typ": "JWT"
}
对其进行Base64编码,之后的字符串就成了JWT的Header。
载荷(Payload)
第二部分是包含声明的有效载荷。声明是关于实体(通常是用户)和附加元数据的声明。例如
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
还有一些是jwt标准的定义字段(非强制),如 iss (issuer), exp (expiration time), sub (subject), aud (audience), and others.
签名(Signature)
将header,payload生成的字符串用HS256算法进行加密。在加密的时候,我们提供一个密钥(secret)就可以得到我们加密后的内容,这就是我们要的token。
那么。jwt是如何实现token通信的呢?可以参考这篇博客。
八幅漫画理解使用JSON Web Token设计单点登录系统
简单来讲,即:用户登录成功后,生成json web token返回给请求端,这里的token的payload设置uid
和exp
(过期时间),此后用户的所有操作,只要带jwt即可,请求时服务器先进行token验证,由于token的生成需要secret,如果第三方伪造token,我们只需要将请求的jwt解码,将header+payload+secret进行加密,必然会生成与请求的jwt不同的token,所以token的验证是安全的。如果设置了过期时间,我们还可以直接检测token的有效期。token验证成功后,我们就可以获取payload信息,比如uid等,进行用户的其他操作。但是有人可能会问,如果token被盗了怎么办? token可以在http的header中携带,也可以直接post传递,如果token被盗了,那么用户名/密码也完全可能被盗的,因此采用https加密通信就非常必要了。
最后,和SESSION这种方式比起来,jwt有什么优点呢?
- 减轻服务端负担:比起使用session来保存cookie,JWT自身包含了所有信息,通过解密即可验证
Session方式存储用户id的最大弊病在于要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些KV数据库和一系列缓存机制来实现Session的存储。而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
- jwt中的payload中携带的信息,非常方便服务器使用。
- 还有是解决前端跨域问题等等
但是,使用jwt让服务器有了一些计算压力(例如加密、编码和解码)等。
这次实现认证,使用jwt的另外一个原因是,想要尝试新技术。
同时,还有一个技术想要记录,就是关于CI框架使用核心控制器类扩展,来实现用户的所有操作,都需要进行token验证。
在CI的application/core
目录下,新建一个MY_Controller.php文件
class MY_Controller extends CI_Controller
{
protected $sno;
function __construct()
{
# code...
parent::__construct();
try
{
$jwt = $this->input->post('jwt',true);
if($jwt == NULL)
{
throw new Exception('jwt is null');
}else{
$this->load->library('JWT');
$key = "salt:let's encrypt";
$objJWT = new JWT();
$decoded = $objJWT->decode($jwt, $key, array('HS256'));
if($decoded == false)
{
throw new Exception('jwt已经失效,请重新登录');
}else
{
$decoded_array = (array) $decoded;
$this->sno = $decoded_array['sno'];
}
}
}
catch(Exception $e)
{
echo_failure(1,$e->getMessage());
// return;
exit();
}
}
}
只要子类都继承这个父类,就可以实现进行子类操作都需要进行token的验证。
最后,想要说说这几天冲刺阶段的感受。两个字,纠结。
非常纠结。 纠结原因在于使用不熟悉的实现方式,会不会让整个团队项目后端进度更加难以跟进?
纠结了几点:
- 我要不要使用jwt,还是直接使用之前熟悉的将token存入数据库?
- 在腾讯云备案的域名无法定向到阿里云的服务器,要不要重新备案?不备案的话接口就只能直接用ip访问了。备案的话又太麻烦...
- 要不要学习使用RESTful标准来设计API?使用的话又得重新学习。
结果事实是,我在Linux 就上面花了巨多的时间,因为只是学了Linux的皮毛,很多东西知道要这样做,但是就是没有自己去真正操作过,结果是遇到了各种麻烦。。比如ci框架配置nginx的路由重定向我就配了好久。以及Linux的各种bug,比如在windows上面好好的代码,在Linux上面就是跑不动....
还有就是学到了如何模拟登录教务处:
- PHP的curl模拟HTTP请求
- PHP简单的正则表达式使用
- HTTP请求的header相关知识
最后纠结到此为止。
睡觉了....