Re:从零开始的Spring Security Oauth2(三)

时间:2022-06-15 22:18:54

上一篇文章中我们介绍了获取token的流程,这一篇重点分析一下,携带token访问受限资源时,内部的工作流程。

@EnableResourceServer与@EnableAuthorizationServer

还记得我们在第一节中就介绍过了OAuth2的两个核心概念,资源服务器与身份认证服务器。我们对两个注解进行配置的同时,到底触发了内部的什么相关配置呢?

上一篇文章重点介绍的其实是与身份认证相关的流程,即如果获取token,而本节要分析的携带token访问受限资源,自然便是与@EnableResourceServer相关的资源服务器配置了。

我们注意到其相关配置类是ResourceServerConfigurer,内部关联了ResourceServerSecurityConfigurer和HttpSecurity。前者与资源安全配置相关,后者与http安全配置相关。(类名比较类似,注意区分,以Adapter结尾的是适配器,以Configurer结尾的是配置器,以Builder结尾的是建造器,他们分别代表不同的设计模式,对设计模式有所了解可以更加方便理解其设计思路)

public class ResourceServerConfigurerAdapter implements ResourceServerConfigurer {

    @Override
public void configure(ResourceServerSecurityConfigurer resources <1> ) throws Exception {
} @Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
} }

<1> ResourceServerSecurityConfigurer显然便是我们分析的重点了。

ResourceServerSecurityConfigurer(了解)

其核心配置如下所示:

public void configure(HttpSecurity http) throws Exception {

    AuthenticationManager oauthAuthenticationManager = oauthAuthenticationManager(http);
resourcesServerFilter = new OAuth2AuthenticationProcessingFilter();//<1>
resourcesServerFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
resourcesServerFilter.setAuthenticationManager(oauthAuthenticationManager);//<2>
if (eventPublisher != null) {
resourcesServerFilter.setAuthenticationEventPublisher(eventPublisher);
}
if (tokenExtractor != null) {
resourcesServerFilter.setTokenExtractor(tokenExtractor);//<3>
}
resourcesServerFilter = postProcess(resourcesServerFilter);
resourcesServerFilter.setStateless(stateless); // @formatter:off
http
.authorizeRequests().expressionHandler(expressionHandler)
.and()
.addFilterBefore(resourcesServerFilter, AbstractPreAuthenticatedProcessingFilter.class)
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)//<4>
.authenticationEntryPoint(authenticationEntryPoint);
// @formatter:on
}

这段是整个oauth2与HttpSecurity相关的核心配置,其中有非常多的注意点,顺带的都强调一下:

<1> 创建OAuth2AuthenticationProcessingFilter,即下一节所要介绍的OAuth2核心过滤器。

<2> 为OAuth2AuthenticationProcessingFilter提供固定的AuthenticationManager即OAuth2AuthenticationManager,它并没有将OAuth2AuthenticationManager添加到spring的容器中,不然可能会影响spring security的普通认证流程(非oauth2请求),只有被OAuth2AuthenticationProcessingFilter拦截到的oauth2相关请求才被特殊的身份认证器处理。

<3> 设置了TokenExtractor默认的实现—-BearerTokenExtractor,这个类在下一节介绍。

<4> 相关的异常处理器,可以重写相关实现,达到自定义异常的目的。

还记得我们在一开始的配置中配置了资源服务器,是它触发了相关的配置。

@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {}

核心过滤器 OAuth2AuthenticationProcessingFilter(掌握)

回顾一下我们之前是如何携带token访问受限资源的: 
http://localhost:8080/order/1?access_token=950a7cc9-5a8a-42c9-a693-40e817b1a4b0 
唯一的身份凭证,便是这个access_token,携带它进行访问,会进入OAuth2AuthenticationProcessingFilter之中,其核心代码如下:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain){

    final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res; try {
//从请求中取出身份信息,即access_token
Authentication authentication = tokenExtractor.extract(request); if (authentication == null) {
...
}
else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken) authentication;
needsDetails.setDetails(authenticationDetailsSource.buildDetails(request));
}
//认证身份
Authentication authResult = authenticationManager.authenticate(authentication);
...
eventPublisher.publishAuthenticationSuccess(authResult);
//将身份信息绑定到SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(authResult);
}
}
catch (OAuth2Exception failed) {
...
return;
} chain.doFilter(request, response);
}

