1、开启Spring Security
将@EnableWebSecurity添加到@Configuration类以在任何WebSecurityConfigurer中定义Spring Security配置,或者更有可能通过扩展WebSecurityConfigurerAdapter基类并覆盖单个方法:
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
// Spring Security should completely ignore URLs starting with /resources/
.antMatchers("/resources/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/public/**").permitAll().anyRequest()
.hasRole("USER").and()
// Possibly more configuration ...
.formLogin() // enable form based log in
// set permitAll for all URLs associated with Form Login
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth
// enable in memory based authentication with a user named "user" and "admin"
.inMemoryAuthentication().withUser("user").password("password").roles("USER")
.and().withUser("admin").password("password").roles("USER", "ADMIN");
}
// Possibly more overridden methods ...
}
2、@EnableWebSecurity
我们自己定义的配置类MyWebSecurityConfiguration加上了@EnableWebSecurity注解,同时继承了WebSecurityConfigurerAdapter。你可能会在想谁的作用大一点,毫无疑问@EnableWebSecurity起到决定性的配置作用,它其实是个组合注解。
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
@Import是springboot提供的用于引入外部的配置的注解,可以理解为:@EnableWebSecurity注解激活了@Import注解中包含的配置类。
@EnableGlobalAuthentication
注解的源码如下:
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
注意点同样在@Import之中,它实际上激活了AuthenticationConfiguration这样的一个配置类,用来配置认证相关的核心类。也就是说:@EnableWebSecurity完成的工作便是加载了WebSecurityConfiguration,AuthenticationConfiguration这两个核心配置类,也就此将spring security的职责划分为了配置安全信息,配置认证信息两部分。除了这两个核心的配置类外还有OAuth2ImportSelector和SpringWebMvcImportSelector,
OAuth2ImportSelector用于OAuth 2.0客户端支持以后再介绍,下面分别看看SpringWebMvcImportSelector、
WebSecurityConfiguration和AuthenticationConfiguration。
3、SpringWebMvcImportSelector
class SpringWebMvcImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean webmvcPresent = ClassUtils.isPresent(
"org.springframework.web.servlet.DispatcherServlet",
getClass().getClassLoader());
return webmvcPresent
? new String[] {
"org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" }
: new String[] {};
}
}
class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContextAware {
private BeanResolver beanResolver;
@Override
@SuppressWarnings("deprecation")
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver();
authenticationPrincipalResolver.setBeanResolver(beanResolver);
argumentResolvers.add(authenticationPrincipalResolver);
argumentResolvers
.add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());
argumentResolvers.add(new CsrfTokenArgumentResolver());
}
@Bean
public RequestDataValueProcessor requestDataValueProcessor() {
//用于为Spring MVC和Spring Security CSRF集成添加RequestDataValueProcessor
return new CsrfRequestDataValueProcessor();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.beanResolver = new BeanFactoryResolver(applicationContext.getAutowireCapableBeanFactory());
}
}
SpringWebMvcImportSelector
的作用是为Spring MVC添加了两个HandlerMethodArgumentResolver用来解析@AuthenticationPrincipal参数。
- org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver
- org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver
对应
- org.springframework.security.core.annotation.AuthenticationPrincipal
- org.springframework.security.web.bind.annotation.AuthenticationPrincipal
其中org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver已被标记过期,使用org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver代替它即可。
3.1、@AuthenticationPrincipal的使用
@Controller
public class MyController {
@MessageMapping("/im")
public void im(@AuthenticationPrincipal CustomUser customUser) {
// do something with CustomUser
}
}
或者,用户可以创建自定义元注释,如下所示:
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal
public @interface CurrentUser {
}
@Controller
public class MyController {
@MessageMapping("/im")
public void im(@CurrentUser CustomUser customUser) {
// do something with CustomUser
}
}
3.2、AuthenticationPrincipalArgumentResolver
允许使用@AuthenticationPrincipal解析Authentication.getPrincipal()。
将使用SecurityContextHolder中的Authentication.getPrincipal()解析CustomUser参数。 如果Authentication或Authentication.getPrincipal()为null,则返回null。 如果类型不匹配,则返回null,除非AuthenticationPrincipal.errorOnInvalidType()为true,否则将抛出ClassCastException。
public boolean supportsParameter(MethodParameter parameter) {
return findMethodAnnotation(AuthenticationPrincipal.class, parameter) != null;
}
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
if (authentication == null) {
return null;
}
Object principal = authentication.getPrincipal();
AuthenticationPrincipal authPrincipal = findMethodAnnotation(
AuthenticationPrincipal.class, parameter);
String expressionToParse = authPrincipal.expression();
if (StringUtils.hasLength(expressionToParse)) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(principal);
context.setVariable("this", principal);
context.setBeanResolver(beanResolver);
Expression expression = this.parser.parseExpression(expressionToParse);
principal = expression.getValue(context);
}
if (principal != null
&& !parameter.getParameterType().isAssignableFrom(principal.getClass())) {
if (authPrincipal.errorOnInvalidType()) {
throw new ClassCastException(principal + " is not assignable to "
+ parameter.getParameterType());
}
else {
return null;
}
}
return principal;
}
4、AuthenticationConfiguration
用于配置身份认证的AuthenticationManager。
AuthenticationConfiguration会导入一个@Configuration类,向Spring容器注册了一个AutowireBeanFactoryObjectPostProcessor,这个类可以使用Spring容器对其进行初始化和自动装配。
@Configuration
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
private AtomicBoolean buildingAuthenticationManager = new AtomicBoolean();
private ApplicationContext applicationContext;
private AuthenticationManager authenticationManager;
private boolean authenticationManagerInitialized;
private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections
.emptyList();
private ObjectPostProcessor<Object> objectPostProcessor;
//注入ObjectPostProcessorConfiguration配置的AutowireBeanFactoryObjectPostProcessor
@Autowired
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
...
}
@Configuration
public class ObjectPostProcessorConfiguration {
@Bean
public ObjectPostProcessor<Object> objectPostProcessor(
AutowireCapableBeanFactory beanFactory) {
return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
}
}
AutowireBeanFactoryObjectPostProcessor允许注册对象以参与AutowireCapableBeanFactory对Aware方法的后处理,InitializingBean.afterPropertiesSet()和DisposableBean.destroy()。
final class AutowireBeanFactoryObjectPostProcessor
implements ObjectPostProcessor<Object>, DisposableBean, SmartInitializingSingleton {
private final Log logger = LogFactory.getLog(getClass());
private final AutowireCapableBeanFactory autowireBeanFactory;
private final List<DisposableBean> disposableBeans = new ArrayList<>();
private final List<SmartInitializingSingleton> smartSingletons = new ArrayList<>();
public AutowireBeanFactoryObjectPostProcessor(
AutowireCapableBeanFactory autowireBeanFactory) {
Assert.notNull(autowireBeanFactory, "autowireBeanFactory cannot be null");
this.autowireBeanFactory = autowireBeanFactory;
}
@SuppressWarnings("unchecked")
public <T> T postProcess(T object) {
if (object == null) {
return null;
}
T result = null;
try {
result = (T) this.autowireBeanFactory.initializeBean(object,
object.toString());
}
catch (RuntimeException e) {
Class<?> type = object.getClass();
throw new RuntimeException(
"Could not postProcess " + object + " of type " + type, e);
}
this.autowireBeanFactory.autowireBean(object);
if (result instanceof DisposableBean) {
this.disposableBeans.add((DisposableBean) result);
}
if (result instanceof SmartInitializingSingleton) {
this.smartSingletons.add((SmartInitializingSingleton) result);
}
return result;
}
@Override
public void afterSingletonsInstantiated() {
for (SmartInitializingSingleton singleton : smartSingletons) {
singleton.afterSingletonsInstantiated();
}
}
public void destroy() throws Exception {
for (DisposableBean disposable : this.disposableBeans) {
try {
disposable.destroy();
}
catch (Exception error) {
this.logger.error(error);
}
}
}
}
4.1、DefaultPasswordEncoderAuthenticationManagerBuilder
配置一个AuthenticationManagerBuilder在getAuthenticationManager()方法中会使用它来构建一个AuthenticationManager,AuthenticationManagerBuilder的源码分析请看《AuthenticationManagerBuilder》。
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(
ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);
//扩展AuthenticationManagerBuilder为其增加一个LazyPasswordEncoder和上面提到的AutowireBeanFactoryObjectPostProcessor
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
result.authenticationEventPublisher(authenticationEventPublisher);
}
return result;
}
//暴露一个方法使用DefaultPasswordEncoderAuthenticationManagerBuilder构建AuthenticationManager
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder(
this.objectPostProcessor, this.applicationContext);
if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder);
}
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
authenticationManager = authBuilder.build();
if (authenticationManager == null) {
authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return authenticationManager;
}
4.2、GlobalAuthenticationConfigurerAdapter
上面代码会使用GlobalAuthenticationConfigurerAdapter来配置AuthenticationManagerBuilder,只需要实现自己的GlobalAuthenticationConfigurerAdapter通过Spring托管,就会通过setGlobalAuthenticationConfigurers()方法注入进来实现对AuthenticationManagerBuilder的配置。
@Autowired(required = false)
public void setGlobalAuthenticationConfigurers(
List<GlobalAuthenticationConfigurerAdapter> configurers) throws Exception {
Collections.sort(configurers, AnnotationAwareOrderComparator.INSTANCE);
this.globalAuthConfigurers = configurers;
}
AuthenticationConfiguration中也定义了三个GlobalAuthenticationConfigurerAdapter:
- EnableGlobalAuthenticationAutowiredConfigurer:debug级别打印@EnableGlobalAuthentication类信息。
- InitializeUserDetailsBeanManagerConfigurer:从Spring中寻找UserDetailsService和PasswordEncoder配置到AuthenticationManagerBuilder。
- InitializeAuthenticationProviderBeanManagerConfigurer:从Spring中寻找AuthenticationProvider配置到AuthenticationManagerBuilder。
5、WebSecurityConfiguration
5.1、WebSecurity的创建
使用WebSecurity创建FilterChainProxy,为Spring Security执行基于Web的安全性。 然后它导出必要的bean。 可以通过扩展WebSecurityConfigurerAdapter并将其作为配置或实现WebSecurityConfigurer并将其公开为配置来对WebSecurity进行自定义。 使用EnableWebSecurity时会导入此配置。
这个类中定义了很多@Bean方法,这些方法中很多依赖于变量名是webSecurity的WebSecurity对象,所以我们先从它的创建看起。
关于 WebSecurity的实现原理请参考【Spring-Security源码分析】WebSecurity。
//webSecurityConfigurers是下面定义AutowiredWebSecurityConfigurersIgnoreParents的getWebSecurityConfigurers()方法返回值
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
ObjectPostProcessor<Object> objectPostProcessor,
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
throws Exception {
//对WebSecurity进行装配
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
if (debugEnabled != null) {
webSecurity.debug(debugEnabled);
}
Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException(
"@Order on WebSecurityConfigurers must be unique. Order of "
+ order + " was already used on " + previousConfig + ", so it cannot be used on "
+ config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
//使用SecurityConfigurer<Filter, WebSecurity>配置webSecurity
webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
@Bean
public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
}
//用于从当前ApplicationContext获取所有WebSecurityConfigurer实例但忽略父实例的类。
final class AutowiredWebSecurityConfigurersIgnoreParents {
private final ConfigurableListableBeanFactory beanFactory;
public AutowiredWebSecurityConfigurersIgnoreParents(
ConfigurableListableBeanFactory beanFactory) {
Assert.notNull(beanFactory, "beanFactory cannot be null");
this.beanFactory = beanFactory;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
}
5.2、使用WebSecurity创建一个bean name为springSecurityFilterChain的Filter
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasConfigurers = webSecurityConfigurers != null
&& !webSecurityConfigurers.isEmpty();
//如果没有提供任何自定义配置,则使用一个默认的WebSecurityConfigurerAdapter配置webSecurity
if (!hasConfigurers) {
WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
.postProcess(new WebSecurityConfigurerAdapter() {
});
webSecurity.apply(adapter);
}
return webSecurity.build();
}
webSecurity.build()返回一个FilterChainProxy用于用户认证和权限检查,具体细节参考《【Spring-Security源码分析】WebSecurity》。
6、如何应用WebSecurity的Filter
现在我们知道了Spring Security真正起作用的就是一个Filter,那么如何将这个过滤器应用到我们的Spring MVC中呢,在非Spring Security的Spring MVC中,我们是通过继承于AbstractAnnotationConfigDispatcherServletInitializer开启我们的Spring MVC功能,具体原理请参考《【Spring源码分析】01-DispatcherServlet注册过程》,而开启带有Spring Security的Spring MVC的功能与之类似,只需要继承AbstractSecurityWebApplicationInitializer即可,因为它的onStartup()方法中会调用insertSpringSecurityFilterChain()方法完成Filter的注册。
public final void onStartup(ServletContext servletContext) throws ServletException {
beforeSpringSecurityFilterChain(servletContext);
if (this.configurationClasses != null) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(this.configurationClasses);
servletContext.addListener(new ContextLoaderListener(rootAppContext));
}
if (enableHttpSessionEventPublisher()) {
servletContext.addListener(
"org.springframework.security.web.session.HttpSessionEventPublisher");
}
servletContext.setSessionTrackingModes(getSessionTrackingModes());
insertSpringSecurityFilterChain(servletContext);
afterSpringSecurityFilterChain(servletContext);
}
//注册springSecurityFilterChain
private void insertSpringSecurityFilterChain(ServletContext servletContext) {
String filterName = DEFAULT_FILTER_NAME;
DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(
filterName);
String contextAttribute = getWebApplicationContextAttribute();
if (contextAttribute != null) {
springSecurityFilterChain.setContextAttribute(contextAttribute);
}
//向servlet容器注册DelegatingFilterProxy
registerFilter(servletContext, true, filterName, springSecurityFilterChain);
}
看到这里只是注册了一个DelegatingFilterProxy,并未直接注册WebSecurityConfiguration定义的springSecurityFilterChain,其实是因为DelegatingFilterProxy是springSecurityFilterChain的委托代理类。在DelegatingFilterProxy的initFilterBean()方法中,会通过Spring容器获取springSecurityFilterChain的bean,然后在doFilter()方法中都是调用这个bean的doFilter()方法。
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// Lazily initialize the delegate if necessary.
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized (this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: " +
"no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
// Let the delegate perform the actual doFilter operation.
invokeDelegate(delegateToUse, request, response, filterChain);
}