隔壁程序妹子推荐的纯 Spring Security 架构干货分享,我先收藏了!

本文主要是介绍隔壁程序妹子推荐的纯 Spring Security 架构干货分享,我先收藏了!,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

点击上方 "程序员小乐"关注, 星标或置顶一起成长

每天凌晨00点00分, 第一时间与你相约

    

每日英文

Three things in life when gone never come back: time, opportunity, and words.

人生有三样东西不可挽回:时间,机遇,以及说出去的话。

每日掏心

有时候,人就是如此,对自己最好的人明明就在身边,但是,却不好好珍惜,去追寻一些不属于自己的东西,等到失去了,才感觉心里空荡荡了,包裹着心的那层温暖消失了,才知道后悔了。

作者:before31 | 责编:乐乐

来自:my.oschina.net/xuezi/blog/3126351

程序员小乐(ID:study_tech)第 1002 次推文

往日回顾:CTO 写的代码,真是绝了

     

   正文   

本指南是Spring Security的入门,它提供了对该框架的设计和基本构建的见解。我们仅介绍了应用程序安全性的最基本知识,但是这样做可以解除使用Spring Security的开发人员所遇到的一些困惑。为此,我们会看一下使用过滤器(更通常是使用方法注释)在Web应用程序中应用安全性的方式。当你需要从高层次了解安全应用程序的工作方式,如何自定义它,或者仅需要学习如何考虑应用程序安全性时,请使用本指南。

本指南并不是解决最基本问题的手册(对于基本问题,有很多其他可参考的资料),但对于初学者和专家都可能有用。Spring Boot也被提及了很多次,因为它为安全的应用程序提供了一些默认行为,并且理解它与整个体系结构之间的关系会对你很有帮助。所有这些原则同样适用于不使用Spring Boot的应用程序。

身份认证和访问控制(Authentication and Access Control)

应用程序安全性差不多可以归结为两个独立的问题:身份认证(你是谁)和授权(authorization)(你可以做什么?)。有时人们会说“访问控制”而不是“授权”,这可能会造成困惑,但是以这种方式思考可能会有所帮助,因为“授权”在其他地方又有其他含义。Spring Security的体系结构旨在将身份认证与授权分开,并且具有许多策略和扩展点。

身份认证

认证的主要策略接口是AuthenticationManager,该接口只有一个方法:

public interface AuthenticationManager {Authentication authenticate(Authentication authentication)throws AuthenticationException;}

在AuthenticationManager接口的authenticate()方法中可以有3种处理情况:

  • 如果认证成功,则返回一个Authentication对象(通常将其authenticated属性设置为true)。

  • 如果认证失败,则抛出AuthenticationException异常。

  • 果无法判断成功或失败,则返回null。

AuthenticationException是运行时异常。它通常由应用程序以通用方式处理,具体取决于应用程序的风格或目的。换句话说,通常不希望用户代码捕获并处理它。例如,一个Web UI将呈现一个页面,该页面说明身份验证失败,而后端HTTP服务将发送401响应,是否携带WWW-Authenticate标头则取决于上下文。

AuthenticationManager最常用的实现是ProviderManager,它委派了AuthenticationProvider实例链。AuthenticationProvider有点像AuthenticationManager,但是它还有一个额外的方法,允许调用者查询是否支持给定的Authentication类型:

public interface AuthenticationProvider {Authentication authenticate(Authentication authentication)throws AuthenticationException;boolean supports(Class<?> authentication);}

supports()方法中的Class<?>参数实际上是Class<? extends Authentication>(仅会询问它是否支持将传递到authenticate()方法中的内容)。通过委派给AuthenticationProviders链,ProviderManager可以在同一应用程序中支持多种不同的身份验证机制。如果ProviderManager无法识别特定的身份验证实例类型,则将跳过该类型。

ProviderManager具有可选的父级,如果所有提供程序都返回null,则可以咨询该父级。如果父级不可用,则空的Authentication将导致AuthenticationException。

