Spring Boot整合Spring Security+JWT+OAuth 2.0 实现认证鉴权登录(框架介绍)

简介

Spring Security框架

描述

Spring Security是一个基于Spring框架的安全性框架,可以为Web应用程序提供身份验证(Authentication)、授权(Authorization)、攻击防御等安全功能。Spring Security框架提供了一整套的身份验证、授权、ACL(访问控制列表)等模块和类库,还提供了一系列的安全过滤器、安全标签等,可以方便地实现常见的安全性控制。

RBAC 权限模型

基于角色的权限控制

RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等;

基于资源的权限控制

RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权,比如:用户必须具有查询工资权限才可以查询员工工资信息等;

两种方式的优缺点对比
基于角色的权限控制

优点: 容易配置,只需要把每个角色的权限在配置好,保存在数据库,所有用户在注册的时候就分配好角色,那么所有的用户的权限可以同一配置了,当角色的权限发生变化的时候,只需要将对应的角色的权限配置好就行了。
缺点: 有些用户归属于同一角色,但是因为岗位略有不同,那么需要一些特殊的权限,那么需要子角色,如果一直这样下去,可能有子角色的子角色…可能需要一直创建很多子级角色。概括来说,粒度不够细。
注: 角色相当于用户和权限的之间的一个中间商;

基于资源的权限控制

优点: 权限的控制的粒度比较灵活,可以按照不同资源模块来限制用户的权限,比如客户端用户不能访问系统管理,那么不用管系统管理里面有多少功能API,不用细分地给用户分配每一个API的权限,一律屏蔽不允许,也可以允许访某个资源模块中的限定的一些API;
缺点: 需要批量修改用户的权限的时候就很麻烦。

综合对比还是基于角色的权限控制实现会更便于控制,虽然同一角色会特殊情况会有不同的功能权限,但是只需要创建子角色就可以解决,这样,即使需要改变某些群体的权限就可以修改角色的权限达到批量修改的目的,但是如果纯粹按照基于资源的权限控制方式设计权限控制,那么当有些用户权限需要变动的时候,不能批量修改。

Spring Security的核心组件

名称 描述
Authentication 身份验证组件,负责用户身份认证
Authorization 权限授权组件,负责用户权限的授权管理
Access Control、ACL 访问控制列表组件,负责资源的访问控制和权限的分配控制
Session Management 会话管理组件,负责管理用户的会话,如Session ID管理等
Web Security 使用Spring Security保护Web应用程序的安全组件
Remember-me 记住我功能组件,负责实现自动登录功能
OpenID OpenID功能组件,负责与Open ID提供商的集成

Spring Security的主要特点包含:

可扩展性: Spring Security框架提供了很多可扩展的类和接口,可以通过自定义实现这些接口和类来满足自己的需求。
配置简单: Spring Security框架的配置非常简单,只需配置几个关键的类即可实现基本的安全性控制。
弹性设计: Spring Security框架非常灵活,可以根据实际情况在不同的场景下灵活应对。
多种安全认证方式: Spring Security框架提供了很多种安全认证方式,如表单认证、基本认证、OAuth2等,可以根据实际需求选择合适的认证方式。

大体认证授权流程

官方说明:

Spring Security的认证机制包括AuthenticationManagerAuthenticationProvider两个核心组件。

AuthenticationManager:是一个接口,定义了身份验证的入口方法authenticate(),该方法接受一个Authentication对象作为参数,并返回一个封装了认证信息的Authentication对象。
AuthenticationProvider:是一个接口,定义了身份验证的具体实现,该接口的实现类可以根据不同的身份验证方式(如用户名密码、数字证书等)来实现身份验证的功能。

在Spring Security中,用户请求经过过滤器链,经过身份认证和授权决策来保护资源的安全。
身份认证包括认证请求的处理身份验证的实现

认证请求的处理: 包括UsernamePasswordAuthenticationFilterRememberMeAuthenticationFilter等过滤器的处理。
身份验证的实现: 则是通过AuthenticationManager来实现的。

授权决策由AccessDecisionManagerAccessDecisionVoter两个核心组件来实现。

