注册鉴权以及购物车系统相关知识点
**
对称加密和非对称加密:
**
对称加密使用的是同一个密钥进行加密解密,适用于数据比较长的时候
非对称加密使用的公钥和私钥进行加密和解密,适用于数据比较少的
主要分为:注册,登录,鉴权判断是否登录。在这个项目中我们用到的是Hs256加密算法,是对称性的,只有一个密钥,如果我们无法对密钥完全保密,相比之下,Rsa非对称加密算法安全性更好一些。
单点登录就是在一个地方登录,其他服务可以共享此登录信息
实现过程:我们实现用户登陆时考虑了使用无状态登录还是有状态登录,为了提高身份认证效率和提升后台服务的延展性,所以我们采用了无状态登录,它可以把用户的信息封装成一个token保存给用户,用户只需要登陆时携带这个token即可,这样我们的服务器不仅可以不需要保存用户的数据,节省了资源,而且我们的服务也可以不用每次在登录请求来的时候都去请求一个服务判断登陆着的身份,提高了我们系统的延展性。
这里采用的是jwt无状态登录。jwt是json风格的轻量级授权和身份验证的规范,可以实现无状态的分布式的web授权。
登录模块功能详细介绍:
无状态登录的流程:
1、当客户端第一次请求服务时,服务端会对用户信息进行认证
2、认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
3、以后每次请求,客户端都会携带认证的token进行访问
4、服务端对token进行解密,判断是否有效
jwt包含三部分:
头部(里面是token的类型和声明签名算法,我们使用的签名算法是rsa非对称加密)
载荷分为标准载荷和自定义载荷(有效数据,一般分为标准载荷(jwtid、过期时间、签发人等)和自定义载荷(存放用户信息,注意这里不要存放敏感数据,因为这里存放的是base64加密,可解密)
签名(整个数据的认证信息,一般根据头部和载荷再加上服务的密钥通过加密算法形成)用于验证整个数据的完整性和可靠性。
token是在jwt的头部+载荷+签名中的secretKey的基础上采用算法生成
注册:SMS短信,RabbitMq消息队列,Redis缓存,BcriptPasswordEncoding加密算法。首先我们的用户通过操作前端页面向后台发送请求携带者自己信息,我们拿着用户名和手机号先判断此用户是否已经存在,如果两个有一个已经存在,就返回给前台说用户已存在,如果不存在,就从redis中拿出之前保存的验证码,和传过来的验证码进行匹配,匹配正确后,调用Spring提供BcriptPasswordEncoding加密算法来生成salt盐,为了确保盐的安全性,我们使用uuid生成一个随机的密钥配置到Bcirpt,拿到盐之后就对密码进行加密存入到数据库中,到这一步证明用户注册成功。
如果想要验证密码:需要拿到用户加密前后的密码,推断出salt,再拿着salt对未加密的密码进行加密,最后和已经加密的密码进行比对
登录分为授权和鉴权
其中授权流程:(登录流程)
1、用户登录携带用户名和密码到授权中心(ly-auth)
2、授权中心携带用户名和密码到用户中心(ly-user)查询用户
3、查询如果正确,生成jwt凭证
4、把jwt写入用户cookie
登录话术:用户发送请求携带者用户名和密码到后台服务中,服务会先去数据库查找对应的用户,拿到用户存在数据库的密码,再使用EcriptPasswordEncoding加密算法来推断出对应的盐,再使用salt加密未加密的密码,和数据库的密码进行对比,如果正确就意味着是本用户。之后jwt会根据用户的信息生成jwt存放到cookie中返回给用户,用户登录成功。
鉴权流程:
1、用户请求微服务功能,携带jwt
2、微服务验证jwt是否有效
3、微服务判断校验结果,成功或失败
4、失败则返回401
5、成功则处理业务并返回
鉴权话术:我们需要配置一个拦截器以及相关的拦截路径和放行路径,用户发送请求服务时直接获取用户cookie中的jwt。若jwt为空,未登录,若jwt不为空则验证jwt,获取用户信息。其中涉及到服务端申请秘钥的过程:其他服务申请密钥,给每个服务都设置一个身份信息,服务想要获得密钥,就携带着自己的身份信息去请求存放密钥的服务,存放密钥服务拿到该请求服务的信息就去数据库进行比对,如果正确就把密钥返回给其他服务。
登录超时控制
方法一:在颁发给用户的jwt中设置超时时间,每次校验jwt顺便校验时间
方法二:在服务端记录用户登录超时时间,即便jwt有效,服务器端过时依然算登录失效(这种方案虽然给服务器端带来了压力,但是过期时间由服务器端记录,控制用户的的登录过时、续期、登出都很方便)
设置超时:发现我们并没有对用户的jwt设置超时,这对jwt的安全性是有一定的影响的,所以我们考虑需要给jwt设置超时,有两种设置方法,
**第一种是颁发给用户的jwt中设置超时时间,每次校验jwt顺便校验时间
第二种是在服务端记录用户登录超时时间,即便jwt有效,服务器端过时依然算登录失效(这种方案虽然给服务器端带来了压力,但是过期时间由服务器端记录,控制用户的的登录过时、续期、
登出都很方便)。
所以我们采用了第二种需要登陆时配置超时时间
用户登录时记录jwt,并设置超时时间,把jwt中的jti信息记录在redis中作为值,并设置超时时间,用户id作为id,之后再把jwt返回给用户。在鉴权的时候就需要取出用户jwt中携带的jti,并且拿着用户id取出redis中存储的jti,如果jti没有的话,就证明用户已经登陆超时,如果存在就两个进行比较。
更新超时时间:当我们的用户在浏览页面或者付款的时候的因为超时时间结束而爆出了用户未登录,那对我们用户的体验感实际差是极差的,所以我们需要做到,当用户一直访问,redis的jti就不会失效消失,需要不断地更新超时时间,如果在超时时间内用户一直没有访问,就让jti消失,综上所述,我们只需要让我们的用户在访问服务时,直接拦截用户,根据用户id刷新存在于redis的超时时间即可,又因为我们的用户不管访问那个服务都需要经过我们的网关,所以我们在网关需要配置一个过滤器去拦截所有的请求,如果是已经登陆的用户就利用redis的expire命令去刷新用户超时时间然后放行,如果没有登录则直接放行交给之后的业务逻辑去处理。这样就可以保证我们的用户一直浏览页面不会出现超时的情况。
使用jwt作为登录凭证,如何解决token注销问题:
jwt的缺陷在于一旦签订无法修改,但是可以采用:
用户登录后生成jwt,其中包含用户身份
以用户id为key,把jwt的id存入redis,只有在redis中id存在的jwt才是有效的jwt
并且给redis设置有效期,有效期到时自己删除
退出登录时把id从redis中删除即可
解决登录超时后的登录续签问题
判断登录是否超时的标准是redis,而不是JWT,因此每次用户访问网关,我们都会刷新redis的数据有效期,保证登录状态不断
如何解决异地登陆或跨设备登录
一是不允许多端登录,如果账户在第二个设备登录,自然会将redis中的jwt覆盖,之前的登录就无效
二是允许多端登录,在存入redis时,我们将用户id设备id一起作为 key,jti作为value,用一个用户,多设备登陆,就会有多个jti
页面如何获取用户信息
1前端发送请求到服务端,查询登陆用户信息
2拦截器拦截请求,验证并解析jwt,得到用户信息
3将用户信息存入上下文之后,放行请求
4controller处理请求,从上下文获取用户后,返回给前端
上下文如何存储当前用户的信息
为了解决线程并发问题,我们使用threadLocal存储数据,threadLocal会得到当前对象,然后获取threadlocalMap对象,接着把数据存储在threadlocalmap对象中,因此每个线程的用户都在线程自己的threadlocalmap对象中,互不干扰。
面对一个新的需求:在服务端随时将一个用户踢下线。
JWT是无状态登录的,而用户踢出、单点控制这都是有状态的特征,违背了无状态登录的特性,无法满足。但是,如果一定要实现,我们需要在服务端记录用户状态,这样就破坏了无状态登录的特性,但也可以实现
ThreadLocal实现流程:ThreadLocal作用与一个线程中,里面是ThreadLocalMap,每个用户都有自己的存储空间,不会相互干扰,我们在用户访问时,会配置一个拦截器,拦截用户信息,并把用户的信息存放在我们自定义的用户上下文中,到需要用的时候就调用方法去取出用户的信息即可。
购物车的设计主要是分为未登录和已登录的购物车的管理,主要功能有查询购物车,修改购物车的数量,删除和添加购物车中的商品,以及合并登陆前后的购物车
未登录前的购物我们设计的是要存放在用户本地的浏览器中,这样不仅节省了服务器的资源,同时也方便了后面商品的合并。之后我们考虑了要不要存放在cookie里面,因为购物车的商品信息可能会很多,数据量太大,cookie 可能会因为数据量过大的原因在向服务器发送请求的时候影响性能,所以我们选择了web本地存储的一种方式-localStorage,localStorage和sessionStorage区别在于一个可以永久有效,一个是作用与一个session的,用户得到购物车保存的时间是一个未知的事情,所以我们就采用了localStorage把我们用户的购物车的商品保存到了用户本地的浏览器。
登陆后的购物车我们采用的是redis缓存数据库,因为用户的购物车数据增删操作较多,而且安全性并不需要多高,也不怎么需要事务的支持,所以我们就没有采用mysql进行存储。购物车中的商品存储到redis中,存储的形式是双层map,第一层的key是用户id,值中的key是商品的id,key是商品具体的参数。添加购物车前肯定是要先判断这个用户是否登录的,用户如果登录再去访问其他服务都会携带者cookie,cookie里面存放了我们生成的jwt,如果jwt不存在就证明未登录,那他添加的购物车都是存放在了localStorage中,如果登录,购物车数据就会存放在redis中,我们会先根据商品id和用户id去redis中查询这个商品是否存在,如果存在就在已有数量基础上加上用户新添加的数量,如果不存在就将其存入redis中。我们在查询购物车时,也会对用户的状态进行一个判断,如果未登录就展示出web本地存储得数据,如果已经登陆就会判断本地存储中是否有数据,有的话就会把数据传入后台进行一个合并商品的业务处理,之后把合并完成用户全部的商品展示给用户,展示之前把本地的商品给删除掉。
jwt和springSecurity的对比:
jwt的优点:
无需在服务器端存储用户数据,减轻服务端压力
轻量级,json风格,比较简单
跨语言
有利于水平扩展
缺点:
token一旦签发,无法修改
无法更新token有效期,用户登录状态刷新难以实现
无法销毁一个token,服务端不能对用户状态进行绝对控制
不包含权限控制
springsecurity:
优点:
用户信息保存在服务端,服务端可以对其 用户状态进行控制
基于spring,无缝整合,修改登录逻辑,其实就是添加过滤器
整合权限管理
缺点:
限定了语言
实现复杂,基于一连串的过滤器链
需要在服务端保存用户信息,增加服务端压力
依赖于tomcat的httpSession,如果是分布式项目,session不共享,登录失效,需要借助springsession,实现共享session效果(利用redis的tomcat的session)
Cookie安全问题
jwt中的cookie泄露是否会泄露用户隐私:
不会,因为jwt中只保存用户id这样的非敏感字段,不会保存用户名密码之类的信息
如何解决token被串改问题:
token中的数据可以被篡改,但是签名无法被修改,否则服务器端认证不会通过,因此篡改的token是无法通过微服务校验的
如何防止token的伪造:
token中带有签名认证,而签名需要秘钥加密生成,只要秘钥不泄露就不可能有人伪造token,而其他秘钥生成的token是不被公钥认可的。此外我们服务内部有秘钥管理机制,其它外部请求是无法获取我们的秘钥的。
用户的cookie被禁用怎么办?
- cookie一般情况下,是不会被禁用,因为普通人根本不知道是什么是cookie,一般不用管,为了友好,我们可以给用户一个提示:你的cookie已经被禁用了,请启用cookie。
如何完成权限校验的?
- 首先我们有权限管理的服务,管理用户的各种权限,及可访问路径等
- 在网关中利用过滤器,拦截一切请求,在过滤器中,解析和验证jwt,获取用户身份,查询用户权限,判断用户身份可以访问当前路径
跨域登录问题
单点登录,顾名思义:在分布式服务中,用户只需要在一处登录,即可在各个受信任的服务器之间,共享登录状态,称为单点登录。
任何登录都离不开cookie,如果cookie无法使用或共享,就会导致登录凭证无法共享,导致登录状态无法共享。例如因为跨域名的多个服务,其cookie不可共享,导致登录失效。
因此实现单点登录有多种方式,其区别就在于是否能解决跨域登录
- 同域名单点登录
- 分布式服务共享二级域名,二级以上域名不同,此时cookie可以共享。解决思路:
- JWT无状态登录
- 共享Session
- 分布式服务共享二级域名,二级以上域名不同,此时cookie可以共享。解决思路:
- 跨域单点登录
- 服务二级域名就不同,导致cookie无法共享,解决办法:
- OAuth协议实现单点登录:Oauth2.0协议,成熟的框架:CAS、SpringCloudOAuth
- 服务二级域名就不同,导致cookie无法共享,解决办法:
接口文档是基于什么生成的:
答:是基于swagger2和Apizza生成接口文档的
SOA是什么
SOA是中文面向服务编程,是一种思想,一种方法论,一种分布式的服务架构。
用途:SOA解决多服务凌乱问题,SOA架构解决数据服务的复杂程度,同时SOA又有一个名字,叫做服务治理。
SOA的优点:
1、降低用户成本,用户不需要关心各服务之间是什么语言的、不需要知道如果调用他们,只要通过统一标准找数据总线就可以了。
2、程序之间关系服务简单
3、识别哪些程序有问题(挂掉)
缺点:提示了系统的复杂程度,性能有相应影响
springboot和springcloud的区别:
springboot是基于springMVC的无配置文件的(基于java)全注解+内置tomcat的springboot框架,main函数启动,约定优于配置,开箱即用,整合了第三方框架原理:maven继承依赖
spring boot是基于个人单体微服务进行开发的,
但是springcloud确实基于全局微服务协调治理框架,它将spring boot开发的单个个体微服务整合并管理,为各个微服务之间提供配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、精选决策、分布式会话等集成服务,在spring boot的基础上构建,用于快速构建分布式系统的通用模式的系统。spring cloud的组件丰富轻量级组件适用于各种环境,约定优于配置,灵活,隐藏了各种组建的复杂性。但是实际开发中大部分我们会选择基于spring boot进行开发,因为不大的项目基于springcloud开会只会增加开发成本,所以在前期我们都会使用spring boot,更有效率,快捷。