网上有很多Spring Security和 oauth2的介绍,但是对于初学者来说,上手比较复杂,本篇从原理上梳理一下两者之间的联系和区别
参见 【Spring Security】基本功能介绍
spring security 的核心功能主要包括:
认证 (你是谁) 通过注解 @EnableWebSecurity
开启
简单来说,就是需要登录,你需要输入用户名和密码,才能访问某个url。
授权 (你能干什么) 不需要通过指定的开关开启,而是通过配置来增加授权规则来生效,不增加授权规则就不生效
授权的目的是可以把资源进行划分,例如公司有不同的资料,有普通级别和机密级别,只有公司高层才能看到机密级别的子类,而普通级别的资料大家都可以看到! 那么授权就是允许你查看某个资源,当然,如果你没有权限,就拒绝你查看!
授权有个前提就是先认证才能授权,例如你是A公司的人,有A公司的卡牌,才能进入A公司资料库。如果是B公司的人,大门都不给你进。
认证可以单独使用,即不划分资源的级别,所有人只要登录都可以查看
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
//配置安全拦截策略
@Override
protected void configure(HttpSecurity http) throws Exception {
//链式配置拦截策略
http
.csrf().disable()//关闭csrg跨域检查
//这里注意matchere是有顺序的。
.authorizeRequests()
.antMatchers("/user/**").hasAuthority("user")
.antMatchers("/order/**").hasAuthority("order")
.antMatchers("/common/**").permitAll() //common下的请求直接通过
.antMatchers("/**.html","/js/**","/css/**","/img/**").permitAll()//放行静态资源
.anyRequest().authenticated() //其他请求需要登录
.and()//并行条件
.formLogin()
.loginPage("/index.html")//自定义登录页面
.loginProcessingUrl("")
.defaultSuccessUrl("/main.html").failureUrl("/common/loginFailed"); //可从默认的login页面登录,并且登录后跳转到main.html
}
简单来说:
@EnableWebSecurity
,就需要先登陆, .anyRequest().authenticated() 就是让你登陆的意思spring security的核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。
如下图所示,这是一组链式处理器类,请求从做往右依次经过多个过滤器类处理:
比如,对于username password认证过滤器来说:
会检查是否是一个登录请求;
是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;
如果不满足则放行给下一个。
下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4
的信息。中间可能还有更多的认证过滤器。最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。
注意:绿色的过滤器可以配置是否生效,其他的都不能控制。
一般情况下 ,Spring Security 应用在一个单体项目中,此时只存在一个独立的项目,你访问一个资源,需要输入用户名和密码就行了
在多体应用中,特别是存在第三方的场景下,我们存在这样的需求:
什么是单体应用?就是资源和检查权限的系统都正在一个地方,传统的注册csdn手动输入邮箱,然后你登陆csdn,就是单体应用,而我们去注册csdn,此时微信的个人信息数据就是第三方系统的,此时涉及csdn和微信2个系统
此时一般的文章都是通过2个步骤来演示的,第一步 拿到token,通过浏览器发送一个请求,完成用户名密码登陆输入,模拟获取token过程,第二步 携带token获取资源,通过postman发送请求模拟查询资源
oauth2是一种协议,核心是用token令牌替换直接输入用户名和密码的方式
,通过该协议,可以实现跨服务至之间的授权功能。一般分为2个模块,认证授权中心和资源中心,资源中心可以有多个:
授权中心:负责颁发token,配置`@EnableAuthorizationServer
资源中心:负责检查token(可以自己检查,例如jwt本地检查 或委托授权中心检查),检查通过后发放资源。
基础原理是这样的:
授权中心颁发token
这个没啥原理,就是让用户输入用户名和密码,检查下是否正确,然后返回一个token
,说白了就是一个字符串,并存在授权中心的服务器上。
就像你买的饭票一样,你用现金买了饭票,将来凭借饭票就可以领取一份盒饭!不需要再付钱了
向资源中心发起一个资源的查询,资源中心检查token,检查通过后,发放资源。
关键点在于微信颁发的token,可以给csdn,csdn带着token去微信获取资源,这个步骤是在csdn后台完成的,对用户来说是隐藏的。也就是说csdn拿了饭票,去微信领盒饭了,不需要付钱(输入密码),爽!
OAuth是Open Authority的缩写,是令牌代替用户密码访问应用的又一标准, OAuth 协议存在第一代和第二代之分,后者称为OAuth2,Spring OAuth2是实现OAuth2协议的具体的框架之一,你也可以使用其他的OAuth2框架。
对于大家而言,我们在互联网应用中最常见的 OAuth2 应该就是各种第三方登录了。利用OAuth2协议,我们在注册csdn账户的时候,可以直接使用微信或QQ账户进行注册,这样,仅需登陆微信或QQ,即可让csdn获取到用户的昵称、爱好、邮箱等基础信息
答案在于 @EnableResourceServer
注解,表示开启资源中心功能,会有代理类负责登陆和授权的检查:
如果某个服务的入口类被 @EnableResourceServer
注解,说明现在是基于oauth2协议的架构,那么原来的Spring Security配置会被覆盖:
一般的Spring Security要求所有的请求url都要先判断是否登录,如果没有登录,就跳转至登陆页,然后检查用户名和密码是否正确,但是资源中心注解会内置有更高优先级的拦截器,会修改这个默认的逻辑,不是通过用户名和密码来检查是否正确,而是通过检查消息头中的Authorization:Bearer xxx
参数。
开启资源中心,所有资源优先用token方式进行检查,即检查消息头中是否含有 Authorization:Bearer xxx 这样格式的;
如果没有token,直接判定失败;即使有了token,那么如何验证?可以本地验证,或转发token至授权中心进行判断
授权中心颁发token后,会把token存储在内存中,这样当ABC服务获得token后,转发至授权中心,和内存中存储的原始值进行比较就行了。
token默认是明文的不安全,采用jwt,可以进行加密,更安全!
加密有2种形式:
对称加密
缺点:容易破解
非对称加密
优点:不容易破解
授权中心需要配置endpoint,那么endpoint是什么?
授权中心内置一些url,表示用于token特定的功能,这些url是免密使用的:
授权中心内置很多url资源,例如
/oauth/token
,这些url就是endpoint概念
你可以去复写/oauth/token
这样的url
从第一章节,我们知道, spring Security是一个框架,提供了认证和授权功能,从本章得知oauth2是一个协议,光有协议也没用,你的有具体的实现,那么spring Security oauth2是什么东西呢?答案就是 spring Security框架内置了oauth2的api,可以直接使用
。当然,其他框架也有oauth2的实现,你可以根据需求选择。
我们在第二章节用到的语法,Spring Security的oauth2 是内置
在Spring Security包中的,例如@EnableAuthorizationServer 开启授权中心注解是在spring-security-oauth2-2.3.4.RELEASE.jar
,说明Spring Security框架已经包含了oauth2协议的api接口:
从注解的package 能看出是springframework下的,说明是基础包:
package org.springframework.security.oauth2.config.annotation.web.configuration;
public @interface EnableAuthorizationServer {
}
spring cloud oauth2
是提供了一个starter,自动引入相关的依赖包,仅仅是方便使用,并没有新增特殊的功能
:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
通过前面的学习,我们知道,利用token原理,可以实现登陆一个系统后,只输入一次用户名和密码,然后在cookie中保存这个token,再访问其他系统时,带上这个token,就可以直接访问资源了。
oauth2的基本原理就是利用了token实现的,帮助我们利用第三方系统获得资源。
单点登录的英文名是 Single Sign On,因此一般简称为SSO。它的用途在于,不管多么复杂的应用群,只要在用户权限范围内,那么就可以做到,用户只需要登录一次就可以访问权限范围内的所有应用子系统。对于用户而言,访问多个应用子系统只需要登录一次,同样在需要注销的时候也只需要注销一次。举个简单的例子,你在百度首页登录成功之后,你再访问百度百科、百度知道、百度贴吧等网站也会处于登录状态了,这就是一个单点登录的真实案例。
划分为2类角色:
特点是 检查是否有权限的操作是在资源服务器上,即每个资源服务器都有检查的逻辑。
缺点:sso默认配置仅能解决登陆认证问题,但是解决不了授权问题
,即/user 和/order需要不同的权限
单点登陆和Oauth2的区别和联系:
也就是说不同公司之间的系统,采用oauth2就是完美方案!
如果是同一个公司的不同子系统,那么不考虑复杂的授权的场景下,单点登陆就是完美方案!
如果单点登陆同时需要考虑授权的情况,那么就要考虑授权功能,系统就更复杂了,需要sso+授权,例如下一章节就是该问题的解决方案!
从前面学习的示例来看,是不包含网关的,即sso项目是多个独立服务都要进行鉴权,每个服务都要配置一套鉴权系统,只不过是把具体的处理流程转发至同一个鉴权中心了,只要登录一次,就可以在多个服务中心共享登录信息。那么有了网关之后怎么办?
有了网关,可以在网关处进行鉴权,后续的请求转发至具体服务后,后者不再需要进行鉴权了,或者网关暂不处理鉴权,转发请求至具体的服务后,由服务自行进行鉴权
有了网关之后,就有了统一的入口!
如果项目集成了网关,在网关里整合 OAuth2.0,实现单点登陆 有两种思路:
一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断权限等操作;
优点: 只有网关层一个模块负责检查权限,资源模块都不用涉及
缺点:需要改造,对技术要求高
另一种是由各资源服务处理,网关只做请求转发。 这种情况下,不用做特殊处理,等价于第四章节的sso单点登陆
优点:保留现有架构不动,每个资源服务配置sso即可
缺点:每个资源服务配置sso,架构上不好,存在冗余的感觉
比较常用的是第一种,把API网关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。
网关在认证授权体系里主要负责两件事:
(1)作为OAuth2.0的资源服务器角色,实现接入方权限拦截。
(2)令牌解析并转发当前登录用户信息(明文token)给微服务
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
(1)用户授权拦截(看当前用户是否有权访问该资源)
(2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)