有时,应用程序具有逻辑组的受保护资源(例如,与路径模式/api/**匹配的所有Web资源),并且每个组可以具有自己的专用AuthenticationManager。通常,每一个都是ProviderManager,它们共享一个父级。因此,父级是一种“全局”资源,充当所有providers的后备。

使用ProviderManager的AuthenticationManager层次结构

定制Authentication Managers

Spring Security提供了一些配置助手,可以快速获取在应用程序中设置的通用Authentication Managers功能。最常用的帮助程序是AuthenticationManagerBuilder,它非常适合设置内存中、JDBC或LDAP用户详细信息,或添加自定义UserDetailsService。这是配置全局(父)AuthenticationManager的应用程序的示例:

@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {... // web stuff here@Autowiredpublic void initialize(AuthenticationManagerBuilder builder, DataSource dataSource) {builder.jdbcAuthentication().dataSource(dataSource).withUser("dave").password("secret").roles("USER");}}

此示例与Web应用程序有关,但是AuthenticationManagerBuilder的用法更为广泛(有关如何实现Web应用程序安全性的详细信息,请参见下文)。注意,AuthenticationManagerBuilder是@Autowired到@Bean中的方法中的-这就是使它构建全局(父)AuthenticationManager的原因。相反,如果我们这样做的话:

搜索公众号程序员小乐回复关键字“Java”获取Java面试题和答案。

@Configuration
public class ApplicationSecurity extends WebSecurityConfigurerAdapter {@AutowiredDataSource dataSource;... // web stuff here@Overridepublic void configure(AuthenticationManagerBuilder builder) {builder.jdbcAuthentication().dataSource(dataSource).withUser("dave").password("secret").roles("USER");}}

(在配置程序中使用方法的@Override),那么AuthenticationManagerBuilder仅用于构建“本地” AuthenticationManager,它是全局管理器的子级。在Spring Boot应用程序中,你可以使用@Autowired将全局的管理器注入到另一个bean,但是除非你自己显式公开“本地”管理器,否则不能对本地管理器执行此操作。

Spring Boot提供了一个默认的全局AuthenticationManager(只有一个用户),除非你通过提供自己的AuthenticationManager类型的bean来抢占它。除非你主动需要自定义全局AuthenticationManager,否则默认值本身就足够安全,你不必担心太多。如果执行任何构建AuthenticationManager的配置,则通常可以在“本地”对要保护的资源进行配置,而不要去关心全局的默认值。

授权或访问控制(Authorization or Access Control)

身份认证成功以后,我们接下来讨论授权,这里的核心策略是AccessDecisionManager。该框架提供了三种实现,所有这三种实现都委托给AccessDecisionVoter链,有点像ProviderManager委托给AuthenticationProviders。

AccessDecisionVoter会考虑Authentication(表示一个主体)和被ConfigAttributes修饰的安全Object:

boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);

Object在AccessDecisionManager和AccessDecisionVoter的签名中是完全通用的-它表示用户可能要访问的任何内容(Web资源或Java类中的方法是两种最常见的情况)。ConfigAttributes也相当通用,用一些元数据来表示安全Object的修饰,这些元数据确定访问它所需的权限级别。ConfigAttribute是一个接口,但是它只有一个非常通用的方法并返回String,因此这些字符串以某种方式编码资源所有者的意图,表达有关允许谁访问它的规则。典型的ConfigAttribute是用户角色的名称(如ROLE_ADMIN或ROLE_AUDIT),并且它们通常具有特殊的格式(如ROLE_前缀)或表示需要求值的表达式。

大多数人只使用默认的AccessDecisionManager,它是AffirmativeBased的,即任何选民(voter)返回允许,则将授予访问权限。任何定制都倾向于在选民中发生,要么增加新选民,要么修改现有选民的工作方式。

使用SpEL(Spring表达式语言)表达式的ConfigAttribute非常常见,例如isFullyAuthenticated()&& hasRole('FOO')。AccessDecisionVoter支持此功能,可以处理表达式并为其创建上下文。为了扩展可以处理的表达式的范围,需要SecurityExpressionRoot的自定义实现,有时还需要实现SecurityExpressionHandler。

Web安全

Web层(用于UI和HTTP后端)中的Spring Security基于Servlet过滤器,因此通常首先了解过滤器的作用会很有帮助。下图显示了单个HTTP请求的处理程序的典型分层。

客户端向应用程序发送请求,然后容器根据请求URI的路径确定对它应用哪些过滤器和哪个servlet。一个servlet最多只能处理一个请求,但是过滤器形成一个链,因此它们是有序的,实际上,如果某个过滤器要自己处理该请求,则其可以否决链的其余部分。过滤器还可以修改下游过滤器和Servlet中使用的请求和/或响应。过滤器链的顺序非常重要,Spring Boot通过两种机制对其进行管理:一种是Filter类型的@Bean可以具有@Order或实现Ordered,另一种是它们可以成为FilterRegistrationBean本身的一部分,该Bean本身就维持了顺序。一些现成的过滤器定义了自己的常量,以帮助表明它们相对彼此的顺序(例如,Spring Session中的SessionRepositoryFilter拥有的DEFAULT_ORDER值为Integer.MIN_VALUE + 50,这告诉我们此过滤器需要在过滤器链中比较靠前,但也并不阻止其他过滤器更靠前一些)。

Spring Security是作为链中的单个Filter安装的,其隐秘类型为FilterChainProxy,原因很快就会变得显而易见。在Spring Boot应用程序中,安全过滤器是ApplicationContext中的@Bean,默认情况下会安装它,以便将其应用于每个请求。它安装在SecurityProperties.DEFAULT_FILTER_ORDER定义的位置,该位置又由FilterRegistrationBean.REQUEST_WRAPPER_FILTER_MAX_ORDER锚定(Spring Boot应用程序希望过滤器包装请求并修改其行为时期望的最大顺序)。但是,还有更多要说的:从容器的角度来看,Spring Security是一个过滤器,但是在内部有其他过滤器,每个过滤器都扮演着特殊的角色。这是一张图:

Spring Security是单一Filter,但将处理逻辑委托给一系列内部过滤器

实际上,安全过滤器中甚至还有一层间接层:它通常作为DelegatingFilterProxy安装在容器中,而不必是Spring @Bean。代理委托给一个始终为@Bean的FilterChainProxy,通常使用固定名称springSecurityFilterChain。它是FilterChainProxy,它包含所有内部安全逻辑,这些安全逻辑在内部排列为一个或多个过滤器链。所有过滤器都具有相同的API(它们都实现了Servlet规范中的Filter接口),并且它们都有机会否决该链的其余部分。

在同一顶级FilterChainProxy中可以有多个由Spring Security管理的过滤器链,而对于容器来说都是未知的。Spring Security过滤器包含一个过滤器链列表,并向与其匹配的第一个链调度(dispatch)一个请求。下图显示了基于匹配请求路径(/foo/*在/*之前匹配)进行的调度。这是很常见的,但不是匹配请求的唯一方法。此调度过程的最重要特征是,只有一个链可以处理请求。

Spring SecurityFilterChainProxy将请求调度到匹配的第一个链

一个Spring Boot应用程序,如果没有自定义安全配置,会拥有多个(称为n)过滤器链,通常n = 6。前(n-1)个链仅用于忽略静态资源,例如/css/和/images/,以及错误视图/error(路径可以由SecurityProperties配置bean中的security.ignored控制)。最后一个链与所有路径/**匹配,并且更活跃,包含用于身份认证、授权、异常处理、会话处理、标头写入等逻辑。默认情况下,该链中共有11个过滤器,但通常情况下用户不必关心使用了哪个过滤器以及何时使用。

注意:容器不知道Spring Security内部的所有过滤器这一事实很重要,尤其是在Spring Boot应用程序中,默认情况下,所有Filter类型的@Beans都会自动向容器注册。因此,如果要向安全链中添加自定义过滤器,请不要使其成为@Bean,或者干脆将其包装在FilterRegistrationBean中,在该Bean中显式禁用了容器注册的。

创建并自定义过滤器链

Spring Boot应用程序中的默认后备过滤器链(匹配/**请求的那个链)具有SecurityProperties.BASIC_AUTH_ORDER的预定义顺序。你可以通过设置security.basic.enabled = false完全关闭它,也可以将其用作后备方式,而只是以较低的顺序定义其他规则。为此,只需添加类型为WebSecurityConfigurerAdapter(或WebSecurityConfigurer)的@Bean并使用@Order注解。例如:

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.antMatcher("/foo/**")...;}
}

这个bean将使Spring Security添加一个新的过滤器链并将其放置在后背过滤器链之前。

搜索公众号程序员小乐回复关键字“offer”,获取算法面试题和答案。

许多应用程序对一组资源的访问规则可能完全不同于另外一组。例如,对于一个承载了UI并且也支持API的应用程序,可能其UI部分需要支持基于cookie(cookid-based)的身份认证以及对登录页面的重定向,而其API部分则需要支持基于令牌(token-based)的身份认证以及对未经身份认证的请求的401响应。每组资源都有其自己的WebSecurityConfigurerAdapter以及唯一的顺序和自己的请求匹配器。如果匹配规则重叠,则最早的有序过滤器链将获胜。

调度和授权之请求匹配(Request Matching for Dispatch and Authorization)

安全过滤器链(或等效的WebSecurityConfigurerAdapter)具有请求匹配器,该请求匹配器用于确定是否将其应用于HTTP请求。一旦决定应用特定的过滤器链,就不再应用其他过滤器链。但是在过滤器链中,可以通过在HttpSecurity配置器中设置其他匹配器来对授权进行更精细的控制。例如:

@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER - 10)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.antMatcher("/foo/**").authorizeRequests().antMatchers("/foo/bar").hasRole("BAR").antMatchers("/foo/spam").hasRole("SPAM").anyRequest().isAuthenticated();}
}

配置Spring Security时最容易犯的一个错误是忘记这些匹配器适用于不同的流程,一个是整个过滤器链的请求匹配器,另一个是仅选择要应用的访问规则。

将应用程序安全规则与执行器规则相结合(Combining Application Security Rules with Actuator Rules)

如果你使用了Spring Boot执行器(Actuator),则可能希望它的端点(endpoint)是安全的,默认情况下确实将是安全的。实际上,将执行器添加到安全应用程序后,你会获得一条仅适用于执行器端点的附加过滤器链。它由仅匹配执行器端点的请求匹配器来定义,并且其顺序为ManagementServerProperties.BASIC_AUTH_ORDER,该顺序比默认的SecurityProperties后备过滤器的顺序小5,因此会在后备过滤器处理之前请执行。

如果你希望将应用程序安全规则应用于执行器端点,则可以添加一个比执行器过滤器链顺序更早的过滤器链,并且使之带有包括所有执行器端点的请求匹配器。如果你更倾向于使用执行器端点的默认安全设置,那么最简单的方法是在执行器端点之后但在后备过滤器链之前(例如ManagementServerProperties.BASIC_AUTH_ORDER +1)添加自己的过滤器。例如:

@Configuration
@Order(ManagementServerProperties.BASIC_AUTH_ORDER + 1)
public class ApplicationConfigurerAdapter extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.antMatcher("/foo/**")...;}
}

注意:Web层中的Spring Security当前与Servlet API绑定,因此仅当在Servlet容器中运行应用程序时才真正适用,不管是嵌入式的容器还是独立式的容器。但是,它不依赖于Spring MVC或Spring Web栈的其它部分,因此可以在任何servlet应用程序中使用,例如使用JAX-RS的servlet应用程序。

方法安全(Method Security)

除了支持Web应用程序安全外,Spring Security还支持将访问规则应用于Java方法执行。对于Spring Security,这只是另一种类型的“受保护资源”。对于用户来说,这意味着使用相同的ConfigAttribute字符串格式(例如角色或表达式)来声明访问规则,但在代码中的不同位置。第一步是启用方法安全性,例如在应用程序的顶级配置中:

@SpringBootApplication
@EnableGlobalMethodSecurity(securedEnabled = true)
public class SampleSecureApplication {
}

然后我们就可以直接修饰方法资源,例如:

@Service
public class MyService {@Secured("ROLE_USER")public String secure() {return "Hello Security";}
}

此示例是一种使用安全方法的服务。如果Spring创建了这种类型的@Bean,则它将被代理,并且在实际执行该方法之前,调用者必须经过安全拦截器。如果访问被拒绝,则调用者将得到AccessDeniedException异常,而不是实际的方法结果。

方法上还可以使用其他注解来强制执行安全性约束,特别是@PreAuthorize和@PostAuthorize,它们可以使你编写分别包含对方法参数和返回值的引用的表达式。

小贴士:结合使用Web安全性和方法安全性并不少见。过滤器链提供了用户体验功能,例如身份认证和重定向到登录页面等,而方法安全性在更精细的级别上提供了保护。

使用线程(Working with Threads)

Spring Security从根本上讲是线程绑定的,因为它需要使当前经过身份验证的主体可供各种下游使用者使用。基本构件是SecurityContext,它可以包含一个Authentication(当用户登录成功以后,它将被显式注明为authenticated)。你始终可以通过SecurityContextHolder中的静态方法访问和操作SecurityContext,而该方法里面其实是简单地操作TheadLocal,例如

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
assert(authentication.isAuthenticated);

用户应用程序代码执行此操作并不常见,但是如果在你需要编写自定义身份认证过滤器时可能会很有用(尽管即使如此,Spring Security中也可以使用基类来避免使用SecurityContextHolder)。

如果需要访问Web端点中当前已认证的用户,则可以在@RequestMapping中使用方法参数。例如。

@RequestMapping("/foo")
public String foo(@AuthenticationPrincipal User user) {... // do stuff with user
}

该注解将当前的Authentication从SecurityContext中取出,并调用其getPrincipal()方法以产生方法参数。Authentication中的主体(Principal)的类型取决于用于身份认证的AuthenticationManager,因此这也是一个获得对用户数据的类型安全引用的有用的小技巧。

如果使用Spring Security,则HttpServletRequest中的Principal的类型将为Authentication,因此你也可以直接使用它:

@RequestMapping("/foo")
public String foo(Principal principal) {Authentication authentication = (Authentication) principal;User = (User) authentication.getPrincipal();... // do stuff with user
}

如果你需要编写在不使用Spring Security的情况下仍可以正常工作的代码,那么这就会很有用(你需要在加载Authentication类时更具防御性)。

异步处理安全方法(Processing Secure Methods Asynchronously)

由于SecurityContext是线程绑定的,因此,如果要执行任何调用安全方法的后台处理,例如使用@Async,你需要确保传播该SecurityContext。简单归结起来就是,要将SecurityContext与在后台执行的任务(Runnable,Callable等)包装在一起。Spring Security提供了一些帮助程序,例如Runnable和Callable的包装器,可以使上述操作变得简单。要将SecurityContext传播到@Async方法,你需要提供AsyncConfigurer并确保Executor具有正确的类型:

@Configuration
public class ApplicationConfiguration extends AsyncConfigurerSupport {@Overridepublic Executor getAsyncExecutor() {return new DelegatingSecurityContextExecutorService(Executors.newFixedThreadPool(5));}
}

欢迎在留言区留下你的观点,一起讨论提高。如果今天的文章让你有新的启发,欢迎转发分享给更多人。欢迎加入程序员小乐技术交流群,在后台回复“加群”或者“学习”即可。

猜你还想看

阿里、腾讯、百度、华为、京东最新面试题汇集

超实用超牛X!Linux系统内存知识总结!

JDK/Java 15 刚刚发布!Java 新增国家/地区使用限制条款引发争议,网友:不让我们用了?

重磅!10月1日之后,你新建的GitHub库默认分支不叫「master」了!

关注订阅号「程序员小乐」,收看更多精彩内容

嘿,你在看吗

这篇关于隔壁程序妹子推荐的纯 Spring Security 架构干货分享,我先收藏了!的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/187878

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

mybatis的整体架构

mybatis的整体架构分为三层: 1.基础支持层 该层包括:数据源模块、事务管理模块、缓存模块、Binding模块、反射模块、类型转换模块、日志模块、资源加载模块、解析器模块 2.核心处理层 该层包括:配置解析、参数映射、SQL解析、SQL执行、结果集映射、插件 3.接口层 该层包括:SqlSession 基础支持层 该层保护mybatis的基础模块,它们为核心处理层提供了良好的支撑。