阅读:78
网上有很多Spring Security和 oauth2的介绍,但是对于初学者来说,上手比较复杂,本篇从原理上梳理一下两者之间的联系和区别
spring security 的核心功能主要包括:
认证 (你是谁) 通过注解 @EnableWebSecurity
开启
简单来说,就是需要登录,你需要输入用户名和密码,才能访问某个url。
授权 (你能干什么) 不需要通过指定的开关开启,而是通过配置来增加授权规则来生效,不增加授权规则就不生效
授权的目的是可以把资源进行划分,例如公司有不同的资料,有普通级别和机密级别,只有公司高层才能看到机密级别的子类,而普通级别的资料大家都可以看到! 那么授权就是允许你查看某个资源,当然,如果你没有权限,就拒绝你查看!
认证可以单独使用,即不划分资源的级别,所有人只要登录都可以查看
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 应用在一个单体项目中,此时只存在一个独立的项目,你访问一个资源,需要输入用户名和密码就行了
随着软件环境和需求的变化,软件的架构通常都会由单体结构演变成具有分布式架构的分布式系统。而分布式系统的每个服务都会有认证、授权的需求。如果每个服务都实现一套认证逻辑,就会非常冗余并且不现实。而针对分布式系统的特点,一般就会需要一套独立的第三方系统来提供统一的授权认证服务。
例如你存在2个服务,都是完成查询某个商品,但是由于负载均衡,你首次访问的是A机器,需要先登陆,输入用户名和密码,第二次访问B机器,此时,如果仍然需要输入用户名和密码的话,岂不要命?
此时一般的文章都是通过2个步骤来演示的,第一步 拿到token,通过浏览器发送一个请求,完成用户名密码登陆输入,模拟获取token过程,第二步 携带token获取资源,通过postman发送请求模拟查询资源
通过前面,我们知道Spring Security存在一些问题,即分布式场景下多次登陆问题。
解决方案有2种:
不需要
再次输入用户名和密码oauth2是一种协议
,通过该协议,可以实现跨服务至之间的授权功能。一般分为2个模块,认证授权中心和资源中心,资源中心可以有多个:
授权中心:负责颁发token,配置`@EnableAuthorizationServer
资源中心:负责检查token(可以自己检查,例如jwt本地检查 或委托授权中心检查),检查通过后发放资源。
基础原理是这样的:
答案在于 @EnableResourceServer
注解,表示开启资源中心功能,会有代理类负责登陆和授权的检查:
一般的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包中的,例如@EnableAuthorizationServer 开启授权中心注解是在spring-security-oauth2-2.3.4.RELEASE.jar
:
从注解的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,就可以直接访问资源了。
划分为2类角色:
特点是 检查是否有权限的操作是在资源服务器上,即每个资源服务器都有检查的逻辑。
缺点:sso默认配置仅能解决登陆认证问题,但是解决不了授权问题,即/user 和/order需要不同的权限
如果项目集成了网关,在网关里整合 OAuth2.0,实现单点登陆 有两种思路:
一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断权限等操作;
优点: 只有网关层一个模块负责检查权限,资源模块都不用涉及
缺点:需要改造,对技术要求高
另一种是由各资源服务处理,网关只做请求转发。 这种情况下,不用做特殊处理,等价于第四章节的sso单点登陆
优点:保留现有架构不动,每个资源服务配置sso即可
缺点:每个资源服务配置sso,架构上不好,存在冗余的感觉
比较常用的是第一种,把API网关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。
网关在认证授权体系里主要负责两件事:
(1)作为OAuth2.0的资源服务器角色,实现接入方权限拦截。
(2)令牌解析并转发当前登录用户信息(明文token)给微服务
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
(1)用户授权拦截(看当前用户是否有权访问该资源)
(2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)