三种用户登录方式

1. 基于传统Session方式 (HttpSession+Spring MVC HandlerInterceptor+本地缓存)

  1. 用户在前台填写表单,通过HTTP POST的方式将账号密码传送给服务器,服务器验证密码和数据库中的密码是否匹配
  2. 如果匹配,创建1个HttpSession对象,将用户信息存入session,Session会将sessionId以Cookie的形式传送给客户端,之后客户端的每次请求都会在Cookie中传送sessionId

基于session的方式,对于所有的方法,系统都需要验证用户登录状态。如果用户未登录,不允许调用后台@Controller中方法。针对这个需求,我们需要在执行每个方法前验证用户是否登录,最笨的方法是:@Controller中的每个方法在被调用前,都执行验证登录逻辑,但我们肯定不会使用这种方法。还有2种方法可选

  1. 使用Aop,将验证登录逻辑封装成切面,再使用Aop注入进来,让每个方法执行前都先调用验证逻辑。需要为每个方法设置1个Aop拦截规则
  2. 使用Spring MVC的拦截器HandlerInterceptor,拦截所有需要验证的Http请求,如果验证成功,才可以继续操作;否则直接返回未登录

我们选择的是Spring MVC拦截器这种方案,因为Aop本质上还是在每个方法执行前,调用验证方法,只不过Aop为我们简化了代码开发
[Spring MVC处理请求流程]

基于传统Session的方式有1个缺点
Session占用Web容器的内存空间,如果Session过多,会使Web容器内存溢出

Session的默认生存时间是30min,如果登录用户的个数太多,Session会占用大量的内存空间,且必须等到30min后超时才会被删除。针对这种情况,我们不使用setAttribute()将用户信息放入Session对象中,而是放在本地缓存。我们没有采用手动编写ConcurrentHashMap的方式来实现缓存,而是使用Google Guava类库中的缓存类LoadingCache

  1. ConcurrentHashMap只能手动移除缓存,而LoadingCache可以在缓存大小超过指定大小或缓存超时时自动被删除
  2. ConcurrentHashMap和LoadingCache都是线程安全的,但是LoadingCache能够提供更加细粒度的操作,可以设置同时访问缓存的线程个数
  3. LoadingCache提供了1个回调函数,能够在缓存被移除时发送通知

2. 基于Token方式 (Token+Redis+Spring Cloud Zuul 过滤器ZuulFilter)

在单体应用中,所有的业务模块都部署在1个进程中,可以使用Session或缓存来存储用户信息,它们都保存在Tomcat内存中,被所有业务模块共享

在微服务系统中,系统按照功能拆分成不同的应用,每个应用都是单独运行的进程。完成1次业务请求需要访问多个子应用,不可能像传统session模式那样,在Web容器中保存用户信息用于后续请求,因为它们属于不同的进程,部署在不同的物理机上

这里我们有2个需求:

  1. 用户在系统的子应用A进行账户密码验证后,再去访问系统中的其他子应用时,不需要再次验证,类似于单点登录SSO
  2. 和单体应用一样,对所有请求做统一验证,而不是所有方法执行前都执行一次验证逻辑。在传统Seesion模式中,因为应用是单体应用,使用Spring MVC的HandlerInterceptor对所有请求进行拦截,然后做统一的验证。但是在微服务系统中,应用单独部署,无法使用HandlerInterceptor做统一验证,我们使用的是Spring Cloud的组件Zuul

Zuul是Spring Cloud微服务系统的网关,所有请求都需要先经过Zuul,由Zuul进行路由。我们的身份验证功能就是使用Zuul提供的过滤器ZuulFilter实现的,这样所有访问微服务系统的请求,只需要验证1次账户密码

ZuulFilter分为4类
(1)PRE
(2)ROUTING
(3)POST
(4)ERROR
客户端请求经过的顺序是PRE-ROUTING-POST,ROUTING负责路由,将请求路由到微服务系统的对应子系统;PRE在ROUTING路由前生效,主要用来实现身份验证,我们就是采用这种方式;POST用来处理微服务子系统返回的响应,POST执行完会将响应返回给客户端,一般用于为HTTP响应添加额外信息,收集统计信息等;ERROR在发送错误时被触发

于是这种模式下的流程是:

  1. 用户1st访问,cookie中无token,请求被引导到验证系统;根据账户密码进行验证,如果通过验证,生成token,以token为key,用户信息为value存入Redis;将token存入cookie传送给客户端
  2. 当用户再次访问时,发送带有token的cookie,查询Redis,如果存在,验证通过;否则会被引导到登录页面进行验证

3. 基于JWT方式

基于Token+Zuul+Redis的方式已经可以满足微服务系统的验证需求了,但是还可以改进。使用JWT来替代Token和Redis可以做到更加轻量

JWT是Json Web Token,将用户信息加密到Token中,服务器不再保存用户信息,这样我们就可以不再使用Redis保存用户信息,使得系统更加轻量

