承接上一篇,whale系统开篇,聊聊用户认证
写在前面
上次老猫和大家说过想要开发一个系统,从简单的权限开始做起,有的网友表示还是挺支持的,但是有的网友嗤之以鼻,认为太简单了,不过也没事,简单归简单,主要的还是个人技术的一个整合和实战。
没错,系统的名称老猫也已经定义好了叫做whale,whale是鲸鱼的意思。其实没有别的意思,也是老猫拍脑袋想出来的,可能是受到docker图标的影响。另外的真要说有点啥么,那就是老猫希望这个系统是成长的,是演变的,能从简单的小鱼系统成长为遨游海洋的鲸鱼,当然猫也喜欢吃鱼,扯远了......本篇起,老猫正式开始养鱼。
用户认证
开篇我们当然从用户的登录认证开始说起,关于用户认证,老猫不晓得大家对此是否熟悉,有些同学可能有所研究,这里老猫还是得详细和大家聊聊。说起用户认证,大家比较有所耳闻的应该就是session认证以及token认证。
传统的Session认证
我们说http请求是无状态的。这句话什么意思?所谓无状态,就是指用户向服务器端发起多个请求,服务器并不会知道多次请求都是来源于同一个用户,这就是无状态。
那么如何让服务器知道我们来自是哪一个用户的请求呢?所以我们只能在服务器中存储一份用户的登录信息,这份登录信息会在响应的时候传给浏览器,并且告诉其保存为cookie,以便下次请求的时候发送给我们的应用,这样我们的应用就识别了请求来源于哪个用户,这也就是传统的session认证。简单地画了一下原理图,大概就长下面这样。
当然,传统的单体架构中的存储是session内存的存储,随着用户的增多,服务器开销增加,为了扩展,逐渐将session信息可以存入到redis中间件中。这也是扩展的一种方式。
聊聊这种方式的短板。
- 每次认证用户发起请求时,服务器需要去创建一个记录来存储信息。当越来越多的用户发请求时,内存的开销也会不断增加。 虽然说中间件可以缓解这个问题。
- 在服务端的内存中使用Seesion存储登录信息,可扩展性会比较差。
- 开放平台的商业理念开始走向主流,显然cookie以及session都无法很好的处理授权管理。
- 跨域资源共享问题,当我们需要让数据跨多台移动设备上使用时,跨域资源的共享会是一个让人头疼的问题。在使用Ajax抓取另一个域的资源,就可以会出现禁止请求的情况。
相对的,我们再来看看相关的token认证。
Token认证
我们直接说个大白话,什么叫做token认证,说白了其实就是暗号。 在一些数据传输之前,要先进行暗号的核对,不同的暗号被授权不同的数据操作。
说得稍微专业一些应该是这样, Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个Token前来请求数据即可,无需再次带上用户名和密码。 大致的流程如下,
聊聊这种方式相对于传统session的优势。
无状态、可扩展:在客户端存储的 token 是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载均衡服务器 能够将用户的请求传递到任何一台服务器上,因为服务器与用户信息没有关联。相反在传统方式中,我们必须将请求发送到一台存储了该用户 session 的服务器上(称为Session亲和性),因此当用户量大时,可能会造成 一些拥堵。使用 token 完美解决了此问题。
关于安全性:请求中发送 token 而不是 cookie,这能够防止 CSRF(跨站请求伪造) 攻击。即使在客户端使用 cookie 存储 token,cookie 也仅仅是一个存储机制而不是用于认证。另外,由于没有 session,让我们少我们不必再进行基于 session 的操作。 Token 是有时效的,一段时间之后用户需要重新验证。我们也不一定需要等到token自动失效,token有撤回的操作,通过 token revocataion可以使一个特定的 token 或是一组有相同认证的 token 无效。
可扩展性:使用 Tokens 能够与其它应用共享权限。例如,能将一个博客帐号和自己的QQ号关联起来。当通过一个 第三方平台登录QQ时,我们可以将一个博客发到QQ平台中。
使用 token,可以给第三方应用程序提供自定义的权限限制。当用户想让一个第三方应用程序访问它们的数据时,我们可以通过建立自己的API,给出具有特殊权限的tokens。
多平台与跨域:当我们的应用和服务不断扩大的时候,我们可能需要通过多种不同平台或其他应用来接入我们的服务。可以让我们的API只提供数据,我们也可以从CDN提供服务(Having our API just serve data, we can also make the design choice to serve assets from a CDN.)。 在为我们的应用程序做了如下简单的配置之后,就可以消除 CORS 带来的问题。只要用户有一个通过了验证的token,数据和资源就能够在任何域上被请求到。
Access-Control-Allow-Origin: *
基于标准:有几种不同方式来创建 token。最常用的标准就是 JSON Web Tokens。很多语言都支持它。
所以综合对比了一下,token认证的优势也就相当明显了,所以老猫后面的认证也将会基于token来实现。
关于JWT
其实jwt就是 Json web token 的简写,一般组成的形式是这样xxx.yyy.zzz。很明显,其实是分为三部分。第一部分称它为头部(header),第二部分称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).
头部(header):头部一般会有两部分信息,第一部分声明类型,这里一般类型就是jwt,第二部分就是加密算法,一般直接使用 HMAC SHA256,那么构造基本就是如下所示。
{
'typ': 'JWT',
'alg': 'HS256'
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分xxx
载荷(payload):主要是对实体(一般可以是用户信息)和其他数据进行声明,关于声明主要有三种类型:registered,public和private。
- Registered claims: 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
- Public claims : 可以随意定义。
- Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。
举个官网的例子如下:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
对payload进行Base64编码就得到JWT的第二部分,但是要注意的是,不要在JWT的payload或header中放置敏感信息,除非它们是加密的。
签名(Signature):为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然后对它们签名即可。
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。
放在一起进行加密之后即为如下:
在 jwt.io Debugger 去decode操作之后我想大家就一目了然了,当然这个图来自官网。
以上关于JWT的相关介绍,说明:关于以下介绍均来自于官网,大家如果觉得老猫翻译的有问题的话,可以自行去官网看一下,官网地址:https://jwt.io/introduction/
JWTUtil的封装实战
如果上述大家觉得还是比较模糊,老猫封装了一个JWTUtil的工具类,大家可以参考着去理解,具体代码如下:
/**
* @author kdaddy@163.com
* @date 2021/4/13 23:06
*/
public class JwtUtil {
// 生成签名是所使用的秘钥
private final String base64EncodedSecretKey; // 生成签名的时候所使用的加密算法
private final SignatureAlgorithm signatureAlgorithm; public JwtUtil(String secretKey, SignatureAlgorithm signatureAlgorithm) {
this.base64EncodedSecretKey = Base64.encodeBase64String(secretKey.getBytes());
this.signatureAlgorithm = signatureAlgorithm;
}
/**
* 生成 JWT Token 字符串
* @param iss 签发人名称
* @param ttlMillis jwt 过期时间
* @param claims 额外添加到荷部分的信息。
* 例如可以添加用户名、用户ID、用户(加密前的)密码等信息
*/
public String encode(String iss, long ttlMillis, Map<String, Object> claims) {
if (claims == null) {
claims = new HashMap<>();
} // 签发时间(iat):荷载部分的标准字段之一
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis); // 下面就是在为payload添加各种标准声明和私有声明了
JwtBuilder builder = Jwts.builder()
// 荷载部分的非标准字段/附加字段,一般写在标准的字段之前。
.setClaims(claims)
// JWT ID(jti):荷载部分的标准字段之一,JWT 的唯一性标识,虽不强求,但尽量确保其唯一性。
.setId(UUID.randomUUID().toString())
// 签发时间(iat):荷载部分的标准字段之一,代表这个 JWT 的生成时间。
.setIssuedAt(now)
// 签发人(iss):荷载部分的标准字段之一,代表这个 JWT 的所有者。通常是 username、userid 这样具有用户代表性的内容。
.setSubject(iss)
// 设置生成签名的算法和秘钥
.signWith(signatureAlgorithm, base64EncodedSecretKey); if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
// 过期时间(exp):荷载部分的标准字段之一,代表这个 JWT 的有效期。
builder.setExpiration(exp);
}
return builder.compact();
} /**
* JWT Token 由 头部 荷载部 和 签名部 三部分组成。签名部分是由加密算法生成,无法反向解密。
* 而 头部 和 荷载部分是由 Base64 编码算法生成,是可以反向反编码回原样的。
* 这也是为什么不要在 JWT Token 中放敏感数据的原因。
* @param jwtToken 加密后的token
* @return claims 返回荷载部分的键值对
*/
public Claims decode(String jwtToken) { // 得到 DefaultJwtParser
return Jwts.parser()
// 设置签名的秘钥
.setSigningKey(base64EncodedSecretKey)
// 设置需要解析的 jwt
.parseClaimsJws(jwtToken)
.getBody();
} /**
* 校验 token
* 在这里可以使用官方的校验,或,
* 自定义校验规则,例如在 token 中携带密码,进行加密处理后和数据库中的加密密码比较。
* @param jwtToken 被校验的 jwt Token
*/
public boolean isVerify(String jwtToken) {
Algorithm algorithm = null; switch (signatureAlgorithm) {
case HS256:
algorithm = Algorithm.HMAC256(Base64.decodeBase64(base64EncodedSecretKey));
break;
default:
throw new RuntimeException("不支持该算法");
} JWTVerifier verifier = JWT.require(algorithm).build();
verifier.verify(jwtToken); // 校验不通过会抛出异常
return true;
} public static void main(String[] args) {
JwtUtil util = new JwtUtil("tom", SignatureAlgorithm.HS256); Map<String, Object> map = new HashMap<>();
map.put("username", "tom");
map.put("password", "123456");
map.put("age", 20);
//测试加密生成token
String jwtToken = util.encode("tom", 30000, map);
System.out.println(jwtToken); //测试token合法性
util.isVerify(jwtToken);
System.out.println("合法"); //测试拿到token之后解密
util.decode(jwtToken).entrySet().forEach((entry) -> {
System.out.println(entry.getKey() + ": " + entry.getValue());
});
}
}上述代码我们用main函数进行测试,输出结果如下:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ0b20iLCJwYXNzd29yZCI6IjEyMzQ1NiIsImV4cCI6MTYxODkyNjc3NywiaWF0IjoxNjE4OTI2NzQ3LCJhZ2UiOjIwLCJqdGkiOiIyYzkxY2I2OS1lYWEzLTRlMmYtOGViNC1iNDUzM2MxNTE1MjkiLCJ1c2VybmFtZSI6InRvbSJ9.Ws4Vw9Ll60uaFbTGBpZJh-LTMI052l4Zzx81jqKq3qY
合法
sub: tom
password: 123456
exp: 1618926777
iat: 1618926747
age: 20
jti: 2c91cb69-eaa3-4e2f-8eb4-b4533c151529
username: tom
写在最后
以上就是相关jwt的介绍以及传统session实现的对比,接下来,老猫会向大家演示如何通过JWT+shiro实现whale系统的一个登录鉴权功能。关于前端系统的框架,老猫决定使用的是一套网上比较流行的开源框架vue-admin-beautiful,功能相当齐全,也给大家推荐一下这位大神的作品,github地址为: https://github.com/chuzhixin/vue-admin-beautiful,大家可以自行下载试着运行一下。感谢大家持续关注老猫。
承接上一篇,whale系统开篇,聊聊用户认证的更多相关文章
- Django【第9篇】:Django之用户认证auth模块
用户认证--------------auth模块 一.auth模块 from django.contrib import auth 1 .authenticate() :验证用户输入的用户名和密码 ...
- 【转】Android系统开篇
版权声明:本站所有博文内容均为原创,转载请务必注明作者与原文链接,且不得篡改原文内容.另外,未经授权文章不得用于任何商业目的. 一.引言 Android系统非常庞大.错综复杂,其底层是采用Linux作 ...
- Android 12(S) 图形显示系统 - 简单聊聊 SurfaceView 与 BufferQueue的关联(十三)
必读: Android 12(S) 图形显示系统 - 开篇 一.前言 前面的文章中,讲解的内容基本都是从我们提供的一个 native demo Android 12(S) 图形显示系统 - 示例应用( ...
- .NET Core采用的全新配置系统[5]: 聊聊默认支持的各种配置源[内存变量,环境变量和命令行参数]
较之传统通过App.config和Web.config这两个XML文件承载的配置系统,.NET Core采用的这个全新的配置模型的最大一个优势就是针对多种不同配置源的支持.我们可以将内存变量.命令行参 ...
- 在VMware Workstation上安装CentOS6.5系统步
在VMware Workstation上安装CentOS6.5系统步骤 听语音 | 浏览:147 | 更新:2016-07-28 15:45 | 标签:安装 虚拟机 CENTOS 1 2 3 4 5 ...
- PHPCMS 实现上一篇、下一篇
方法一:直接调用phpcms系统的函数 <div class="info"> <span>上一篇:<a href="{$previous_p ...
- java 全自动生成Excel之ExcelUtil篇(上一篇的升级版 [针对实体类对象的遍历赋值])
看了上一篇随笔之后可以对本篇有更好的了解! 使用的poi的jar包依然是上一篇的poi-3.17.jar.... import pojo.UserPojo(上一篇里有,这里就不粘贴了!) 不废话了,直 ...
- 手牵手,使用uni-app从零开发一款视频小程序 (系列上 准备工作篇)
系列文章 手牵手,使用uni-app从零开发一款视频小程序 (系列上 准备工作篇) 手牵手,使用uni-app从零开发一款视频小程序 (系列下 开发实战篇) 前言 好久不见,很久没更新博客了,前段时间 ...
- Redis 日志篇:系统高可用的杀手锏
特立独行是对的,融入圈子也是对的,重点是要想清楚自己向往怎样的生活,为此愿意付出怎样的代价. 我们通常将 Redis 作为缓存使用,提高读取响应性能,一旦 Redis 宕机,内存中的数据全部丢失,假如 ...
随机推荐
- linux下安装mysql8.0.x步骤
1.下载mysql mysql官网:https://dev.mysql.com/downloads/mysql/ 将下载的mysql上传打linux 2.解压并重命名 [root@rsyncClien ...
- vue_webpack
1.生成项目工程描述文件 npm init 2.安装webpack开发依赖 (本地安装):npm install -D 3.(webpack4.0版本以上安装webpack cli) npm inst ...
- eclipse中lombok注解不生效
现象:eclipse中在对象上使用lombok的@Data,引用get方法时,没有set.get方法. 解决办法: 1.在lombok官网(https://www.projectlombok.org/ ...
- linux之docker 安装 mysql
首先进入docker : 命令:systemctl start docker 查詢一下docker的状态: 命令:docker images 现在开始安装mysql了,第一步拉取镜像 命令:doc ...
- Loki日志系统
一.概述 背景 Loki的第一个稳定版本于2019年11月19日发布,是 Grafana Labs 团队最新的开源项目,是一个水平可扩展,高可用性,多租户的日志聚合系统. Grafana 对 Loki ...
- 看完我的笔记不懂也会懂----AngulaJS
目录 Angular.js学习笔记 ng-app(指令) ng-model ng-init angular之表达式 双向数据绑定 数据流向的总结 作用域对象 控制器对象 依赖对象与依赖注入 命令式与声 ...
- Nginx解析漏洞复现以及哥斯拉连接Webshell实践
Nginx解析漏洞复现以及哥斯拉连接Webshell实践 目录 1. 环境 2. 过程 2.1 vulhub镜像拉取 2.2 漏洞利用 2.3 webshell上传 2.4 哥斯拉Webshell连接 ...
- 剑指 Offer 44. 数字序列中某一位的数字 + 找规律 + 数位
剑指 Offer 44. 数字序列中某一位的数字 Offer_44 题目描述 题解分析 java代码 package com.walegarrett.offer; /** * @Author Wale ...
- 2020年12月-第01阶段-前端基础-HTML常用标签
1. HTML常用标签 首先 HTML和CSS是两种完全不同的语言,我们学的是结构,就只写HTML标签,认识标签就可以了. 不会再给结构标签指定样式了. HTML标签有很多,这里我们学习最为常用的,后 ...
- 【RocketMQ源码分析】深入消息存储(1)
最近在学习RocketMQ相关的东西,在学习之余沉淀几篇笔记. RocketMQ有很多值得关注的设计点,消息发送.消息消费.路由中心NameServer.消息过滤.消息存储.主从同步.事务消息等等. ...