整个过滤器便是oauth2身份鉴定的关键,在源码中,对这个类有一段如下的描述

A pre-authentication filter for OAuth2 protected resources. Extracts an OAuth2 token from the incoming request and uses it to populate the Spring Security context with an {@link OAuth2Authentication} (if used in conjunction with an {@link OAuth2AuthenticationManager}).

OAuth2保护资源的预先认证过滤器。如果与OAuth2AuthenticationManager结合使用,则会从到来的请求之中提取一个OAuth2 token,之后使用OAuth2Authentication来填充Spring Security上下文。

其中涉及到了两个关键的类TokenExtractor,AuthenticationManager。相信后者这个接口大家已经不陌生,但前面这个类之前还未出现在我们的视野中。

OAuth2的身份管理器–OAuth2AuthenticationManager(掌握)

在之前的OAuth2核心过滤器中出现的AuthenticationManager其实在我们意料之中,携带access_token必定得经过身份认证,但是在我们debug进入其中后,发现了一个出乎意料的事,AuthenticationManager的实现类并不是我们在前面文章中聊到的常用实现类ProviderManager,而是OAuth2AuthenticationManager。

Re:从零开始的Spring Security Oauth2(三)

图1 新的AuthenticationManager实现类OAuth2AuthenticationManager

回顾我们第一篇文章的配置,压根没有出现过这个OAuth2AuthenticationManager,并且它脱离了我们熟悉的认证流程(第二篇文章中的认证管理器UML图是一张经典的spring security结构类图),它直接重写了容器的*身份认证接口,内部维护了一个ClientDetailService和ResourceServerTokenServices,这两个核心类在 Re:从零开始的Spring Security Oauth2(二)有分析过。在ResourceServerSecurityConfigurer的小节中我们已经知晓了它是如何被框架自动配置的,这里要强调的是OAuth2AuthenticationManager是密切与token认证相关的,而不是与获取token密切相关的。

其判别身份的关键代码如下:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    ...
String token = (String) authentication.getPrincipal();
//最终还是借助tokenServices根据token加载身份信息
OAuth2Authentication auth = tokenServices.loadAuthentication(token);
... checkClientDetails(auth); if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
...
}
auth.setDetails(authentication.getDetails());
auth.setAuthenticated(true);
return auth; }

说到tokenServices这个密切与token相关的接口,这里要强调下,避免产生误解。tokenServices分为两类,一个是用在AuthenticationServer端,第二篇文章中介绍的

public interface AuthorizationServerTokenServices {
//创建token
OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
//刷新token
OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
throws AuthenticationException;
//获取token
OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);
}

而在ResourceServer端有自己的tokenServices接口:

public interface ResourceServerTokenServices {

    //根据accessToken加载客户端信息
OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException; //根据accessToken获取完整的访问令牌详细信息。
OAuth2AccessToken readAccessToken(String accessToken); }

具体内部如何加载,和AuthorizationServer大同小异,只是从tokenStore中取出相应身份的流程有点区别,不再详细看实现类了。

TokenExtractor(了解)

这个接口只有一个实现类,而且代码非常简单

public class BearerTokenExtractor implements TokenExtractor {

    private final static Log logger = LogFactory.getLog(BearerTokenExtractor.class);