AccessDecisionManager: 是一个接口,定义了授权决策的入口方法decide(),该方法接受三个参数:Authentication对象(当前用户的认证信息)、Object对象(正在访问的资源)、List对象(访问资源所需的权限列表)。
AccessDecisionVoter: 是一个接口,定义了对当前用户的认证信息、当前请求所需的权限、资源的访问控制列表进行比较的方法,以决定当前用户是否有访问该资源的权限。

注: Spring Security的认证授权原理: 是通过 AuthenticationManagerAuthenticationProvider实现身份认证,通过AccessDecisionManagerAccessDecisionVoter实现授权决策,以保护资源的安全。

具体的执行流程其实是一个过滤链:

1、用户向应用程序发起请求,请求需要经过Spring Security的过滤器链。
2、过滤器链首先会经过UsernamePasswordAuthenticationFilter过滤器,该过滤器判断请求是否是一个认证请求。如果是认证请求,过滤器将获取请求中的用户名和密码,然后使用AuthenticationManager进行身份认证。
3、AuthenticationManager会根据用户名和密码创建一个Authentication对象,并将该对象传递给AuthenticationProvider进行认证。
4、AuthenticationProvider会根据传递过来的Authentication对象进行身份认证,并返回一个认证成功或失败的结果。
5、如果认证成功,UsernamePasswordAuthenticationFilter会将认证信息封装成一个Authentication对象,并将其放入SecurityContextHolder上下文中。
6、用户请求获取资源时,会经过FilterSecurityInterceptor过滤器,该过滤器会根据请求的URL和HTTP方法获取访问控制列表(Access
Control List)。
7、Access Control List会包含访问资源所需要的权限信息,FilterSecurityInterceptor会将Authentication对象和Access
Control List传递给AccessDecisionManager进行授权决策。
8、AccessDecisionManager会调用多个AccessDecisionVoter进行投票,并根据投票结果来决定当前用户是否有访问该资源的权限。如果用户被授权访问资源,应用程序将返回资源的响应结果。

总结就是首先经过认证过滤器实现认证,认证成功的话就会将用户信息存到authentication对象里面放到security上下文去(后续的权限校验需要获取到),这里面是包括权限的,之后再由AccessDecisionManager去根据相关策略进行权限鉴定

JWT框架

描述

JWT全称叫json web token,通过数字签名的⽅式,以json对象为载体,在不同的服务终端之间安全的传输信息。

1、JWT在前后端分离系统,或跨平台系统中,通过JSON形式作为WEB应⽤中的令牌,⽤于在各方之间安全地将信息作为JSON对象传输。在数据传输过程中,还可以完成数据加密、签名等相关处理。
2、前端应⽤在访问后端应⽤时,会携带令牌,后端应⽤在接到令牌后,会验证令牌的合法性。从⽽决定前端应⽤是否能继续访问。
3、JWT还可以系统之间进⾏信息传递,A系统通过令牌对B系统进⾏数据传输,在传输过程中,可以完成数据的加密,B系统拿到数据后,通过签名进⾏验证,从⽽判断信息是否有篡改。
4、JWT就是一个签名验证的框架,负责将用户信息进行编码加密,编码加密后再将加密后的数据返回给浏览器。浏览器下一次请求只需要带上这个令牌即可。服务器端就可以根据这个令牌来验证用户的身份信息。
5、JWT是一种客户端浏览器和服务器之间传递安全信息的一种声明规范

应用

JWT最常⻅的场景就是授权认证,⼀旦⽤户登录,后续每个请求都将包含JWT,系统在每次处理⽤户请求之前,都要先进⾏JWT安全校验,通过之后再进⾏处理。

授权

这是JWT最常⻅⽅案,⼀旦⽤户登录,每个后续请求将包括JWT,从⽽允许⽤户访问该令牌允许的路由,服务和资源。

信息交换

JWT是在各⽅之间安全地传输信息的好⽅法,可以验证传输内容是否遭到篡改。

JWT和session的区别:

1、jwt可以隐藏数据、安全系数更高(安全性更好)
2、jwt更适合分布式/微服务系统
3、session是将数据存储在应用服务器里的,当用户量较大的时候,会造成服务资源的浪费。
4、session是存在单台服务器上的,如果涉及到分布式应用,那么因为每一台服务器上存储的session不一致导致用户状态丢失等问题。
5、jwt所产生的验证字符串(token)是不会存储在服务器的,而是存储到客户端。

