认证授权与JWT
- 1、认证授权概念介绍
- 1.1 什么是认证
- 1.2 什么是授权
- 2、权限数据模型
- 3、RBAC权限模型
- 3.1 介绍
- 3.2 基于角色访问控制
- 3.3 基于资源访问控制
- 4、常见认证方式
- 4.1 Cookie-Session
- 4.2 jwt令牌无状态认证
- 5 常见技术实现
- 6.Jwt介绍
- 6.1 JWT简介
- 6.2.Jwt组成
- 7、JWT使用
- 7.1 工程引入JWT依赖
- 7.2 生成JWT令牌
- 7.3 JWT令牌校验
1、认证授权概念介绍
1.1 什么是认证
在互联网中,我们每天都会使用到各种各样的APP和网站,在使用过程中通常还会遇到需要注册登录的情况,输入你的用户名和密码才能正常使用,也就是说成为这个应用的合法身份才可以访问应用的资源,这个过程就是认证。认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。
当然认证的方式有很多,常见的账号密码登录,手机验证码登录,指纹登录,刷脸登录等等。
简单说: 认证就是让系统知道我们是谁。
1.2 什么是授权
认证是为了保护身份的合法性,授权则是为了更细粒度的对数据进行划分,授权是在认证通过的前提下发生的。控制不同的用户能够访问不同的资源。
授权是用户认证通过后根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。
例如视频网站的VIP用户,可以查看到普通用户看不到的资源信息。
2、权限数据模型
授权过程中,我们需要知道如何对用户访问的资源进行控制,需要了解一些简单的授权数据模型。
授权可以非常简单的理解成谁(Who)对什么资源(What)进行怎么样(How)的操作。
名词 | 含义 | 备注 |
---|---|---|
Who | 主体(Subject) | 一般指用户,也可以是应用程序 |
What | 资源(Resource) | 例如商品信息,订单信息,页面按钮或程序中的接口等信息 |
How | 权限(Permission) | 规定了用户或程序对资源操作的许可。例如普通用户只能查看订单,管理员可修改或删除订单,这是因为普通用户和管理员用户对订单资源的操作权限不一样。 |
主体、资源、权限的关系图:
主体、资源、权限相关的数据模型如下:
A. 主体(用户id、账号、密码、…)
B. 资源(资源id、资源名称、访问地址、…)
C. 权限(权限id、权限标识、权限名称、资源id、…)
D. 主体和权限关系(用户id、权限id、…)
显然,多个主体与资源之间的权限关联存在冗余,可引入角色关联对应的权限信息;
主体、资源、权限的表结构关系:
我们发现权限中包含了一个资源ID,多个权限可指向一个资源,我们是否可以直接在权限信息中把资源信息包含进来呢?当然,这也是很多企业开发中的做法,将权限和资源合并为 权限(权限ID、权限标识、权限名称、资源名称、资源访问地址、…)
3、RBAC权限模型
3.1 介绍
如何实现授权?业界通常基于RBAC模型(Role-Based Access Control -> 基于角色的访问控制)实现授权。 RBAC认为授权实际就是who,what,how三者之间的关系(3W),即who对what进行how的操作。
3.2 基于角色访问控制
RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
根据上图中的判断逻辑,授权代码可表示如下:
if(主体.hasRole("总经理角色标识")){
//查询工资
}else{
//权限不足
}
如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是 总经理或部门经理”,修改代码如下:
if(主体.hasRole("总经理角色标识") || 主体.hasRole("部门经理角色标识")){
//查询工资
}else{
//权限不足
}
根据上边的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。
3.3 基于资源访问控制
RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权。
同样是上面的需求,这时候我们的代码变成了
if(Subject.hasPermission("查询员工工资的权限标识")){
// 查询员工工资
}
优点:
系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修授权代码,系统可扩展性强。
4、常见认证方式
认证(登录)几乎是任何一个系统的标配,web 系统、APP、PC客户端等都需要注册、登录、授权。
4.1 Cookie-Session
早期互联网以 web 为主,客户端是浏览器,所以 Cookie-Session 方式最那时候最常用的方式,直到现在,一些 web 网站依然用这种方式做认证;
认证过程大致如下:
A. 用户输入用户名、密码或者用短信验证码方式登录系统;
B. 服务端验证后,创建一个 Session 记录用户登录信息 ,并且将 SessionID 存到 cookie,响应回浏览器;
C. 下次客户端再发起请求,自动带上 cookie 信息,服务端通过 cookie 获取 Session 信息进行校验;
弊端
- 只能在 web 场景下使用,如果是 APP 中,不能使用 cookie 的情况下就不能用了;
- 即使能在 web 场景下使用,也要考虑跨域问题,因为 cookie 不能跨域;(域名或者ip一致,端口号一致,协议要一致)
- cookie 存在 CSRF(跨站请求伪造)的风险;
- 如果是分布式服务,需要考虑 Session 同步(同步)问题;
- session-cookie机制是有状态的方式(后端保存主题的用户信息-浪费后端服务器内存)
4.2 jwt令牌无状态认证
JSON Web Token(JWT-字符串)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息,同时也不需要客户端每记录一次数据服务端同步记录一次(无状态)。
认证过程:
A. 依然是用户登录系统;
B. 服务端验证,并通过指定的算法生成令牌返回给客户端;
C. 客户端拿到返回的 Token,存储到 local storage/session Storate/Cookie中;
D. 下次客户端再次发起请求,将 Token 附加到 header 中;
E. 服务端获取 header 中的 Token ,通过相同的算法对 Token 进行验证,如果验证结果相同,则说明这个请求是正常的,没有被篡改。这个过程可以完全不涉及到查询 Redis 或其他存储;
优点
A. 使用 json 作为数据传输,有广泛的通用型,并且体积小,便于传输;
B. 不需要在服务器端保存相关信息,节省内存资源的开销;
C. jwt 载荷部分可以存储业务相关的信息(非敏感的),例如用户信息、角色等;
5 常见技术实现
技术 | 概述 |
---|---|
Apache Shiro | Apache旗下的一款安全框架 |
SpringSecurity | Spring家族的一部分, Spring体系中提供的安全框架, 包含认证、授权两个大的部分 |
CAS | CAS是一个单点登录(SSO)服务,开始是由耶鲁大学的一个组织开发,后来归到apereo去管 |
自行实现 | 自行通过业务代码实现(基于filter过滤器或者springmvc拦截器+AOP), 实现繁琐, 代码量大 |
6.Jwt介绍
6.1 JWT简介
JSON Web Token(JWT)是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。该token被设计为紧凑且安全的,特别适用于前后端无状态认证的场景。
6.2.Jwt组成
- 头部(Header)(非敏感)
- 头部用于描述关于该JWT的最基本的信息,例如数据类型以及签名所用的算法等,本质是一个JSON格式对象;
- 举例说明
- {“typ”:“JWT”,“alg”:“HS256”} 解释:在头部指明了签名算法是HS256算法,整个JSON对象被BASE64编码形成JWT头部字符串信息;
- BASE64编码详见:https://tool.oschina.net/encrypt,编码后的字符串:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
- 值得注意的是BASE64不是加密算法,可进行正向编码和反向解码处理;
- 载荷(playload)(非敏感数据)
- 载荷就是存放有效信息的地方,该部分的信息是可以自定义的;
- 载荷payload格式:{“sub”:“1234567890”,“name”:“John Doe”,“admin”:true}
- 载荷相关的JSON对象经过BASE64编码形成JWT第二部分:eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG7CoERvZSIsImFkbWluIjp0cnVlfQ==
- 签证(signature)
- jwt的第三部分是一个签证信息,这个签证信息由三部分组成:签名算法( header (base64后的).payload (base64后的) . secret )
- 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret秘钥组合加密,然后就构成了jwt的第三部分:TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
最后将这三部分用●连接成一个完整的字符串,构成了最终的jwt:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG7CoERvZSIsImFkbWluIjp0cnVlfQ==.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
7、JWT使用
7.1 工程引入JWT依赖
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
7.2 生成JWT令牌
@Test
public void testGenerate(){
String compact = Jwts.builder()
.setId(UUID.randomUUID().toString())//设置唯一标识
.setSubject("JRZS") //设置主题
.claim("name", "nineclock") //自定义信息
.claim("age", 88) //自定义信息
.setExpiration(new Date()) //设置过期时间
.setIssuedAt(new Date()) //令牌签发时间
.signWith(SignatureAlgorithm.HS256, "shen")//签名算法, 秘钥
.compact();
System.out.println(compact);
}
7.3 JWT令牌校验
票据过期异常:io.jsonwebtoken.ExpiredJwtException:
@Test
public void testVerify(){
String jwt = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5MzljNjU4MC0yMTQyLTRlOWEtYjcxOC0yNzlmNzRhODVmNDMiLCJzdWIiOiJOSU5FQ0xPQ0siLCJuYW1lIjoibmluZWNsb2NrIiwiYWdlIjo4OCwiaWF0IjoxNjE3MDMxMjUxfQ.J-4kjEgyn-Gkh0ZuivUCevrzDXt0K9bAyF76rn1BfUs";
Claims claims = Jwts.parser().setSigningKey("shen").parseClaimsJws(jwt).getBody();//获取载荷
System.out.println(claims);
}
当我们对令牌进行任何部分(header , payload , signature)任何部分进行篡改, 都会造成令牌解析失败(io.jsonwebtoken.SignatureException:
) ;