zoukankan      html  css  js  c++  java
  • springboot情操陶冶-web配置(八)

    本文关注应用的安全方面,涉及校验以及授权方面,以springboot自带的security板块作为讲解的内容

    实例

    建议用户可直接路由至博主的先前博客spring security整合cas方案。本文则针对相关的源码作下简单的分析,方便笔者以及读者更深入的了解spring的security板块

    @EnableWebSecurity

    这个注解很精髓,基本上可以作为security的入口,笔者贴一下它的源码

    @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
    @Target(value = { java.lang.annotation.ElementType.TYPE })
    @Documented
    @Import({ WebSecurityConfiguration.class,
    		SpringWebMvcImportSelector.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;
    }
    

    可以分为三个部分来分析,
    SpringWebMvcImportSelector-支持mvc的参数安全校验,替代了@EnableWebMvcSecurity注解
    WebSecurityConfiguration-Web的安全配置
    @EnableGlobalAuthentication-支持公共的认证校验

    SpringWebMvcImportSelector

    首先先看下其如何整合mvc的安全校验,其是一个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[] {};
    	}
    

    由上述代码可知,在classpath环境中存在mvc的关键类DispatcherServlet时便会引入WebMvcSecurityConfiguration类,那么此类又配置了什么东西呢?
    里面的代码很简单,但关键是其是WebMvcConfigurer接口的实现类,根据之前的文章提到,该接口主要是用于配置MVC的相关功能,比如参数处理器、返回值处理器、异常处理器等等。

    而该类只是扩展了相应的参数处理器,我们可以看下源码

    	@Override
    	@SuppressWarnings("deprecation")
    	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
    		// 支持@AuthenticationPrinciple参数注解校验
    		AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver();
    		authenticationPrincipalResolver.setBeanResolver(beanResolver);
    		argumentResolvers.add(authenticationPrincipalResolver);
    		// 废弃
    		argumentResolvers
    				.add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());
    		// csrf token参数
    		argumentResolvers.add(new CsrfTokenArgumentResolver());
    	}
    

    针对@AuthenticationPrinciple注解的参数校验,本文不展开了,这里作下归纳

    1. 带有@AuthenticationPrinciple注解的参数其值会从SecurityContext的上下文读取相应的Authentication校验信息
    2. 有一个要求,被该注解修饰的参数须同SecurityContext的上下文存放的Authentication信息为同一接口,否则则会返回null。如果设置了errorOnInvalidType属性为true,则会抛异常
    3. 综上所述,该注解主要是方便将校验通过的Token用于参数赋值,其它的作用也不是很大

    @EnableGlobalAuthentication

    再来分析下springboot-security的公共认证校验是什么概念,贴下源码

    @Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
    @Target(value = { java.lang.annotation.ElementType.TYPE })
    @Documented
    @Import(AuthenticationConfiguration.class)
    @Configuration
    public @interface EnableGlobalAuthentication {
    }
    

    OK,直接进入相应的AuthenticationConfiguration类进行具体的分析


    1.其引入了ObjectPostProcessorConfiguration配置用于创建AutowireBeanFactoryObjectPostProcessor类,作用应该是通过Spring上下文实例相应的实体类并注册到bean工厂中

    	@Bean
    	public ObjectPostProcessor<Object> objectPostProcessor(
    			AutowireCapableBeanFactory beanFactory) {
    		return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
    	}
    

    2.创建基于密码机制的认证管理器Bean,类型为DefaultPasswordEncoderAuthenticationManagerBuilder

    	@Bean
    	public AuthenticationManagerBuilder authenticationManagerBuilder(
    			ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
    		// 密码加密器
    		LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
    		// 认证事件传播器
    		AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);
    		// 默认的认证管理器
    		DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
    		if (authenticationEventPublisher != null) {
    			result.authenticationEventPublisher(authenticationEventPublisher);
    		}
    		return result;
    	}
    

    上述的密码加密器支持多种方式的加密,比如bcrypt(默认)/ladp/md5/sha-1等,感兴趣的读者可自行阅读。用户也可多用此Bean作额外的扩展,例如官方建议的如下代码

    @Configuration
    @EnableGlobalAuthentication
    public class MyGlobalAuthenticationConfiguration {
    
    	@Autowired
    	public void configureGlobal(AuthenticationManagerBuilder auth) {
     		auth.inMemoryAuthentication().withUser("user").password("password").roles("USER")
    				.and().withUser("admin").password("password").roles("ADMIN,USER");
      	}
     }
    

    3.创建基于UserDetails的认证器,用于管理用户的授权信息

    	@Bean
    	public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
    		return new InitializeUserDetailsBeanManagerConfigurer(context);
    	}
    

    其会创建基于Datasource源的DaoAuthenticationProvider认证校验器,前提是ApplicationContext上下文存在UserDetailsServiceBean对象,否则会不创建。如果用户想基于数据库或者其他数据源的可尝试复写UserDetailsService接口

    @Configuration
    public class DaoUserDetailsServiceConfig {
    
        /**
         * load user info by dao
         *
         * @see org.springframework.security.authentication.dao.DaoAuthenticationProvider
         */
        @Configuration
        public static class DefaultUserDetailsService implements UserDetailsService {
    
            private static final String DEFAULT_PASS = "defaultPass";
    
            // admin authority
            private Collection<? extends GrantedAuthority> adminAuthority;
    
            @Resource
            private PasswordEncoder defaultPasswordEncoder;
    
            public DefaultUserDetailsService() {
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_ADMIN");
                List<GrantedAuthority> authorities = new ArrayList<>();
                authorities.add(authority);
    
                adminAuthority = Collections.unmodifiableList(authorities);
            }
    
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                User userdetails = new User(username, defaultPasswordEncoder.encode(DEFAULT_PASS), adminAuthority);
    
                return userdetails;
            }
    
            @Bean
            public PasswordEncoder daoPasswordEncoder() {
                PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
    
                return passwordEncoder;
            }
        }
    }
    

    注意:实现UserDetailsService的自定义实例请确保只有一个注册至ApplicationContext上,否则上述的基于数据源配置无法自动化配置;但也可通过AuthenticationManagerBuilder#userDetailsService()方法来进行相应的配置


    4.创建AuthenticationProvider认证器,用于用户信息的校验

    	@Bean
    	public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
    		return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
    	}
    

    同第三点,只是它就配置简单的AuthenticationProvider至相应的AuthenticationManagerBuilderBean中

    所以综上所述,@EnableGlobalAuthentication注解的主要目的是配置认证管理器,里面包含了加密器以及相应的认证器

    WebSecurityConfiguration

    web方面的安全配置,笔者也根据加载的顺序来进行分析


    1.获取WebSecurityConfigurer接口bean集合的AutowiredWebSecurityConfigurersIgnoreParents

    	@Bean
    	public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
    			ConfigurableListableBeanFactory beanFactory) {
    		return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
    	}
    

    此Bean用于获取所有注册在bean工厂上的WebSecurityConfigurer接口,用户也一般通过此接口的抽象类WebSecurityConfigurerAdapter来进行相应的扩展


    2.设置Security的Filter过滤链配置,提前为创建过滤链作准备

    	@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);
    		}
    		
    		// 根据@Order属性排序
    		Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);
    
    		Integer previousOrder = null;
    		Object previousConfig = null;
    		// 校验Order对应的值,不允许相同,否则会抛出异常
    		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;
    		}
    		// 对排序过的SecurityConfigurer依次放入WebSecurity对象中
    		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
    			webSecurity.apply(webSecurityConfigurer);
    		}
    		this.webSecurityConfigurers = webSecurityConfigurers;
    	}
    

    这里便提一下,我们在继承WebSecurityConfigurerAdapter抽象类的时候,记得在其头上加上@Order属性,并且保证值唯一


    3.创建Security过滤链

    	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    	public Filter springSecurityFilterChain() throws Exception {
    		// 如果用户没有配置WebSecurityConfigurer接口,则创建一个空的
    		boolean hasConfigurers = webSecurityConfigurers != null
    				&& !webSecurityConfigurers.isEmpty();
    		if (!hasConfigurers) {
    			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
    					.postProcess(new WebSecurityConfigurerAdapter() {
    					});
    			webSecurity.apply(adapter);
    		}
    		// create Filter
    		return webSecurity.build();
    	}
    

    看来Filter拦截器的配置是通过WebSecurity这个类来完成的,限于里面的代码过于复杂,本文就不展开了,感兴趣的读者可以重点关注下此类。由此可以得出Springboot的安全校验是通过过滤链的设计方式来完成的


    4.URI权限校验Bean,其依赖于第三点的配置

    	@Bean
    	@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    	public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
    		return webSecurity.getPrivilegeEvaluator();
    	}
    

    5.安全校验表达式验证Bean,其也依赖于第三点的配置,应该是与第四点搭配使用

    	@Bean
    	@DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
    	public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
    		return webSecurity.getExpressionHandler();
    	}
    

    小结

    Springboot整合的Security板块内容很多,本文也展示不完,不过值得关注的是以下几个方面
    1)WebSecurity的个性化配置类,一般是复写抽象接口WebSecurityConfigurerAdapter,再加上@EnableWebSecurity注解便可

    2)AuthenticationManagerBuilder认证校验器,重点关注其中的密码校验器,用于密码的加密解密,默认使用bcrypt方式。如果用户想通过其他数据源获取用户信息,可以关注UserDetailsService接口。推荐用户均使用AuthenticationManagerBuilder类配置认证机制!

    3)WebSecurity类,此类是Springboot Security模块的核心类,具体的过滤链配置均是由此类得到的。读者以及笔者应该对此加以关注

  • 相关阅读:
    2020.07.01
    2020年5月9日
    2020年4月25日
    2020年4月24日
    2020年4月23日
    2020年4月22日
    2020年3月29日
    2020.3.25
    nacos源码解析(三)-注册中心服务注册处理
    nacos源码解析(二)-客户端如何访问注册中心
  • 原文地址:https://www.cnblogs.com/question-sky/p/10084423.html
Copyright © 2011-2022 走看看