专业术语

1、每个⽤户经过我们的应⽤认证之后,将认证信息保存在session中,由于session服务器中对象,随着认证⽤户的增多,服务器内存开销会明显增⼤。
2、⽤户认证之后,服务端使⽤session保存认证信息,那么要取到认证信息,只能访问同⼀台服务器,才能拿到授权的资源。这样在分布式应⽤上,就需要实现session共享机制,不⽅便集群应⽤。
3,JWT认证基于令牌,该令牌存储在客户端。但认证由服务器端进⾏,解决服务器内存占⽤问题。当⽤户提交⽤户名和密码时,在服务器端认证通过后,会⽣成token令牌。然后将令牌响应给客户端浏览器。
4,客户端浏览器会在本地存储令牌。在客户端再次请求服务器接⼝时,每次都会携带JWT,在服务器验证通过后,再继续访问服务器资源。

优势

1、简洁明了,可以通过URL、POST参数或Http header发送,因为数据量⼩,传输速度快。用户只需要关心密钥安全性问题。
2、⾃包含,jwt的负载可以传递一些⽤户基本的信息,不需要在服务器端保存会话信息(不需要再将数据保存在服务器里),不占服务器内存,也避免了多次查询数据库,特别适⽤于分布式微服务。
3、因为(jwt)token是以json加密的形式保存在客户端的,所以JWT是可以跨语⾔使⽤(如:python、go),原则上任何WEB形式都⽀持。很多编程语言都可以使用jwt进行授权。

JWT认证流程

1、首先由客户端浏览器发送登录请求,服务器收到登录请求后进行用户身份的验证。(账户、密码的验证),验证失败,将失败信息回执给浏览器(用户名/密码错误),验证通过,会进行jwt加密授权。

2、JWT可以将用户信息存放在其内部结构里(负载中(payload)),将header(头部)和负载进行Base64编号拼接后进行签名(加密)签名时需要提供自定的一串密钥(密钥是jwt里重要的一环,密钥决定用户数据是否能够被篡改)。最后返回一个token令牌(形成JWT)。

3、后端服务器再将令牌(JWT字符串)作为登录成功的返回结果发送给客户端浏览器,客户端浏览器将返回结果存在localStorage对象里。

4、客户端浏览器下一次请求只需要将令牌(JWT)放在在header里的的Authorization位。

5、后端检查是否存在,如果验证令牌(JWT)有效,后端就可以使⽤JWT中包含的⽤户信息。

JWT组成结构

jwt是由三个部分组成,JWT其实就是⼀段字符串,由3部分组成,⽤ . (点)拼接
JWT字符串示例

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2M
jc5NzA5NDUsInVzZXIiOiJ7XCJpZFwiOjMsXCJuYW1lXCI6XCL
lkInlkIlcIixcInB3ZFwiOlwiMzMzXCJ9In0.NvGdUjFLJWj_
ZzhY9Qp--NkZgK1QGQtQjiCB7lEsFTg
标头:header——

一般不需要手动改设置,JWT会自动进行设置。
头部主要分为两部分:
typ: “JWT” 加密类型
alg: "hs256"签名算法。

Header{ //标头
‘typ’:JWT, 表示token类型
‘alg’:HS256’ 表示签名算法
}
负载:payload——

负载一般设置的是用户基本数据。⽤于存储用户的主要信息,使⽤ Base64编码组成JWT结构的第⼆部分。由于该信息是可以被解析的,所以不要存放敏感信息在第二部分

{name:"用户名",phoneNumber:"13878654547"};

Payload //有效负载

{
"userCode":"43435",
"name":"john",
"phone’:"13950497865"
}
签名:signature——

将编码过后的表头header和负载payload这两部分数据进行加密(加密需要使用到一个自定义的密钥。)

注: 前两部分都使⽤Base64进⾏编码,前端可以解开知道⾥⾯的信息, Signature需要使⽤编码后的header和payload以及我们提供的⼀密钥,然后使⽤header中指定的签名算法进⾏签名,以保证JWT没有被篡改过。
使⽤Signature签名可以防⽌内容被篡改。如果有⼈对头部及负载内容解码后进⾏修改,再进⾏编码,最后加上之前签名组成新的JWT。那么服务器会判断出新的头部和负载形成的签名和JWT附带的签名是不⼀样的。如果要对新的头部和负载进⾏签名,在不知道服务器加密时⽤的密钥的话,得出来的签名也是不⼀样的