    @Override
public Authentication extract(HttpServletRequest request) {
String tokenValue = extractToken(request);
if (tokenValue != null) {
PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(tokenValue, "");
return authentication;
}
return null;
} protected String extractToken(HttpServletRequest request) {
// first check the header...
String token = extractHeaderToken(request); // bearer type allows a request parameter as well
if (token == null) {
...
//从requestParameter中获取token
} return token;
} /**
* Extract the OAuth bearer token from a header.
*/
protected String extractHeaderToken(HttpServletRequest request) {
Enumeration<String> headers = request.getHeaders("Authorization");
while (headers.hasMoreElements()) { // typically there is only one (most servers enforce that)
...
//从Header中获取token
}
return null;
} }

它的作用在于分离出请求中包含的token。也启示了我们可以使用多种方式携带token。 
1 在Header中携带 
http://localhost:8080/order/1 
Header: 
Authentication:Bearer f732723d-af7f-41bb-bd06-2636ab2be135

2 拼接在url中作为requestParam 
http://localhost:8080/order/1?access_token=f732723d-af7f-41bb-bd06-2636ab2be135

3 在form表单中携带 
http://localhost:8080/order/1 
form param: 
access_token=f732723d-af7f-41bb-bd06-2636ab2be135

异常处理

OAuth2在资源服务器端的异常处理不算特别完善,但基本够用,如果想要重写异常机制,可以直接替换掉相关的Handler,如权限相关的AccessDeniedHandler。具体的配置应该在@EnableResourceServer中被覆盖,这是适配器+配置器的好处。

总结

到这儿,Spring Security OAuth2的整个内部流程就算是分析结束了。本系列的文章只能算是揭示一个大概的流程,重点还是介绍相关设计+接口,想要了解更多的细节,需要自己去翻看源码,研究各个实现类。在分析源码过程中总结出的一点经验,与君共勉:

1 先掌握宏观,如研究UML类图,搞清楚关联

2 分析*接口,设计是面向接口的,不重要的部分,具体实现类甚至都可以忽略

3 学会对比,如ResourceServer和AuthenticationServer是一种对称的设计,整个框架内部的类非常多,但分门别类的记忆,会加深记忆。如ResourceServerTokenServices ,AuthenticationServerTokenServices就一定是作用相关,但所属领域不同的两个接口

4 熟悉设计模式,spring中涉及了大量的设计模式,在框架的设计中也是遵循着设计模式的规范,如以Adapter结尾,便是运用了适配器模式;以Factory结尾,便是运用了适配器模式;Template结尾,便是运用了模板方法模式;Builder结尾,便是运用了建造者模式...

5 一点自己的理解:对源码的理解和灵感,这一切都建立自身的编码经验之上,自己遵循规范便能更好的理解别人同样遵守规范的代码。相对的,阅读好的源码,也能帮助我们自身提升编码规范。

原文:https://blog.csdn.net/u013815546/article/details/77046453

Re:从零开始的Spring Security Oauth2(三)的更多相关文章

  1. Re:从零开始的Spring Security Oauth2(二)

    本文开始从源码的层面,讲解一些Spring Security Oauth2的认证流程.本文较长,适合在空余时间段观看.且涉及了较多的源码,非关键性代码以…代替. 准备工作 首先开启debug信息: l ...

  2. Re:从零开始的Spring Security Oauth2(一)

    前言 今天来聊聊一个接口对接的场景,A厂家有一套HTTP接口需要提供给B厂家使用,由于是外网环境,所以需要有一套安全机制保障,这个时候oauth2就可以作为一个方案. 关于oauth2,其实是一个规范 ...

  3. Spring Security Oauth2系列(一)

    前言: 关于oauth2,其实是一个规范,本文重点讲解spring对他进行的实现,如果你还不清楚授权服务器,资源服务器,认证授权等基础概念,可以移步理解OAuth 2.0 - 阮一峰,这是一篇对于oa ...

  4. Spring Security Oauth2 的配置

    使用oauth2保护你的应用,可以分为简易的分为三个步骤 配置资源服务器 配置认证服务器 配置spring security 前两点是oauth2的主体内容,但前面我已经描述过了,spring sec ...

  5. Spring Security OAuth2&period;0认证授权三:使用JWT令牌

    Spring Security OAuth2.0系列文章: Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二: ...

  6. 使用Spring Security Oauth2完成RESTful服务password认证的过程

            摘要:Spring Security与Oauth2整合步骤中详细描述了使用过程,但它对于入门者有些重量级,比如将用户信息.ClientDetails.token存入数据库而非内存.配置 ...

  7. spring security oauth2 jwt 认证和资源分离的配置文件(java类配置版)

    最近再学习spring security oauth2.下载了官方的例子sparklr2和tonr2进行学习.但是例子里包含的东西太多,不知道最简单最主要的配置有哪些.所以决定自己尝试搭建简单版本的例 ...

  8. springboot&plus;spring security &plus;oauth2&period;0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)

    项目security_simple(认证授权项目) 1.新建springboot项目 这儿选择springboot版本我选择的是2.0.6 点击finish后完成项目的创建 2.引入maven依赖  ...

  9. Spring Security OAuth2 SSO 单点登录

    基于 Spring Security OAuth2 SSO 单点登录系统 SSO简介 单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自 ...

