本章内容:
▪️Spring Security介绍
▪️使用Servlet规范中的Filter保护Web应用
▪️基于数据库和LDAP进行认证
Spring Security是为基于Spring的应用程序提供声明式安全保护的安全性框架。Spring Security提供了完整的安全性解决方案,它能够在Web请求级别和方法调用级别处理身份认证和授权。因为基于Spring框架,所以Spring Security充分利用了依赖注入(dependency injection,DI)和面向切面的技术。
最初,Spring Security被称为Accegi Security。到了2.0版本,Accegi Security更名为Spring Security。但是2.0发布版本所带来的不仅仅是表面上名字的变化。为了在Spring中配置安全性,Spring Security引入了一个全新的、与安全性相关的XML命名空间。这个新的命名空间连同注解和一些合理的默认设置,将典型的安全性配置从几百行XML减少到几十行。Spring Security 3.0融入了SpEl,这进一步简化了安全性的配置。
它的最新版本为3.2,Spring Security从两个角度来解决安全性问题。它使用Servlet规范中的Filter保护Web请求并限制URL级别的访问。Spring Security还能够使用Spring AOP保护方法调用---借助于对象代理和使用通知,能够确保只有具备适当权限的用户才能访问安全保护的方法。
在本章中,我们将会关注如何将Spring Security用于Web层的安全性之中。在稍后的第14章中,我们会重新学习Spring Security,了解它如何保护方法的调用。
不管你想使用Spring Security保护哪种类型的应用程序,第一件需要做的事就是将Spring Security模块添加到应用程序类路径下。Spring Security 3.2分为11个模块,如表9.1所示:
▪️ACL:支持通过访问控制列表(access contol list,ACL)为域对象提供安全性。
▪️切面(Aspects):一个很小的模块,当使用Spring Security注解时,会使用基于AspectJ的切面,而不是使用标准的Spring AOP。
▪️CAS客户端(CAS Client):提供与Jasig的中心认证服务(Central Authentication Service,CAS)进行集成的功能。
▪️配置(Configuration):包含通过XML和Java配置Spring Security的功能支持。
▪️核心(Core):提供Spring Security基本库。
▪️加密(Cryptography):提供了加密和密码编码的功能。
▪️LDAP:支持基于OpenID进行集中式认证。
▪️Remoting:提供了对Spring Remoting的支持。
▪️标签库(Tag Library):Spring Security的JSP标签库。
▪️Web:提供了Spring Security基于Filter的Web安全性支持。
应用程序的类路径下至少要包含Core和Configuration这两个模块。Spring Security经常被用于保护Web应用,这显然也是Spittr应用的场景,所以我们还需要添加Web模块。同时我们还会用到Spring Security的JSP标签库,所以我们需要将这个模块也添加进来。
现在,我们已经为在Spring Security中进行安全性配置做好了准备。让我们看看如何使用Spring Security的XML命名空间。
Spring Security借助一系列Servlet Filter来提供各种安全性功能。你可能会想,这是否意味着我们需要在web.xml或WebApplicationInitializer中配置多个Filter呢?实际上,借助于Spring的小技巧,我们只需配置一个Filter就可以了。
DelegatingFilterProxy是一个特殊的Servlet Filter,它本身所做的工作并不多。只是将工作委托给一个javax.servlet.Filter实现类,这个实现类作为一个注册在Spring应用的上下文中,如图9.1所示。
如果你喜欢在传统的web.xml中配置Servlet和Filter的话,可以使用元素,如下所示:
在这里,最重要的是设置成了SpringSecurityFilterChain。这是因为我们马上就会将Spring Security配置在Web安全性之中,这里会有一个名为SpringSecurityFilterChain的Filter bean,DelegatingFilterProxy会将过滤逻辑委托给它。
如果你希望借助WebApplicationInitializer以Java的方式来配置DelegatingFilterProxy的话,那么我们所需要做的就是创建一个扩展的新类:
AbstractSecurityWebApplicationInitializer实现了WebApplicationInitializer,因此Spring会发现它,并用它在Web容器中注册DelegatingFilterProxy。尽管我们可以override它的appendFilters()或insertFilters()来注册资金选择的Filter,但是要注册DelegatingFilterProxy的话我们并不需要重载任何方法。
不管我们通过web.xml还是通过AbstractSecurityWebApplicationInitializer的子类来配置DelegatingFilterProxy,它都会拦截发往应用中的请求,并将请求委托给ID为springSecurityFilterChain的bean。
springSecurityFilterChain本身是另一个特色的Filter,它也被称为FilterChainProxy。它可以链接任意一个或多个其他的Filter。Spring Security依赖一系列Servlet Filter来提供不同的安全特性。但是,你几乎不需要知道这些细节,因为你不需要显式声明springSecurityFilterChain以及它所链接在一起的其他Filter。当我们启用Web安全性的时候,会自动创建这些Filter。
为了让Web安全性运行起来,我们创建一个简单的安全性配置。
在Spring Security的早期版本中(在其还被称为Accegi Security之时),为了在Web应用中启用简单的安全功能,我们需要编写上百行的XML配置。Spring Security 2.0提供了安全性相关的XML配置命名空间,让情况有了一些好转。
Spring 3.2引入了新的Java配置方案,完全不再需要通过XML来配置安全性功能了。如下的程序清单展现了Spring Security最简单的Java配置。
顾名思义,@EnableWebSecurity注解将会启用Web安全功能。但它本身并没有什么用处,Spring Security必须配置在一个实现了WebSecurityConfugurer的bean中,或者(简单起见)扩展WebSecurityConfigurerAdater。在Spring应用上下文中,任何实现了WebSecurityConfigurer的bean都可以用来配置Spring Security,但是最为简单的方式还是像程序清单9.1那样扩展WebSecurityConfigurerAdapter类。
@EnableWebSecurity可以启用任意Web应用的安全性功能,不过,如果你的应用碰巧是使用Spring MVC开发的,那么就应该考虑使用@EnableWebMvcSecurity替代它,如程序清单9.1所示。
除了其他的内容以外,@EnableWebMvcSecurity注解还配置了一个Spring MVC参数解析器(argument resolver),这样的话处理器方法就能够通过带有@AuthenticationPrincipal注解的参数获得认证用户的principal(或username)。它同时还配置了一个bean,在使用Spring表单绑定标签库来定义表单时,这个bean会自动添加一个隐藏的跨站请求伪造(cross-site request forgery,CSRF)token输入域。
看起来似乎并没有做太多的事情,但程序清单9.1和9.2中的配置类会给应用产生很大的影响。其中任何一种配置都会将应用严格锁定,导致没有人能够进入该系统了!
尽管部署严格要求的,但是我们可能希望指定Web安全的细节,这要通过override WebSecurityConfigurerAdapter中的一个或多个方法来实现。我们可以通过override WebSecurityConfigurerAdapter的三个configure()方法来配置Web安全性,这个过程中会使用传递进来的参数设置行为。表9.2描述了这三个方法。
▪️configure(WebSecurity):通过override,配置Spring Security的Filter链。
▪️configure(HttpSecurity):通过override,配置如何通过拦截器保护请求。
▪️configure(AuthenticationManagerBuilder):通过override,配置user-detail服务。
让我们重新看一下程序清单9.2,可以看到它没有override上述三个configure()方法中的任何一个,这就说明了为什么应用现状是被锁定的。尽管对于我们的需求来讲默认的Filter链是不错的,但是默认的configure(HttpSecurity)实际上等同于如下所示。
这个简单的默认配置指定了该如何保护HTTP请求,以及客户端认证用户的方案。通过调用authorizeRequests()和anyRequest().authenticated()就会要求所有进入应用的HTTP请求都要进行认证。它也配置Spring Security支持基于表单的登录以及HTTP Basic方式的认证。
同时,因为没有override configure(AuthenticationManagerBuilder)方法,所以没有用户存储支持认证过程。没有用户存储。实际上就等于没有用户。所以,在这里所有的请求都需要认证,但是没有人能够登录成功。
为了让Spring Security满足我们应用的需求,还需要再添加一点配置。具体来讲,我们需要:
▪️配置用户存储;
▪️指定哪些请求需要认证,哪些请求不需要认证,以及所需要的权限;
▪️提供一个自定义的登录页面,替代原来简单的默认登录页。
除了Spring Security的这些功能,我们可能还希望基于安全限制,有选择性地在Web视图上显示特定的内容。
但首先,我们看一下如何在认证的过程中配置访问用户数据的服务。
我们所需要的是用户存储,也就是用户名、密码以及其他信息存储的地方,在进行认证决策的时候,会对其进行检索。
好消息是,Spring Security非常灵活、能够基于各种数据存储来认证用户,在进行了多种常见用户存储场景,如内存、关系型数据库以及LDAP。但我们也可以编写并插入自定义的用户存储实现。
借助Spring Security的Java配置,我们能够很容易地配置一个或多个数据存储方案。那我们就从最简单的开始:在内存中维护用户存储。
因为我们的安全配置扩展了WebSecurityConfigurerAdapter,因此配置用户存储的最简单方式就是overrideconfigure()方法,并以AuthenticationManagerBuilder作为传入参数。AuthenticationManagerBuilder有多个方法可以用来配置Spring Security对认证的支持。通过inMemoryAuthentication()方法,我们可以启用、配置并任意填充基于内存的用户存储。
例如,在如程序清单9.3中,SecurityConfig override了configure()方法,并使用两个用户来配置内存用户存储。
操作补充:
我们可以看到configure()方法中的AuthenticationManagerBuilder使用构造者风格的接口来构建风格的接口来构建认证配置。通过简单地调用inMemoryAuthentication()就能启用内存用户存储。但是我们还需要有一些用户,否则的话,这和没有用户并没有什么区别。
因此,我们需要调用withUser()方法返回的是UserDetailsManagerConfigurer.UserDetailsBuilder,这个对象提供了多个进一步配置用户的方法,包括设置用户密码的password()方法以及为给定用户授予一个或多个角色权限的roles()方法。
在代码清单9.3中,我们添加了两个用户,“user”和“admin”,密码均为“password”。“user”用户具有USER角色,而“admin”用户具有ADMIN和USER两个角色。我们可以看到,and()方法能够将多个用户的配置连接起来。
除了password()、roles()和and()方法以外,还有其他的几个方法可以用来配置内存用户存储中的用户信息。以下描述了UserDetailsManagerCondigurer.UserDetailsBuilder对象所有可用的方法。
方法 描述
accountExpired(boolean) 定义账号是否已经过期
accountLocked(boolean) 定义账号是否已经锁定
and() 用来连接配置
authorities(GrantedAuthority...) 授予某个用户一项或多项权限
authorities(List) 授予某个用户一项或多项权限
authorities(String...) 授予某个用户一项或多项权限
credentialsExpired(boolean) 定义凭证是否已经过期
disabled(boolean) 定义账号是否已被禁用
password(String) 定义用户的密码
username(String) 定义用户的用户名
roles(String...) 授予某个用户一项或多项角色
对于调试和开发人员测试来讲,基于内存的用户存储是很有用的,但是对于生成级别的应用来讲,这就不是最理想的可选方案了。为了用于生产环境,通常最好讲用户数据报错在某种类型的数据库之中。
用户数据通常会存储在关系型数据库中,并通过JDBC进行访问。为了配置Spring Security使用以JDBC为支撑的用户存储,我们可以使用jdbcAuthentication()方法,所需的最少配置如下所示:
我们必须要配置的只是一个DataSource,这样的话,就能访问关系型数据库了。在这里,DataSource是通过自动装配的技巧得到的。
尽管默认的最少配置能够让一切运转起来,但是它对我们的数据库模式有一些要求,它预期存在某些存储用户数据的表。下面的代码片段来源于Spring Security内部,这块代码展现了当查找用户信息时所执行的SQL查询语句:
在第一个查询中,我们获取了用户的用户名、密码以及是否启用的信息,这些信息会用来进行用户认证。接下来的查询查找了用户所授予的权限,用来进行鉴权,最后一个查询中,查找了用户作为群组成员所授予的权限。
补充:源码中的建表SQL
如果你能够在数据库中定义和填充满足这些查询的表,那么基本上就不需要你再做什么额外的事情了。但是,也有可能你的数据库与上面所述并不一致,那么你就会希望在查询上有更多的控制权。如果是这样的话,我们可以按照如下的方式配置自己的查询:
在本例中,我们只重写了认证和基本权限的查询语句,但是通过调用groupAuthoritiesByUsername()方法,我们也能够讲群组权限重写为自定义的查询语句。
将默认的SQL查询替换为自定义的设计时,很重要的一点就是要遵循查询的基本协议。所有查询都将用户名作为唯一的参数。认证查询会选取用户名、密码以及启用状态信息。权限查询会选取零行或多行数据,每行数据中都会包含群组ID、群组名称以及权限。
补充:
看一下上面的认证查询,它会预期用户密码存储在了数据库之中。这里唯一的问题在于如果密码明文存储的话,会很容易受到黑客的窃取。但是,如果数据库中的密码进行了转码的话,那么认证就会失败,因为它与用户提交的明文密码并不匹配。
为了解决这个问题,我们需要借助passwordEncoder()方法指定一个密码转码器(encoder)。
passwordEncoder()方法可以接受Spring Security中PasswordEncoder接口的任意实现。Spring Security的加密模块包括了三个这样的实现:BCryptPasswordEncoder、NoOpPasswordEncoder和StandardPasswordEncoder。
上述的代码中使用了StandardPasswordEncoder,但是如果内置的实现无法满足需求时,你可以提供自定义的实现。PasswordEncoder接口非常简单:
不管你使用哪一个密码转码器,都需要理解的一点是,数据库中的密码是永远不会解码的。所采取的策略与之相反,用户在登录时输入的密码会按照相同的算法进行转码,然后再与数据库中已经转码过的密码进行对比。这个比对是在PasswordEncoder的matchers()方法中进行的。
假设我们需要认证的用户存储在非关系型数据库中,如Mongo或Neoj4,在这种情况下,我们需要提供一个自定义的UserDetailsService接口实现。
UserDetailsService接口非常简单:
我们所需要做的就是实现loadUserByUsername()方法,根据给定的用户名来查找用户。loadUserByUsername()方法会返回代表给定用户的UserDetails对象。如下的程序清单展现了一个UserDetailsService的实现,它会从给定的SpitterRepository实现中查找用户。
UserServiceImpl有意思的地方在于它并不知道用户数据存储在什么地方。设置进来的userDao能够从关系型数据库,文档数据库或图数据中查找UserEntry对象,甚至可以伪造一个。UserServiceImpl不知道也不会关心底层所使用的数据存储。它只是获得UserEntry对象,并使用它来创建User对象。(User是UserDetails的具体实现)。
为了使用UserServiceImpl来认证用户,我们可以通过userDetailsService()方法将其设置倒安全配置中:
userDetailsService()方法(类似于jdbcAuthentication()、ldapAuthentication()以及inMemoryAuthentication())会配置一个用户存储。不过,这里所使用的不是Spring所提供的用户存储,而是使用UserDetailsService的实现。
另外一个值得考虑的方案就是修改UserEntry,让其实现UserDetails。这样的话loadUserByUsername()就能直接返回UserEntry对象了,而不必再将它的值复制到 User对象中。
在前面的9.1.3小节中,我们看到一个特别简单的Spring Security配置,在这个默认的配置中,回要求所有请求都要经过认证。有些人可能会说,过多的安全性总比安全性太少要好。但也有一种说法就是要适量地应用安全性。
在任何应用中,并不是所有的请求都需要同等程度地保护。有些请求需要认证,而另一些可能并不需要。有些请求可能只有具备特定权限的用户才能访问,没有这些权限的用户无法访问。
例如,考虑Spittr应用的请求。首先当然是公开的,不需要进行保护。类似地,因为所有的Spittle都是公开的,所以展现Spittle的页面不需要安全性。但是,创建Spittle的请求只有认证用户才能知晓。同样,尽管用户基本信息页面是公开的,不需要认证,但是,如果要处理“/spitter/me”请求,并展现当前用户的基本信息时,那么就需要进行认证,从而确定要展现谁的信息。
对每个请求进行细粒度安全性控制的关键在于overrideconfigure(HttpSecurity)方法。如下的代码片段展现了override的configure(HttpSecurity)方法,它为不同的URL路径有选择地应用安全性:
configure()方法中得到的HttpSecurity对象可以在多个方面配置HTTP的安全性。在这里,我们首先调用authorizeRequests(),然后调用该方法所返回的对象的方法来配置请求级别的安全性细节。其中,第一次调用antMatchers()更为具体,说明对"security"路径的请求需要进行认证。第二次调用antMatchers()更为具体,说明对"/spittle"路径的HTTP POST请求必须要经过认证。最后对anyRequests()的调用中,说明其他所有的请求都是允许的,不需要认证和任何的权限。
antMatchers()方法中设定的路径支持Ant风格的通配符。在这里我们并没有这样使用,但是也可以使用通配符来指定路径,如下所示:
.antMatchers("/security/**").authenticated();
我们也可以在一个对antMatchers()方法的调用中指定多个路径:
.antMatchers("/security/**","/security/test").authenticated();
antMatchers()方法所使用的路径可能会包括Ant风格的通配符,而regexMatchers()方法则能够接受正则表达式来定义请求路径。例如,如下代码片段所使用的正则表达式与"/security/**"(Ant风格)功能是相同的:
.regexMatchers("/security/.**").authenticated();
除了路径选择,我们还通过authenticated()和permitAll()来定义该如何保护路径。authenticated()要求在执行该请求时,必须已经登录了应用。如果用户没有认证时,permitAll()方法允许请求没有任何的安全限制。
除了authenticated()和permitAll()以外,还有其他的一些方法能够涌来定义该如何保护请求,如下表描述了所有可用的方案。
定义如何保护路径的配置方法
方法 能够做什么
access(String) 如果给定的SpEL表达式计算结果为true,就允许访问
anonymous() 允许匿名用户访问
authenticated() 允许经过认证的用户访问
denyAll() 无条件拒绝所有访问
fullyAuthenticated() 如果用户是完整的话(不是通过Remember-me功能认证的),就允许访问
hasAnyAuthority(String...) 如果用户具备给定权限中的某一个的话,就允许访问
hasAnyRole(String...) 如果用户具备给定角色中的某一个的话,就允许访问
hasAuthority(String) 如果用户具备给定权限的话,就允许访问
hasIpAddress(String) 如果请求来自给定IP地址的话,就允许访问
hasRole(String) 如果用户具备给定角色的话,就允许访问
not() 对其他访问方法的结果球反
permitAll() 无条件允许访问
rememberMe() 如果用户是通过Remember-me功能认证的,就允许访问
通过使用上表中的方法,我们所配置的安全性能够不仅仅限于认证用户。例如,我们可以修改之前的configure()方法,要求用户不仅需要认证,还要具备ROLE_SECURITY权限:
作为替代方案,我们还可以使用hasRole()方法,它会自动适应“ROLE_”前缀:
我们可以将任意数量的antMatchers()、regexMatchers()和anyRequest()连起来,以满足Web应用安全规则的需要。但是,我们需要知道,这些规则会按照给定的顺序发挥作用。所以说,很重要的一点就是将最为具体的请求路径放在前面,而最不具体的路径(anyReuquest())放在最后面。如果不这样的话,那部具体的路径配置将会覆盖掉更为具体的路径配置。
上表中的大多数法官法都是一维的,也就是说我们可以使用hasRole()限制某个特定的角色,但是我们不能在相同的路径上同时通过hasIpAddress()限制特定的IP地址。
另外,除了上表定义的方法以外,我们没有方法使用其他的条件。如果我们希望限制某个角色智能在星期二进行访问的话,该怎么办呢?
在第3章中,我们看到了如何使用Spring表达式语言(Spring Expression Language,SpEl),将其作为装配bean属性的高级技师。借助access()方法,我们也可以将SpEL作为声明访问限制的一种方式。例如,如下就是使用SpEL表达式来声明声明具有“ROLE_SECURITY”角色才能访问"/security/test"URL:
.antMatchers("/security/test").access("hasRole('ROLE_SECURITY')")
这个对"/security/test"的安全限制与开始时代效果时等价的,只不过这里使用了SpEL来描述安全规则。如果当前用户呗授予了给定角色的话,那hasRole()表达式的计算结果就为true。
让SpEL更强大的原因在于,hasRole()仅是Spring支持的安全相关表达式中的一种,下表列出了Spring Security支持的所有SpEL表达式。
Spring Security通过一些安全性相关的表达式扩展了Spring表达式语言
安全表达式 计算结果
authentication 用户的认证对象
denyAll 结果始终为false
hasAnyRole(list of roles) 如果用户被授予了列表中任意的指定角色,结果为true
hasRole(role) 如果用户被授予了指定的角色,结果为true
hasIpAddress(IP Address) 如果请求来自指定IP的话,结果为true
isAnonymous() 如果当前用户为匿名用户,结果为true
isAuthenticated() 如果当前用户进行了认证的话,结果为true
isFullyAuthenticated() 如果当前用户进行了完整认证的话(不是Remember-me),结果为true
isRememberMe() 如果当前用户是通过Remember-me自动认证的,结果为true
permitAll 结果始终为true
principal 用户的principal对象
在掌握了Spring Security的SpEL表达式后,我们就能够不再局限于机遇用户的权限逆袭访问限制了。例如,如果你想限制"/security/test" URL的访问,不仅需要ROLE_SECURITY,还需要来自指定的IP地址,那么我们可以按照如下的方式调用access()方法。
我们可以使用SpEL实现各种各样的安全性限制。我敢打赌,你已经在想象基于SpEL所能实现的那些有趣的安全性限制了。
但现在,让我们看一下Spring Security拦截请求的另外一种方式:强制通道的安全性。
使用HTTP提交数据是一件具有风险的事情。如果使用HTTP发送无关紧要的信息,这可能不是什么大问题。但是如果你通过HTTP发送诸如密码和信用卡号这样的敏感信息的话,那你就是在找麻烦了。通过HTTP发送的数据没有经过加密,黑客就有机会拦截请求并且能够看到他们想看的数据。这就是为什么敏感信息要通过HTTPS来加密发送的原因。
使用HTTPS似乎很简单。你要的事情知识在URL中的HTTP后加上一个字母“s”就可以了。是这样吗?
这是真的,但这是把使用HTTPS通道的责任放在了错误的地方。通过添加“s”我们就能很容易地实现页面的安全性,但是忘记添加“s”同样也是很容易出现的。如果我们的应用中有多个链接需要HTTPS,估计在其中的一两个上忘记添加“s”的概率还是很高的。
另一方面,你可能还会在原本并步需要HTTPS的地方,误用HTTPS。
传递到configure()方法中的HttpSecurity对象,除了具有authorizeRequests()方法以外,还有requiresChannel()方法,借助这个方法能够为各种URL模式声明所要求的通道。
作为示例,可以参考security应用的注册表单。尽管security应用步需要信用卡号、社会保障好或其他敏感的信息,但用户有可能仍然希望信息是私密的。为了保证注册表单的数据通过HTTPS传递,我们可以在配置中添加requiresChannel()方法,如下所示:
不论何时,只要是对"/security/form"的请求,Spring Security都视为需要安全通道(通过调用requiresChannel()确定的)并自动将请求重定向到HTTPS上。
与之相反,有些页面并不需要通过HTTPS传送。例如,首页包含任何敏感信息,因此并步需要通过HTTPS传送。我们可以使用requiresInsecure()代替requiresSecure()方法,将首页声明为始终通过HTTP传送:
.antMatchers("/").requiresInsecure();
如果通过HTTPS发送了对“/”的请求,Spring Security将会把请求重定向到不安全的HTTP通道上。
在强制要求通道时,路径的选取方案与authorizeRquests()是相同的。使用了antMatchers(),但是我们也可以使用regexMatchers()方法,通过正则表达式选取路径模式。
对于应用程序的人类用户来说,基于表单的认证是比较理想的。但是在第16章中,将会看到如何将我们Web应用的页面转化为RESTful API。当应用程序的使用者是另外一个应用程序的话,使用表单来提示登录的方式就不太合适了。
HTTP Basic认证(HTTP Basic Authentication)会直接通过HTTP请求本书,对药访问应用程序的用户进行认证。你可能在以前见过HTTP 401响应,表明必须要在请求中包含一个用户名和密码。在REST客户端向它使用的服务进行认证的场景中,这种方式比较合适。
如果要启用HTTP Basic认证的话,只需在configure()方法所传入的HttpSecurity对象调用httpBasic()即可。另外,还可以通过调用realmName()方法指定域。如下是在Spring Security中启用HTTP Basic认证的典型配置:
注意:和前面意义,在configure()方法中,通过调用and()方法来将不同的配置指令连接在一起。
这里我们要操作的是FilterSecurityInterceptor这个interceptor,使用withObjectPostProcessor来设置。
这个filters有几个要素,如下:
▪️SecurityMetadataSource
▪️AccessDecisionManager
▪️AuthenticaManager
可以根据情况自己去重新设置,这里我们重写一下SecurityMetadataSource用来动态获取url权限配置,还有AccessDecisionManager来进行权限判断。
这里遍历判断url所需的角色看用户是否具备,有具备则返回,都不具备则AccessDeniedException异常。
MyFilterInvocationSecurityMetadataSource
这里以内存的map来展示一下,实际应用可以从分布式配置中心或者数据库中读区,另外循环遍历这个可能很消耗性能,必要时得优化一下。
▪️异常:IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
spring security 5的密码存储格式改了,要求我们提供一个密码编码器。我们必须进行如下配置,数据库保存的密码必须是加密之后的。
如果需要給我修改意见的发送邮箱:erghjmncq6643981@163.com
本博客的代码示例已上传GitHub:分布式配置中心
资料参考:《Spring in cation》,《Spring Cloud 微服务实战》
转发博客,请注明,谢谢。