Spring Security
学习 Spring Security 用法,架构、设计模式等。
核心组件
主要介绍 Spring Security
中常见核心 Java
类以及他们之间的依赖关系,以及整个架构的设计原理。
SecurityContextHolder
SecurityContextHolder
用于存储安全上下文(security context)
的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保存在 SecurityContextHolder
中。SecurityContextHolder
默认使用 ThreadLocal
策略来存储认证信息。看到 ThreadLocal
也就意味着,这是一种与线程绑定的策略。Spring Security
在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息。但这一切的前提,是你在 web
场景下使用 Spring Security
。
获取当前用户的信息
因为身份信息是与线程绑定的,所以可以在程序的任何地方使用静态方法获取用户信息。一个例子如下:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
getAuthentication()
返回了认证信息,再次 getPrincipal()
返回了身份信息,UserDetails
便是 Spring
对身份信息封装的一个接口。Authentication
和 UserDetails
的介绍在下面的小节具体讲解,本节重要的内容是介绍 SecurityContextHolder
这个容器。
Authentication
直接上源码:
package org.springframework.security.core;// <1>
public interface Authentication extends Principal, Serializable { // <1>
Collection extends GrantedAuthority> getAuthorities(); // <2>
Object getCredentials();// <2>
Object getDetails();// <2>
Object getPrincipal();// <2>
boolean isAuthenticated();// <2>
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
<1> Authentication 是 spring security 包中的接口,直接继承自 Principal 类,而 Principal 是位于 java.security 包中的。可以见得,Authentication 在 spring security 中是最高级别的身份/认证的抽象。
<2> 由这个顶级接口,我们可以得到用户拥有的权限信息列表,密码,用户细节信息,用户身份信息,认证信息。
还记得1.1节中,authentication.getPrincipal()返回了一个 Object,我们将 Principal 强转成了 Spring Security 中最常用的UserDetails,这在 Spring Security 中非常常见,接口返回 Object,使用 instanceof 判断类型,强转成对应的具体实现类。接口详细解读如下:
- getAuthorities(),权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系列字符串。
- getCredentials(),密码信息,用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。
- getDetails(),细节信息,web 应用中的实现接口通常为 WebAuthenticationDetails,它记录了访问者的 ip 地址和 sessionId 的值。
- getPrincipal(),敲黑板!!!最重要的身份信息,大部分情况下返回的是 UserDetails 接口的实现类,也是框架中的常用接口之一。UserDetails 接口将会在下面的小节重点介绍。
Spring Security是如何完成身份认证的?
- 用户名和密码被过滤器获取到,封装成
Authentication
,通常情况下是UsernamePasswordAuthenticationToken
这个实现类。 -
AuthenticationManager
身份管理器负责验证这个Authentication
- 认证成功后,
AuthenticationManager
身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication
实例。 -
SecurityContextHolder
安全上下文容器将第3步填充了信息的Authentication
,通过SecurityContextHolder.getContext().setAuthentication(…)
方法,设置到其中。
这是一个抽象的认证流程,而整个过程中,如果不纠结于细节,其实只剩下一个 AuthenticationManager
是我们没有接触过的了,这个身份管理器我们在后面的小节介绍。
AuthenticationManager
初次接触 Spring Security
的朋友相信会被 AuthenticationManager
,ProviderManager
,AuthenticationProvider
…这么多相似的 Spring
认证类搞得晕头转向,但只要稍微梳理一下就可以理解清楚它们的联系和设计者的用意。AuthenticationManager
(接口)是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的操作?没想到吧),所以说 AuthenticationManager
一般不直接认证,AuthenticationManager
接口的常用实现类 ProviderManager
内部会维护一个 List
列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate
)。也就是说,核心的认证入口始终只有一个:AuthenticationManager
,不同的认证方式:用户名+密码(UsernamePasswordAuthenticationToken
),邮箱+密码,手机号码+密码登录则对应了三个 AuthenticationProvider
。这样一来四不四就好理解多了?熟悉 shiro
的朋友可以把AuthenticationProvider
理解成 Realm
。在默认策略下,只需要通过一个 AuthenticationProvider
的认证,即可被认为是登录成功。
ProviderManager 关键部分源码
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
// 维护一个AuthenticationProvider列表
private List providers = Collections.emptyList();
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
// 依次认证
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
...
catch (AuthenticationException e) {
lastException = e;
}
}
// 如果有Authentication信息,则直接返回
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
//移除密码
((CredentialsContainer) result).eraseCredentials();
}
//发布登录成功事件
eventPublisher.publishAuthenticationSuccess(result);
return result;
}
...
//执行到此,说明没有认证成功,包装异常信息
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
prepareException(lastException, authentication);
throw lastException;
}
}
ProviderManager
中的 List
,会依照次序去认证,认证成功则立即返回,若认证失败则返回 null
,下一个 AuthenticationProvider
会继续尝试认证,如果所有认证器都无法认证成功,则 ProviderManager
会抛出一个ProviderNotFoundException
异常。
以上已经把 Spring Security 的整个认证流程都讲述了一遍,简单小结如下:身份信息的存放容器 SecurityContextHolder
,身份信息的抽象 Authentication
,身份认证器 AuthenticationManager
及其认证流程。姑且在这里做一个分隔线。下面来介绍下 AuthenticationProvider
接口的具体实现。
DaoAuthenticationProvider
AuthenticationProvider
最最最常用的一个实现便是 DaoAuthenticationProvider
。顾名思义,Dao
正是数据访问层的缩写,也暗示了这个身份认证器的实现思路。由于本文是一个 Overview
,姑且只给出其 UML
类图:
按照我们最直观的思路,怎么去认证一个用户呢?用户前台提交了用户名和密码,而数据库中保存了用户名和密码,认证便是负责比对同一个用户名,提交的密码和保存的密码是否相同便是了。在Spring Security
中。提交的用户名和密码,被封装成了UsernamePasswordAuthenticationToken
,而根据用户名加载用户的任务则是交给了 UserDetailsService
,在DaoAuthenticationProvider
中,对应的方法便是 retrieveUser
,虽然有两个参数,但是 retrieveUser
只有第一个参数起主要作,返回一个 UserDetails
。还需要完成 UsernamePasswordAuthenticationToken
和 UserDetails
密码的比对,这便是交给 additionalAuthenticationChecks
方法完成的,如果这个 void
方法没有抛异常,则认为比对成功。比对密码的过程,用到了PasswordEncoder
和 SaltSource
,密码加密和盐的概念相信不用我赘述了,它们为保障安全而设计,都是比较基础的概念。
如果你已经被这些概念搞得晕头转向了,不妨这么理解 DaoAuthenticationProvider
:它获取用户提交的用户名和密码,比对其正确性,如果正确,返回一个数据库中的用户信息(假设用户信息被保存在数据库中)。
UserDetails 与 UserDetailsService
上面不断提到了 UserDetails
这个接口,它代表了最详细的用户信息,这个接口涵盖了一些必要的用户信息字段,具体的实现类对它进行了扩展。
public interface UserDetails extends Serializable {
Collection extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
它和 Authentication
接口很类似,比如它们都拥有 username
,authorities
,区分他们也是本文的重点内容之一。Authentication
的 getCredentials()
与 UserDetails
中的 getPassword()
需要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码,认证器其实就是对这两者的比对。Authentication
中的 getAuthorities()
实际是由 UserDetails
的 getAuthorities()
传递而形成的。还记得 Authentication
接口中的 getUserDetails()
方法吗?其中的 UserDetails
用户详细信息便是经过了 AuthenticationProvider
之后被填充的。
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
UserDetailsService
和 AuthenticationProvider
两者的职责常常被人们搞混,关于他们的问题在文档的 FAQ
和 issues
中屡见不鲜。记住一点即可,敲黑板!!!UserDetailsService
只负责从特定的地方(通常是数据库)加载用户信息,仅此而已,记住这一点,可以避免走很多弯路。UserDetailsService
常见的实现类有 JdbcDaoImpl
,InMemoryUserDetailsManager
,前者从数据库加载用户,后者从内存中加载用户,也可以自己实现 UserDetailsService
,通常这更加灵活。
架构概览图
为了更加形象的理解上述我介绍的这些核心类,附上一张按照我的理解,所画出 Spring Security
的一张非典型的 UML
图
整个Spring Security 都是围绕以上这张架构图展开的,最顶层为最核心最抽象的 AuthenticationManager
接口, ProviderManager
为 AuthenticationManager
的一个具体实现,功能如其名字他的作用是管理 Provider
的, AuthenticationProvider
才是真正认证的接口,因此我们在实践中要实现我们自己的认证方式,也就是 AuthenticationProvider
的一个具体实现。当然可以实现多个,如果有多种认证方式,现实中往往也是有多种认证方式。
注意 AuthenticationProvider
接口中的 supports
方法,ProviderManager
里面其实是维护了一个 AuthenticationProvider
的 List
因此到底使用哪个 Provider
来做验证,可以使用 Filter
返回的 Authentication
实现类来限定,部分代码如下:
//Filter 代码
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
......
return getAuthenticationManager().authenticate(new JwtAuthenticationToken(token));
}
//Provider 代码
@Override
public boolean supports(Class> authentication) {
return (JwtAuthenticationToken.class.isAssignableFrom(authentication));
}
//在 ProviderManager 中验证时有如下代码
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
如果对Spring Security
的这些概念感到理解不能,不用担心,因为这是 Architecture First
导致的必然结果,先过个眼熟。后续的文章会秉持 Code First
的理念,陆续详细地讲解这些实现类的使用场景,源码分析,以及最基本的:如何配置 Spring Security
,在后面可以时不时往回看一看,找到具体的类在整个架构中所处的位置。另外,一些 Spring Security
的过滤器还未囊括在架构概览中,如将表单信息包装成 UsernamePasswordAuthenticationToken
的过滤器,考虑到这些虽然也是架构的一部分,但是真正重写他们的可能性较小,所以打算放到后面讲解。
配置介绍
Security 安全核心配置例子:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
当配置了上述的javaconfig之后,我们的应用便具备了如下的功能:
- 除了“/”,”/home”(首页),”/login”(登录),”/logout”(注销),之外,其他路径都需要认证。
- 指定“/login”该路径为登录页面,当未认证的用户尝试访问任何受保护的资源时,都会跳转到“/login”。
- 默认指定“/logout”为注销页面
- 配置一个内存中的用户认证器,使用admin/admin作为用户名和密码,具有USER角色
- 防止CSRF攻击
- Session Fixation protection
- Security Header(添加一系列和Header相关的控制)
- HTTP Strict Transport Security for secure requests
- 集成X-Content-Type-Options
- 缓存控制
- 集成X-XSS-Protection.aspx)
- X-Frame-Options integration to help prevent Clickjacking(iframe被默认禁止使用)
- 为Servlet API集成了如下的几个方法
- HttpServletRequest#getRemoteUser())
- HttpServletRequest.html#getUserPrincipal())
- HttpServletRequest.html#isUserInRole(java.lang.String))
- HttpServletRequest.html#login(java.lang.String, java.lang.String))
- HttpServletRequest.html#logout())
一个 Restful 配置
动态从数据库加载权限,这边因为 Spring Security
的机制是在模块启动的时候进行加载的,如果想要动态 reload
权限,调查来看Spring 并没有提供相关的接口,需要动态 reload Spring
相关的 bean
是一种比较危险暴力的做法,需要多加注意。
@Override
protected void configure(HttpSecurity http) throws Exception {
List permitAllEndpointList = Arrays.asList(AUTHENTICATION_URL,"/api/*/webjars/**","/api/*/swagger**","/api/*/swagger-resources/**","/api/*/v2/api-docs");
//load Authorities from DB
ResponseData> authorityRules = authorityService.getAuthority();
try {
for (AuthorityModel rule : authorityRules.getData()) {
//这里因为 Restful API url 有相同的情况,因此需要URL和方法名组合来区分,注意一种特殊情况,
//因为是使用Ant语法来匹配URL,如果出现请求方法相同且URL是匹配子集关系时,要把最具体的URL放在
//前面,比如同是 GET 方法,Api1:/api/user/paged 和 Api2:/api/user/{id},在Ant语法里面
//Api2是能匹配Api1的,如果用户有Api2的权限而没有Api1的权限,在如下初始化权限时Api2初始化的
//顺序在Api1的前面,就会导致用户即使没有Api1的权限也能访问,因此要确保数据库加载的时候Api1在Api2
//前面
if (HttpMethod.POST.name().equals(rule.getMethod().name())) {
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, rule.getPattern()).hasAuthority(rule.getSystemName());
} else if (HttpMethod.GET.name().equals(rule.getMethod().name())) {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, rule.getPattern()).hasAuthority(rule.getSystemName());
} else if (HttpMethod.PUT.name().equals(rule.getMethod().name())) {
http
.authorizeRequests()
.antMatchers(HttpMethod.PUT, rule.getPattern()).hasAuthority(rule.getSystemName());
} else if (HttpMethod.DELETE.name().equals(rule.getMethod().name())) {
http
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, rule.getPattern()).hasAuthority(rule.getSystemName());
}
}
} catch (Exception e) {
log.error("", e);
}
http
.csrf().disable() // We don't need CSRF for JWT based authentication
.exceptionHandling().authenticationEntryPoint(this.authenticationEntryPoint)
//自定义没有权限时的处理,一般根据业务封装自定义的返回结果
.accessDeniedHandler(ajaxAccessDeniedHandler)
.and()
//Restful API 完全无状态,使用JWT token
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//定义哪些API不需要认证,比如获取 Token 的接口
.authorizeRequests()
.antMatchers(permitAllEndpointList.toArray(new String[permitAllEndpointList.size()])).permitAll()
.and()
// Protected API End-points
.authorizeRequests()
.antMatchers(API_ROOT_URL, API_ATTACHMENT_URL).authenticated()
.and()
//处理跨域过滤器
.addFilterBefore(new CustomCorsFilter(), UsernamePasswordAuthenticationFilter.class)
//处理登录获取token的过滤器
.addFilterBefore(buildAjaxLoginProcessingFilter(AUTHENTICATION_URL), UsernamePasswordAuthenticationFilter.class)
//验证用户token,以及用户是否有接口访问权限的过滤器
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(permitAllEndpointList, Arrays.asList(API_ROOT_URL,API_ATTACHMENT_URL)), UsernamePasswordAuthenticationFilter.class);
}
具体解释见代码上的注释
@EnableWebSecurity
我们自己定义的配置类 WebSecurityConfig
加上了 @EnableWebSecurity
注解,同时继承了 WebSecurityConfigurerAdapter
。你可能会在想谁的作用大一点,毫无疑问 @EnableWebSecurity
起到决定性的配置作用,它其实是个组合注解。
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,// <2>
SpringWebMvcImportSelector.class })// <1>
@EnableGlobalAuthentication // <3>
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
- <1>
@Import
是springboot
提供的用于引入外部的配置的注解,可以理解为:@EnableWebSecurity
注解激活了@Import
注解中包含的配置类。 - <2>
WebSecurityConfiguration
顾名思义,是用来配置web
安全的,下面的小节会详细介绍。 - <3>
@EnableGlobalAuthentication
注解的源码如下:
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
注意点同样在 @Import
之中,它实际上激活了 AuthenticationConfiguration
这样的一个配置类,用来配置认证相关的核心类。
也就是说:@EnableWebSecurity
完成的工作便是加载了WebSecurityConfiguration
,AuthenticationConfiguration
这两个核心配置类,也就此将 spring security
的职责划分为了配置安全信息,配置认证信息两部分。
WebSecurityConfiguration
在这个配置类中,有一个非常重要的Bean被注册了。
@Configuration
public class WebSecurityConfiguration {
//DEFAULT_FILTER_NAME = "springSecurityFilterChain"
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
...
}
}
在未使用springboot
之前,大多数人都应该对springSecurityFilterChain
这个名词不会陌生,他是spring security
的核心过滤器,是整个认证的入口。在曾经的XML
配置中,想要启用spring security
,需要在web.xml
中进行如下配置:
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
而在springboot
集成之后,这样的XML
被java
配置取代。WebSecurityConfiguration
中完成了声明springSecurityFilterChain
的作用,并且最终交给DelegatingFilterProxy
这个代理类,负责拦截请求(注意DelegatingFilterProxy
这个类不是spring security
包中的,而是存在于web
包中,spring
使用了代理模式来实现安全过滤的解耦)。
AuthenticationConfiguration
@Configuration
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(
ObjectPostProcessor
AuthenticationConfiguration
的主要任务,便是负责生成全局的身份认证管理者 AuthenticationManager
。还记得在《初识 Spring Security 篇一》中,介绍了 Spring Security
的认证体系,AuthenticationManager
便是最核心的身份认证管理器。
WebSecurityConfigurerAdapter
适配器模式在 spring
中被广泛的使用,在配置中使用 Adapter
的好处便是,我们可以选择性的配置想要修改的那一部分配置,而不用覆盖其他不相关的配置。WebSecurityConfigurerAdapter
中我们可以选择自己想要修改的内容,来进行重写,而其提供了三个 configure
重载方法,是我们主要关心的:
/**WebSecurityConfigurerAdapter**/
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
...
/**
* Override this method to configure {@link WebSecurity}. For example, if you wish to
* ignore certain requests.
*/
public void configure(WebSecurity web) throws Exception {
}
/**
* Override this method to configure the {@link HttpSecurity}. Typically subclasses
* should not invoke this method by calling super as it may override their
* configuration. The default configuration is:
*
*
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
*
*
* @param http the {@link HttpSecurity} to modify
* @throws Exception if an error occurs
*/
// @formatter:off
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
由参数就可以知道,分别是对 AuthenticationManagerBuilder
,WebSecurity
,HttpSecurity
进行个性化的配置。
HttpSecurity常用配置
@Configuration
@EnableWebSecurity
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.failureForwardUrl("/login?error")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index")
.permitAll()
.and()
.httpBasic()
.disable();
}
}
上述是一个使用 Java Configuration
配置 HttpSecurity
的典型配置,其中 http
作为根开始配置,每一个 and()
对应了一个模块的配置(等同于xml配置中的结束标签),并且 and()
返回了 HttpSecurity
本身,于是可以连续进行配置。他们配置的含义也非常容易通过变量本身来推测
- authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息。
- formLogin()对应表单认证相关的配置
- logout()对应了注销相关的配置
- httpBasic()可以配置basic登录
他们分别代表了 http
请求相关的安全配置,这些配置项无一例外的返回了 Configurer
类,而所有的 http
相关配置可以通过查看HttpSecurity
的主要方法得知:
public LogoutConfigurer logout() throws Exception {
return getOrApply(new LogoutConfigurer());
}
......
public CsrfConfigurer csrf() throws Exception {
ApplicationContext context = getContext();
return getOrApply(new CsrfConfigurer(context));
}
需要对 http
协议有一定的了解才能完全掌握所有的配置,不过,springboot
和spring security
的自动配置已经足够使用了。其中每一项 Configurer
(e.g.FormLoginConfigurer,CsrfConfigurer)都是 HttpConfigurer
的细化配置项。
WebSecurityBuilder
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
}
以笔者的经验,这个配置中并不会出现太多的配置信息。
AuthenticationManagerBuilder
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
想要在 WebSecurityConfigurerAdapter
中进行认证相关的配置,可以使用 configure(AuthenticationManagerBuilder auth)
暴露一个 AuthenticationManager
的建造器:AuthenticationManagerBuilder
。如上所示,我们便完成了内存中用户的配置。
细心的朋友会发现,在前面的文章中我们配置内存中的用户时,似乎不是这么配置的,而是:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
如果你的应用只有唯一一个 WebSecurityConfigurerAdapter
,那么他们之间的差距可以被忽略,从方法名可以看出两者的区别:使用@Autowired
注入的 AuthenticationManagerBuilder
是全局的身份认证器,作用域可以跨越多个WebSecurityConfigurerAdapter
,以及影响到基于Method
的安全控制;而 protected configure()
的方式则类似于一个匿名内部类,它的作用域局限于一个WebSecurityConfigurerAdapter
内部。关于这一点的区别,可以参考我曾经提出的issuespring-security#issues4571。官方文档中,也给出了配置多个WebSecurityConfigurerAdapter
的场景以及demo
,将在该系列的后续文章中解读
原文链接:http://blog.didispace.com/xjf-spring-security-4/