OAuth2框架

描述

OAuth(开放授权)是一个开放标准,允许用户授权第三方应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方应用或分享他们数据的所有内容。OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH认证服务,这些都足以说明OAUTH标准逐渐成为开放资源授权的标准。
OAuth相关的名词

Third-party application 第三方应用程序,比如这里的虎牙直播;
HTTP service HTTP服务提供商,比如这里的QQ(腾讯);
Resource Owner 资源所有者,就是QQ的所有人,你;
User Agent 用户代理,这里指浏览器;
Authorization server 认证服务器,这里指QQ提供的第三方登录服务;
Resource server 资源服务器,这里指虎牙直播提供的服务,比如高清直播,弹幕发送等(需要认证后才能使用)。

服务器

OAuth2主要包含认证服务器资源服务器这两大块的实现。

认证服务器(也称:授权服务器)

主要包含了四种授权模式的实现和Token的生成与存储

资源服务器

主要是在Spring Security的过滤器链上加了OAuth2AuthenticationProcessingFilter过滤器,即使用OAuth2协议发放令牌认证的方式来保护我们的资源

四种授权模式

客户端凭证模式

客户端向授权服务器发送自己的身份信息,并请求令牌(access_token)确认客户端身份无误后,将令牌(access_token)发送给client,请求如下:

url?client_id=c1&client_secret=secret&grant_type=client_credentials

参数列表如下:

client_id: 客户端准入标识。
client_secret: 客户端秘钥。
grant_type: 授权类型,填写client_credentials表示客户端模式

优缺点: 这种模式是最方便但最不安全的模式。因此这就要求我们对client完全的信任,而client本身也是安全的。因此这种模式一般用来提供给我们完全信任的服务器端服务。比如,合作方系统对接,拉取一组用户信息。

总结来说: 该模式针对客户端而言,对用户是透明的,不需要用户参与,非用户层面授权。客户端向授权服务器发送自己的的 client_id 和client_secrect 请求 access_token ,用户中心仅校验客户端应用身份。客户端通过授权后可以获得授权范围内所有用户的信息,对客户端应用需要极高的信任。

模式特点: 针对客户端层面进行授权,而非对单独用户进行授权的场景,用户中心仅校验客户端应用的身份。
适用场景: 适用于的自家产品、微服务中,需要从接口层面发起请求的场景。

注: 当前模式仅生成 access_token,不生成 refresh_token。

密码模式

资源拥有者将用户名、密码发送给客户端,客户端拿着资源拥有者的用户名、密码向授权服务器请求令牌(access_token),请求如下:
授权服务器将令牌(access_token)发送给client

url?client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123

参数列表如下:

client_id: 客户端准入标识。
client_secret: 客户端秘钥。
grant_type: 授权类型,填写password表示密码模式
username: 资源拥有者用户名(账号)。
password: 资源拥有者密码(密码)。

授权服务器将令牌(access_token)发送给client
这种模式十分简单,但是却意味着直接将用户敏感信息泄漏给了client,把用户名和密码直接泄露给客户端,代表了资源拥有者和授权服务器对客户端的绝对互信,相信客户端不会做坏事。一般适用于内部开发的客户端的场景。因此这就说明这种模式只能用于client是我们自己开发的情况下。因此密码模式一般用于我们自己开发的,第一方原生App或第一方单页面应用。

总结来说: 用户直接提供用户名与密码给客户端应用,客户端使用用户的账号和密码、自己的 client_id 和 client_secrect 向授权服务器请求 token 。用户中心校验用户和客户端应用身份,响应 access_token 和 refresh_token。

模式特点: 用户的账号和密码直接暴露给客户端,安全性低。

适用场景: 适用于自家的产品、微服务中,需要从用户层面发起请求的场景。

注: 该模式已经被 Oauth2.1 废弃,直接将用户的账号和密码明文交给客户端应用是一个传统的方案,其本身没有校验意义,需要逐步过渡到通过 token 凭证这种授权方式。

换而言之,客户端都有密码了,通过 Oauth2.0 流程要一个临时的 access_token 干嘛?