随机推荐

  1. javascript平时小例子①(移动的小div)

    css样式: #box{ width: 300px; height: 300px; background: deepskyblue; position: absolute; margin-right: ...

  2. python学习之路-day1-python基础1

    本节内容: Python介绍 发展史 Python 2 or 3? 安装 Hello World程序 变量 用户输入 模块初识 .pyc是个什么鬼? 数据类型初识 数据运算 表达式if ...else ...

  3. WCF的行为与异常-------配置文件说明

    ServiceBehavior and OperationBehavior(这些都是应用在实现类上) http://msdn.microsoft.com/zh-cn/library/system.se ...

  4. 总线接口与计算机通信(三)UART起止式异步通用串行数据总线

    串口简介 1. 什么是串口? 串口是计算机上一种非常通用的设备通信的协议.串口通信的概念非常简单,串口按位(bit) 发送和接收字节.尽管比按字节(byte)的并行通信慢,但是串口可以在使用一根线发送 ...

  5. Tomcat 乱码设置

    如果表单是以get方式提交就会出现中文乱码这时可以在tomcat中配置解决中文乱码问题. 方法如下:在tomcat的conf文件夹下的conf中找到server.xml文件 找到 Connector ...

  6. 【stanford C&plus;&plus;】字符串&lpar;String&rpar;与流&lpar;Stream&rpar;

    字符串(String)与流(Stream) 一.C++中字符串(String) 字符串(String):就是(可能是空的)字符序列. C++中的字符串在概念上和Java中的字符串类似. C++字符串用 ...

  7. 我用linux系统的采坑记

    我的新Ubuntu18,也没安装什么,但是在使用过程中总是莫名其妙的卡死,真的很烦.有时候cpu使用率接近100%,有时候貌似是内存不够了,但是我明明是8GB,这些小问题搞得我很恼火.这样的机器真的不 ...

  8. CentOS Too Many Open Files 解决

    问题 在使用 WRK 对应用服务进行压测的时候,提示 "too many open files" 信息,导致无法启动测试. 原因 CentOS 7.x 默认的打开文件数目限制为 1 ...

  9. CSS3网页动画

    CSS3网页动画 概要:CSS3变形是一些效果的集合 如:平移.旋转.缩放.倾斜效果 每个效果都可以称为变形(transform)他们可以分别操控元素发生平移.旋转.缩放.倾斜等变化. 网页中能够实现 ...

  10. C&num; Aspose&period;Cells导出xlsx格式Excel,打开文件报&OpenCurlyDoubleQuote;Excel 已完成文件级验证和修复。此工作簿的某些部分可能已被修复或丢弃”

    报错信息: 最近打开下载的 Excel,会报如下错误.(xls 格式不受影响) 解决方案: 下载代码(红色为新添代码) public void download() { string fileName ...