用户登录

186 阅读8分钟

最近启动一个新的项目,需要支持用户登录功能.简单整理一下实现过程.

注册

  1. 用户通过邮箱注册.邮箱作为登录名.
  2. 服务端收到邮箱,验证未注册则创建验证码,服务端创建key-value缓存,key邮箱,value验证码,服务端通过发送邮件验证码,验证邮箱真实.
  3. 用户录入登录邮箱、密码和验证码
  4. 服务端根据邮箱查询缓存,比较验证码.验证码正确.验证密码各式,存储用户信息返回成功

登录

  1. 输入登录名(邮箱)和密码登录系统.
  2. 服务端收到用户名和密码
  3. 验证用户名和密码是合法用户,则在服务端建立登录会话信息.主要就是创建一个key-value结构.key是一个随机字符串.value是登录用户信息,例如:用户唯一标识、用户名称、角色、所属组织、当前登录设备信息等.
  4. 然后将key写入cookie或者返回值返回给前端.
  5. 前端接收到key之后将key缓存到本地(浏览器缓存等).
  6. 登录成功之后调用接口将key放到cookie里或者header里面(cookie也是head的一个参数).
  7. 后端通过拦截器处理验证请求,拦截器收到请求之后找到key.根据key到缓存里面读取用户信息.读取信息之后验证访问权限.验证通过. 以上说明了注册登录的基本流程 实际开发中对于各种情况需要不同的处理方案,针对以上过程增加一些扩展.

扩展

注册

用户可能通过手机号注册,需要对接短信运营商发送短信验证码. 用户通过三方系统注册(微信、微博等),需要对接三方系统,拿到三方系统用户ID.查看三方系统api文档即可.通常需要一个三方跳转过程. 服务端保存用户登录密码.防止密码泄漏,通常会对密码进行加密存储.例如接收到用户注册密码之后使用SHA加密.将密文和混乱数存储到数据库.混乱数也叫盐(通常是一个随机字符串).

用户登录

登录过程的第一步:用户通过手机号登录,对接运营商发送密码;防止暴力攻击需要增加登录用户验证,最简单的就是验证码图片.目前比较流行的是图片拖拽或者接入ai滑动验证等.如果使用用户名密码登录,密码在网络传输中如果是明文可能导致密码泄漏.可以通过非对称密钥对密码加密(RSA等).前端使用公开密钥对密码加密.后端使用私有密钥对密码解密.

登录过程第三步缓存信息通常有一个过期时间,超过时间自动清除.如果有页面操作是否需要刷新过期时间?通常的方案是增加一个key-value结构.通用的叫法是刷新TOKEN.这个存储时间可以长一些.如果用户发现会话信息的key-value没有了,查刷新k-v.如果刷新k-v存在.则删除了再存一个新的刷新k-v(也可以更新刷新k-v的过期时间),然后服务端默认走一次用户登录过程,缓存新的会话k-v.大家可能有疑问,为什么要重新登录过程?直接保存新的会话k-v不行么?答案是肯定的.当然可以直接缓存.但是实际业务场景会比较复杂.例如:用户基本信息可能变更.实际上登录方法通常需要处理很多业务场景.所以会话k-v过期之后使用刷新token更新会话k-v的时候需要执行一个简化的登录过程.具体业务具体分析.后端更新信息之后将新的会话key和刷新key写入cookie或者返回值里面返回给前端.这里注意一下前后端会话信息的传递.通常放到cookie或者header里面.但是有的时候可能需要跨域.由于浏览器限制放到cookie里面可能无法正常传递.

大家可能会问.会话k-v不设置过期时间不行么?用户执行退出的时候删除不就可以了么?大多数情况用户不会执行退出.直接关闭浏览器了.下次打开可以自动登录.如果用户关闭之后不再访问了呢?或者用户清空了所有浏览器缓存数据.那么会话k-v就成为僵尸数据了.永远在缓存里面.还有一种是为了安全考虑.会话时间短,减少攻击的可能性.

再看第四步服务端返回前端key.这里可以做一个优化,如果服务端将用户名或者一些基本信息一起返回前端缓存起来作为后期使用.我们考虑一种极端情况.用户信息都给前端返回,前端缓存,甚至将会话信息也给前端返回.每次访问前端就将用户信息会话信息打包给后端,后端通过会话信息验证用户.这个方案也行,无非就是会话信息存在前端了.服务端存储的信息少了.但是信息再网上传输总有不安全的时候.需要防泄漏、防串改.所以要对信息进行加密和签名.这些问题可以通过jwt协议解决.jwt定义了一个结构体.header、load、签名.load通常需要加密.两个极端的方案就是会话信息存在服务端或者前端.实际开发过程中会取一个折中方案.前端和服务端都存储会话信息.具体业务具体分析.其实jwt提供的最基本功能就是签名防篡改.

登录过程的第七步:过滤器的实现需要知道哪些请求不需要权限验证.例如登录请求、公开展示信息等.通常实现方式:增加一个白名单记录域名.增加java注解过滤.增加注解在实际开发过程中可能遇到一个小问题,java注解切面是通过拦截器实现的.过滤器无法通过注解进行切面.一个实现方案是过滤器通过spring 容器拿到所有bean然后解析所有注解.然后通过上下文得到当前请求的注解.具体方案可以在网上查一下(BeanFactoryUtils).这里不具体写了.

上面的说明中有些名词:会话、token、cookie.会话就是一次连接,连接过程服务端移动端需要存储一些数据确认连接.大家可能总是在网上看到http是无状态的.无状态到底是什么意思呢?你发起一次http请求之后接收到返回值.再次发起请求的时候http不知道第二次请求和第一次请求是不是同一个人或者是不是有依赖关系.为了让第二次请求知道和第一次请求是同一个人.那么两次请求里面需要一个相同的标识.只要两次标识一样就认为同一个人.这个标识可以是一个简单的无意义字符串.也可以是用户登录信息(会话信息).传递的信息就叫token.无意义字符可以叫会话(sessionId).token里面可以放会话信息、会话ID任意能标记登录用户的信息都行,说的具体一点:由于http的协议header和body的传递是key-value结构,sessionId、token、cookie可以看作http的header和body的一个key.value就是存放具体内容.在存储信息的时候一定要考虑安全.你说为了方便把用户密码和用户信息存储到token里行不行.行.但是不安全.一个简单的判断方法就是:如果认为客户端不安全(浏览器)那就对客户端缓存信息加密.如果网络传递不安全,那就对网络传递的信息加密.当然协议使用https.如果服务端不安全那就对服务端存储加密.加密算法就是对称加密和非对称加密这里不细说了.总之安全是十分重要的事情,一定要仔细考虑.在实际开发之前最好上网查查已经成熟的协议.因为成熟的协议一般都经过验证.安全漏洞很小.说两个开源框架:shiro和CAS.学习阶段建议可以从最基本的手写开始.等流程原理都通了.使用框架就知道各个扩展接口和配置信息是做什么的了.

以上是权限验证的一个基本过程.除了鉴权还有授权.授权一个简单实现就是权限之后通过角色配置验证用户权限.这里不细说.