授权码模式

资源拥有者打开客户端,客户端要求资源拥有者给予授权,它将浏览器被重定向到授权服务器,重定向时会附加客户端的身份信息。如:

url?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com

参数列表如下:

client_id: 客户端准入标识。
response_type: 授权码模式固定为code。
scope: 客户端权限。
redirect_uri: 跳转uri,当授权码申请成功后会跳转到此地址,并在后边带上code参数(授权码)。

1、浏览器出现向授权服务器授权页面,之后将用户同意授权。
2、授权服务器将授权码(AuthorizationCode)转经浏览器发送给client(通过redirect_uri)。
3、客户端拿着授权码向授权服务器索要访问access_token,请求如下:

url? client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://w ww.baidu.com

参数列表如下

client_id: 客户端准入标识。
client_secret: 客户端秘钥。
grant_type: 授权类型,填写authorization_code,表示授权码模式
code: 授权码,就是刚刚获取的授权码,注意:授权码只使用一次就无效了,需要重新申请。
redirect_uri: 申请授权码时的跳转url,一定和申请授权码时用的redirect_uri一致。

总结来说: 客户端应用引导(重定向)用户携带着 client_id 和 redirect_url 前往认证服务器认证,认证通过后认证服务会附带上 code 参数重定向到redirect_url 地址(客户端应用提供的接收授权码的地址)。客户端应用的服务端携带自己的 client_id 、 client_secrect 和 code 请求认证服务器获取 access_token 和 refresh_token 返回到用户。

模式特点: 四种模式中最安全、最常见的一种模式。
适用场景: 适用于有服务端服务的客户端应用。

网上说,这种方式避免了 access_token 直接在公网传输,黑客截获到 code 也无法获得最终的 access_token,所以这种方案非常安全。不是非常认同,如果 code 会被截获,那么用户登录时的 token 是否也可以直接被截获到?

个人观点认为,安全主要体现在这种模式同时校验了用户和客户端应用的 client_secrect ,与密码模式不同,当前模式用户和客户端应用双方都不知道对方的密码明文。

隐式授权(简易模式)
  1. 资源拥有者打开客户端,客户端要求资源拥有者给予授权,它将浏览器重定向到授权服务器,重定向时会附加客户端的身份信息。如:
url?client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com

参数描述同授权码模式 ,注意response_type=token,说明是简化模式。

  1. 浏览器出现向授权服务器授权页面,之后需要用户同意授权。

  2. 授权服务器将授权码将令牌(access_token)以Hash的形式存放在重定向uri的fargment中发送给浏览器。

注: fragment 主要是用来标识 URI 所标识资源里的某个资源,在 URI 的末尾通过(#)作为 fragment 的开头,

其中 # 不属于 fragment 的值。如https://domain/index#L18这个 URI 中 L18 就是 fragment 的值。大家只需要

知道js通过响应浏览器地址栏变化的方式能获取到fragment 就行了。

一般来说,简化模式用于没有服务器端的第三方单页面应用,因为没有服务器端就无法接收授权码。

浏览器访问认证页面:

url? client_id=c1&response_type=token&scope=all&redirect_uri=http://www.baidu.com

然后输入模拟的账号和密码点登陆之后进入授权页面:

确认授权后,浏览器会重定向到指定路径(oauth_client_details表中的web_server_redirect_uri)并以Hash的形式存放在重定向uri的fargment中,如:

http://aa.bb.cc/receive#access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZW5hbn...

总结来说: 客户端应用引导用户携带 client_id 前往授权服务器认证,认证通过后认证服务器直接返回access_token。

模式特点: 不需要与客户端应用的服务端进行交互,没有校验 client_secrect。与授权码模式,省去返回授权码,再重定向到授权服务,直接一步到位。
适用场景: 适用于仅有前端页面,没有后端服务的客户端应用。

通过 # 锚点链接的方式返回 access_token,避免 token 被携带传输到 web 前端文件托管的服务器上。
注: 该模式已经被 Oauth2.1 废弃,这种方式容易泄露 token ,不安全。但是token以#fragment 返回,并传输,容易泄露

Jwt

借鉴文章

借鉴文章

添加链接描述

添加链接描述

你可能感兴趣的:(spring,spring,boot,数据库)