Spring Security + OAuth2
一、认证基本概念
1. 认证和授权
授权是用户通过认证后根据用户的权限来控制用户访问资源的过程,拥有权限正常访问,没有权限则拒绝访问
2. 基于token
二、基于Session的认证方式
略
三、Spring Security
1. Security使用
1.1 springboot 整合 security 只需maven引入依赖即可
引入后访问任何借口都需要登录,未登录会跳到登录页面
1.2 配置
创建srping security配置类 extends WebSecurityConfigurerAdapter 并注入到容器,且使用@EnableWebSecurity注解
1.3 springboot可以拦截错误异常并跳转 达到返回自定义错误信息的作用
以下是拦截异常并发起指定请求
新建一个异常controller,并应对以上的异常,举例:
1.4 自定义登录页面
1.5 用户角色权限数据库设计 RBAC权限模型
1.6 Security从数据库查询用户密码/权限信息
-
设计好数据库表,配置好mybatis查询
-
新建service实现Security的UserDetailService
UserEntity实体Security的User,在UserEntity中设置好数据库查询的权限,并返回,这里会返回当前登录用户名的密码信息和权限信息供security匹配使用
-
Spring Security配置类带auth形参的方法修改为:要将上面的类配置进行
1.7 将url和对应tag从数据库查出
2. JWT
json web token
jwt.io
传统token:放在服务端,token和SesionID思想一样
-
Session存放在服务器端JVM内存中
-
Token存放在Redis中 分布式
-
解决分布式Sesion数据一致性问题:Spring-Session
-
登录使用Token流程
-
验证会话信息
-
传统token缺点
- 必须依赖服务器端,从redis查,占用服务器端资源
- 效率较低
-
传统token优点
- 可以隐藏数据真实性
- 适用于分布式/微服务
jwt:放在客户端,无需在服务端保存
- 参考笔记
- 由header、payload、签名组成(签名可以防止抓包篡改数据)
- 不依赖服务器,减轻服务器压力,效率比传统 token高
- jwt一旦生成,后期无法修改
- 无法销毁jwt
**2.1 jwt可以读取但无法篡改 因为要验证签名密钥,密钥在服务端配置 **
- 登录成功,将用户信息塞进jwt的payload返回给前端
使用jwt,验证签名后直接从jwt获取传来的用户信息以及角色信息,无需再从库里查询,效率提高
解析前端传来的jwt,获取其中的信息:
**2.2 jwt如何注销 **
- 无法做真正意义上的注销,可以从如下方法考虑:
- 用户注销的时候,直接将cookie缓存清除
- 建议最好将jwt过期时间不要定义太长
- 每个用户对应的密钥不一样,用户注销的时候直接将密钥发送变化
- 当用户权限更新时,无法马上更新,只能重新刷新jwt(即重新登录)
3. java生成/解密jwt的工具
3.1 引入maven依赖
3.2 生成使用方法
可参考项目,等待补充
3.2 解密使用方法
-
比对签名
3.3 jwt设置过期时间
设置过期时间后,使用3.2解密,如果超过过期时间,会产生报错
JwtBuilder builder = Jwts.builder()
.setIssuedAt(new Date(System.currentTimeMillis()))
// 设置过期时间
.setExpiration(new Date(System.currentTimeMillis() + credentialExpireSeconds * 1000))
.setSubject(EntityUtils.toJsonString(userInfo))
.signWith(signatureAlgorithm, secretKey);
过期错误:
4. security整合jwt
与普通Security的核心2个区别:
- 登录成功后,用户信息无需存储在session,只需返回jwt(此过程可以不用Security)
- 配置Security从jwt读取用户的信息和角色,而不是从session(此过程需要配置Security)
流程如下,准备
- jwt工具类
- Spring Security依赖
- 配置好Security config
4.1 Security验证账号密码
实现UserDetailService;
配置过滤器,放行登录接口,配置登录成功、登录失败方法
4.2 验证成功,生成jwt返回客户端
jwt payload可以放哪些内容?
- username
- roles
新建过滤器,拦截到jwt,进行jwt验证
从jwt中读取用户权限列表并返回给security,让security来判断用户是否有权限,不再使用session
配置Security Config,增加上述过滤器,剔除session
4.3 验证jw流程
- base64解密 jwt,获取payload数据
- 获取roles权限列表,传到Spring Security
4.4 存在的问题
- 是否安全?
- 存在安全机制
- 如果篡改payload数据需要拿到存放在服务端的签名才有可能
- 如何注销?
- 直接清理客户端缓存 -> 服务端读取不到jwt,要求用户重新登录
- 权限发生变化如何处理?
- 只能重新登录,因为jwt一旦生成无法修改(管理员通知用户权限不足)
-
jwt有效期不能设置太长
-
jwt中存放userId等隐私参数时,可以进行对称加密防止信息泄漏,但最好不要全部都加密,否则效率很低。
一般情况下 只需要防御别人获取jwt就行了
四、OAuth2
1. oauth协议
oauth是一个协议,并非一个技术,开放授权协议,spring cloud oauth security实现了oauth2
真实案例:
如何管理开放接口:调用第三方接口登录(微信、qq、支付宝、丁丁等)=》它们底层都遵循 oauth2 协议
2. 四种形式
2.1 授权码形式【主流、安全】
获取accessToken,获取了该token(临时性且唯一,所以安全),才能调用接口
2.1.1 流程
认证授权中心和网关资源服务器可以写在同一个项目,也可写在不同的项目,做到区分,一个专门做授权,一个专门做过滤
2.1.1.1 appId和appKey
申请appid和appkey => appid终身唯一,appkey可以修改
2.1.1.2 根据appid(clientId)获取授权码
-
第一步:-> 生成授权链接,重定向到授权链接,授权链接包括以下:
// 第一步:浏览器访问,如果没登录,会重定向到login页面,如果已经登录了,会直接跳转到重定向页面 /** * client_id:appId,即该应用服务的id * response_type:授权类型,此值固定为code * redirect_uri:登录成功后的回调地址 * state:client端的状态值。用于第三方应用防止csrf攻击 * scope:请求用户授权时向用户显示的可进行授权的列表 */ GET http://127.0.0.1:3001/oauth/authorize?client_id=client1&response_type=code&redirect_uri=http://www.baidu.com
-
第二步:-> 到达授权链接,(用户)输入用户名、密码进行登录,认证中心识别用户身份,登录成功后 -> 到达是否允许授权应用确认页面(也可能是弹框,反正属于一个确认是否授权的步骤)
-
第三步:确认授权,重定向原应用并携带授权码code
https://www.baidu.com/?code=r9zqyd
-
第四步:原应用根据授权码code去认证中心请求oauth/token接口获取 access_token,原应用可以使用access_token去认证中心获取用户信息,(认证中心会保存一份access_token到redis或mysql达到持久化目的,否则进程结束后用户都会需要重新授权)
POST http://127.0.0.1:3001/oauth/token 方法体携带参数: "client_id", "client1", // 即应用服务的appid "client_secret", "123123", // 即应用服务的appkey "grant_type", "authorization_code", // (授权模式,固定参数) "code", "r9zqyd", // 授权码(第三步拿到) "redirect_uri", "http://www.baidu.com" // 验证成功后的重定向地址
返回json串,包含access_token:
{ "access_token": "415b010c-4891-41ef-90ce-1653ecd37658", "token_type": "bearer", "refresh_token": "02021d0e-34ed-42eb-8abc-f495b2dcd512", "expires_in": 133, "scope": "all" }
参考:https://blog.csdn.net/qq_27384769/article/details/79443242
2.1.1.3 网关鉴权架构
微服务的设计思想决定了不能使用代码去判断权限,而是通过网关去判断权限
- 网关也是一个springboot服务,比如zuul网关会配以zuul相关的包和配置,请求所有的应用都先经过这里
-
使用网关鉴权,即客户端将jwt发送到网关,网关搭配oauth2进行用户认证和权限验证,通过后将解析后的jwt直接发送给底下各个微服务,从而达到了解耦作用。而不是在每个服务都去分别认证。
- 请求服务接口的时候,会先进入网关oauth校验,网关会去调用认证中心接口
/oauth/check_token
校验access_token的正确性并进行权限校验。(认证中心也是网关下的微服务之一)
网关架构流程图,通常微服务都使用这种:
2.2 密码形式
传递usernane和userPassword(不安全)
2.3 简化模式
几乎不用(略)
2.4 客户端模式
几乎不用(略)
3. 搭建认证中心
暂略
4. 搭建网关过滤服务
暂略
五、SSO单点登录
单点登录是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统的保护资源,若用户在某个应用系统中进行注销登录,所有的应用系统都不能再直接访问保护资源,像一些知名的大型网站,如:淘宝与天猫、新浪微博与新浪博客等都用到了这个技术。
?如何实现sso单点登录
单点登录搭配sso的实现步骤:
有a,b两个系统,域名为a.com,b.com。然后sso为 sso.com。首先都处于未登录状态a系统和b系统都是自己独立的登录界面。 1、a首先去请求,然后a的server发现未登录,这个时候首先重定向sso.checkTokenId,这样sso发现cookie下确实没有种下身份令牌这样就重定向回a.login界面 2、a在登录界面提交账号密码到达sso.login方法,sso验证信息正确,那么就为sso.com的cookie种下身份令牌这个就是全局会话,重定向回最初的访问链接,并携带身份令牌,在a处生成了局部会话。 3、现在客户端再去访问b.com下的页面,这个时候b.server发现没有局部会话,未登录,这个时候首先重定向到sso.checkTokenId,sso验证是否有全局的身份令牌 4、sso验证有身份令牌,那么就返回b最初的访问链接,携带身份令牌,在b处生成局部会话,b处也就成了已登录 5、sso验证没有身份令牌,那么就重新到b.com下的b.login界面,重复2步骤,最终生成全局会话和局部会话
参考:https://blog.csdn.net/zhangjingao/article/details/89052764
登录流程思考: 思考工作项目中的oauth2+sso认证流程:
即cookie都存储在认证中心(示例:sso.com)的域名下。例如有2个子系统a、b,其实这两者都不需要跨域去互相获取cookie信息(cookie包含access_token),而是如果在a登录了的话,其实认证中心sso.com已经有登录成功后的cookie信息(包含access_token),而此时去b系统,b的后端发现没登录会重定向到认证中心sso.com,而此时sso.com已经有登录成功后返回cookie信息了(access_token)了,此时sso.com携带token去访问认证中心后端,发现已经登录,此时返回code并重定向回b,b在根据code去获取access_toekn即可,这样就不存在跨域的那些问题了
登出流程思考:
之所以能直接在任意应用下(同ip不同端口)通过删除cookie的方式达到退出登录的目的,是因为cookie部分端口,cookie是和域名绑定的,不区分端口;所以删除了cookie等于删除了该ip下的所有cookie信息。