JWT分为3部分,头部header、载荷数据payload和签证signature,这3部分组成1个token,用.连接
header中设置JWT使用的加密算法
payload存放有效信息,分为标准数据和自定义数据,标准数据包括JWT签发者和接受者、过期时间等,自定义数据一般存放用户信息
signature是签证,使用BASE64算法对header和payload进行加密,使用.连接后,再使用header中设置的加密方式对结果和加盐字符串进行组合加密
base64UrlEncode(header) + "." + base64UrlEncode(payload)+your-256-bit-secret

  1. 生成JWT
    使用jjwt包下Jwts类的builder()获得JwtBuilder对象,通过setClaims()设置JWT的payload,通过setExpiration()设置过期时间,通过signWith()设置signature和加盐加密用的盐,来生成JWT

String token = Jwts.builder().setClaims(claims).setExpiration(expiresDate)
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();

  1. 解析JWT
    使用Jwts类的parser()获得JwtParser对象,设置加盐加密用到的盐,解析JWT
    Jws<Claims> jws = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token);
    String signature = jws.getSignature();
    Map<String, String> header = jws.getHeader();
    Claims claims = jws.getBody();

加盐加密

加密是指使用已经公开的、不可逆的Hash加密算法对密码进行加密,这会出现2个问题:

  1. 对于相同的密码,加密得到的密文是一样的。如果存储密码的数据库泄漏,黑客可以破解具有相同密码的用户
  2. 如果密码设置的比较简单,破解起来也会比较容易

因此,设置1个盐值SALT,加密时,将密码和盐值一起进行Hash,提高了破解难度

注册

  1. 用户在前台填写表单,表单验证通过后,使用MD5对密码进行加盐加密
    之后将注册信息存储到DB,不过记录为未激活状态false
  2. 生成1个UUID与当前注册用户绑定,通过Spring Boot Mail组件异步的将UUID发送到用户邮箱,用于激活。以UUID为key,DB记录id为value作为缓存,放入Guava实现的内存缓存中
  3. 用户点击邮箱中的链接,链接将调用后台对应@Controller方法,这个方法会去缓存中取UUID对应的DB记录id,将该记录设置为已激活状态

登出

默认情况cookie生命周期为浏览器会话时间,浏览器关闭,cookie,这种cookie保存在内存中。如果设置cookie生存时间,cookie保存在硬盘中,即使关闭浏览器,cookie也不会消失

默认情况下,session存活时间为30min,用户手动登出时,使用HttpSession.invalidate()将session销毁

鉴权

目的:

  1. 验证用户是否登录,如果用户知道页面url,在没有登录的情况下,不能请求后台@Controller
  2. 每个用户都有自己的角色,每个角色可以看到的页面,数据范围,前台页面的按钮是不一样的。这方面使用的是联通自己做的权限系统,角色,角色可见的页面,数据范围和按钮以人工的方式配置在DB中,鉴权的时候,根据用户id查询,该用户权限的相关信息

对于所有的前台请求,都需要做鉴权处理,如果将鉴权逻辑写到各个@Controller方法中,所有方法中都存在冗余的鉴权逻辑

为了防止鉴权代码冗余,使用Spring MVC的拦截器,通过自定义HandlerInterceptor的实现类完成鉴权。当HTTP请求到达Spring MVC的DispatcherServlet后,Spring MVC会根据请求中的uri,使用HandlerMapping找到匹配的Handler,返回1个HandlerExecutionChain对象,这个对象包括1个Handler对象和若干个拦截器对象HandlerInterceptor,Spring MVC会先执行拦截器,再执行Handler,因此所有的请求在使用Handler处理前,都会先由HandlerInterceptor处理

HandlerInterceptor接口有3个方法,preHandle()、postHandle()和afterCompletion()
(1)请求到达Handler前,先执行拦截器的前置处理方法preHandle(),若该方法返回false,请求直接返回给客户端,返回true才会向下传递
(2)执行完Handler方法,请求会按调用链的反方向返回,执行拦截器的后置处理方法postHandle()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,548评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,497评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,990评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,618评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,618评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,246评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,819评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,725评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,268评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,356评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,488评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,181评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,862评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,331评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,445评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,897评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,500评论 2 359

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,696评论 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,434评论 1 92
  • 原文链接:https://docs.spring.io/spring-boot/docs/1.4.x/refere...
    pseudo_niaonao阅读 4,709评论 0 9
  • 1. 微服务架构介绍 1.1 什么是微服务架构? 形像一点来说,微服务架构就像搭积木,每个微服务都是一个零件,并使...
    静修佛缘阅读 6,648评论 0 39
  • 昨天晚上下班的时候,我因为要去家附近的一个地方拿点东西,下车后就绕了一些路。 这个季节天黑得很早,当时不算很晚,大...
    半浅阅读 442评论 0 0