问题
一般情况下 ,Spring Security 应用在一个单体项目中,此时只存在一个独立的项目,你访问一个资源,需要输入用户名和密码就行了
随着软件环境和需求的变化,软件的架构通常都会由单体结构演变成具有分布式架构的分布式系统。而分布式系统的每个服务都会有认证、授权的需求。如果每个服务都实现一套认证逻辑,就会非常冗余并且不现实。而针对分布式系统的特点,一般就会需要一套独立的第三方系统来提供统一的授权认证服务。
例如你存在2个服务,都是完成查询某个商品,但是由于负载均衡,你首次访问的是A机器,需要先登陆,输入用户名和密码,第二次访问B机器,此时,如果仍然需要输入用户名和密码的话,这种设计相信在座的各位都得挨揍了。
那么,如何去解决这个问题呢?
解决方案
- 一种是同步session
- 另一种是采用token形式进行检查,token存在统一授权中心,访问A的时候,A转发请求至授权中心获得token(此时需要输入用户名和密码),访问B的时候,也携带这个token,B收到token后检查这个token,如果token是正确的,就返回资源。此时不需要再次输入用户名和密码。
这里以第二种方案作为实现,需要用到一个新的东西oauth2
oauth2
oauth2是一种协议,核心是用token令牌替换直接输入用户名和密码的方式,通过该协议,可以实现跨服务至之间的授权功能。
一般分为2个模块,认证授权中心和资源中心,资源中心可以有多个:
授权中心:负责颁发token,配置`@EnableAuthorizationServer
资源中心:负责检查token(可以自己检查,例如jwt本地检查 或委托授权中心检查),检查通过后发放资源。
oauth2基础原理
授权中心颁发token这个没啥原理,就是让用户输入用户名和密码,检查下是否正确,然后返回一个token,说白了就是一个字符串,并存在授权中心的服务器上。
向资源中心发起一个资源的查询,资源中心检查token,检查通过后,发放资源。
OAuth是Open Authority的缩写,是令牌代替用户密码访问应用的又一标准, OAuth 协议存在第一代和第二代之分,后者称为OAuth2,Spring OAuth2是实现OAuth2协议的具体的框架之一,你也可以使用其他的OAuth2框架。
对于大家而言,我们在互联网应用中最常见的 OAuth2 应该就是各种第三方登录了。利用OAuth2协议,我们在注册csdn账户的时候,可以直接使用微信或QQ账户进行注册,这样,仅需登陆微信或QQ,即可让csdn获取到用户的昵称、爱好、邮箱等基础信息
那么如何实现检查token的呢?
答案在于 @EnableResourceServer注解,表示开启资源中心功能,会有代理类负责登陆和授权的检查:
一般的Spring Security要求所有的请求url都要先判断是否登录,如果没有登录,就跳转至登陆页,然后检查用户名和密码是否正确,但是资源中心注解会内置有更高优先级的拦截器,会修改这个默认的逻辑,不是通过用户名和密码来检查是否正确,而是通过检查消息头中的Authorization:Bearer xxx参数。
开启资源中心,所有资源优先用token方式进行检查,即检查消息头中是否含有 Authorization:Bearer xxx 这样格式的;
如果没有token,直接判定失败;即使有了token,那么如何验证?可以本地验证,或转发token至授权中心进行判断
授权中心颁发token后,会把token存储在内存中,这样当ABC服务获得token后,转发至授权中心,和内存中存储的原始值进行比较就行了。
endpoint概念
授权中心需要配置endpoint,那么endpoint是什么?
授权中心内置一些url,表示用于token特定的功能,这些url是免密使用的:
授权中心内置很多url资源,例如/oauth/token,这些url就是endpoint概念
你可以去复写/oauth/token 这样的url
单点登陆
利用token原理,可以实现登陆一个系统后,只输入一次用户名和密码,然后在cookie中保存这个token,再访问其他系统时,带上这个token,就可以直接访问资源了。
单点登录的英文名是 Single Sign On,因此一般简称为SSO。它的用途在于,不管多么复杂的应用群,只要在用户权限范围内,那么就可以做到,用户只需要登录一次就可以访问权限范围内的所有应用子系统。对于用户而言,访问多个应用子系统只需要登录一次,同样在需要注销的时候也只需要注销一次。举个简单的例子,你在百度首页登录成功之后,你再访问百度百科、百度知道、百度贴吧等网站也会处于登录状态了,这就是一个单点登录的真实案例。
划分为2类角色:
- 授权服务器 负责检查是否授权及生成token
- 资源服务器,提供资源,如果发现没有登陆,统一重定向至授权中心进行token授权。多台资源服务器可以共享同一个token,不用每次都生成token
特点是 检查是否有权限的操作是在资源服务器上,即每个资源服务器都有检查的逻辑。
缺点:sso默认配置仅能解决登陆认证问题,但是解决不了授权问题,即/user 和/order需要不同的权限
单点登陆和Oauth2的区别和联系:
-
相似点都是利用token作为介质
-
单点登陆侧重多个子系统的场景,登陆一个,另几个系统免登陆,简单来说就是同一个公司下的不同子系统,而oauth2侧重存在一个第三方系统,彼此完全独立,简单理解就是不同公司旗下的不同系统。
-
单点登陆侧重认证,即检查是否登陆,一般不细分权限,即只要登陆即可,所有的功能都可以访问。如果有角色概念,那么单点登陆就不能完全满足,需要额外的工作去负责授权检查;oauth2侧重认证+授权,属于单一url的,一次只能访问特定的资源,如果访问多个资源,需要多个token也就是说不同公司之间的系统,采用oauth2就是完美方案!
如果是同一个公司的不同子系统,那么不考虑复杂的授权的场景下,单点登陆就是完美方案!
统一网关
如果项目集成了网关,在网关里整合 OAuth2.0,实现单点登陆 有两种思路:
- 一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断权限等操作;
优点: 只有网关层一个模块负责检查权限,资源模块都不用涉及
缺点:需要改造,对技术要求高
- 另一种是由各资源服务处理,网关只做请求转发。 这种情况下,不用做特殊处理,等价于第四章节的sso单点登陆
优点:保留现有架构不动,每个资源服务配置sso即可
缺点:每个资源服务配置sso,架构上不好,存在冗余的感觉
比较常用的是第一种,把API网关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。
网关在认证授权体系里主要负责两件事:
(1)作为OAuth2.0的资源服务器角色,实现接入方权限拦截。
(2)令牌解析并转发当前登录用户信息(明文token)给微服务
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
(1)用户授权拦截(看当前用户是否有权访问该资源)
(2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)