security 订阅
网络安全技术及其协议,包括了网络通信安全、信息在网络传输中的保密性和完整性、控制访问受限网域与敏感信息以及在公共网络如因特网上使用隐秘通讯。为了解决这些问题,各大组织及技术供应商纷纷推出了各种网络和信息安全技术。 展开全文
网络安全技术及其协议,包括了网络通信安全、信息在网络传输中的保密性和完整性、控制访问受限网域与敏感信息以及在公共网络如因特网上使用隐秘通讯。为了解决这些问题,各大组织及技术供应商纷纷推出了各种网络和信息安全技术。
信息
外文名
Security
包括了
网络通信安全
中文名
网络安全技术及其协议
推出了
各种网络和信息安全技术
Security基本介绍
Security:网络安全技术及其协议(Network Security Technologies and Protocols:AAA,VPN and Firewall)
收起全文
精华内容
下载资源
问答
  • spring security CSRF防护

    万次阅读 多人点赞 2019-07-31 19:14:30
    从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护。 我这边是spring boot项目,在启用了@EnableWebSecurity...

    CSRF是指跨站请求伪造(Cross-site request forgery),是web常见的攻击之一。
    从Spring Security 4.0开始,默认情况下会启用CSRF保护,以防止CSRF攻击应用程序,Spring Security CSRF会针对PATCH,POST,PUT和DELETE方法进行防护。
    我这边是spring boot项目,在启用了@EnableWebSecurity注解后,csrf保护就自动生效了。
    所以在默认配置下,即便已经登录了,页面中发起PATCH,POST,PUT和DELETE请求依然会被拒绝,并返回403,需要在请求接口的时候加入csrfToken才行。
    如果你使用了freemarker之类的模板引擎或者jsp,针对表单提交,可以在表单中增加如下隐藏域:

    <input  type = “hidden”  name = “${_csrf.parameterName}”  value = “${_csrf.token}” /> 
    

    如果您使用的是JSON,则无法在HTTP参数中提交CSRF令牌。相反,您可以在HTTP头中提交令牌。一个典型的模式是将CSRF令牌包含在元标记中。下面显示了一个JSP示例:

    <html> 
    <head> 
    	<meta  name = “_csrf” content = “${_csrf.token}” /> 
    	<!-- 默认标题名称是X-CSRF-TOKEN  --> 
    	<meta  name = “_csrf_header”  content = “${_csrf.headerName}” /> 
    </ head> 
    

    然后,您可以将令牌包含在所有Ajax请求中。如果您使用jQuery,可以使用以下方法完成此操作:

    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    $.ajax({
    	url:url,
    	type:'POST',
    	async:false,
    	dataType:'json',    //返回的数据格式:json/xml/html/script/jsonp/text
    	beforeSend: function(xhr) {
    		xhr.setRequestHeader(header, token);  //发送请求前将csrfToken设置到请求头中
    	},
    	success:function(data,textStatus,jqXHR){
    	}
    });
    

    如果你不想启用CSRF保护,可以在spring security配置中取消csrf,如下:

    @Configuration
    @EnableWebSecurity
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/login").permitAll()
                    .anyRequest().authenticated()
                    .and()
                	...
            http.csrf().disable(); //取消csrf防护
        }
    }
    
    展开全文
  • springSecurity安全框架的学习和原理解读

    万次阅读 多人点赞 2018-08-11 09:32:01
    最近在公司的项目中使用了springsecurity框架,所以有机会来学习一下,公司的项目是使用springboot搭建springBoot版本1.59 spring security版本4.2.3 (个人理解可能会有偏差,希望有不正确之处,大家能够指出来...

    最近在公司的项目中使用了spring security框架,所以有机会来学习一下,公司的项目是使用springboot搭建 springBoot版本1.59

    spring security 版本4.2.3

       (个人理解可能会有偏差,希望有不正确之处,大家能够指出来,共同探讨交流。)

    目录

    一、Spring security框架简介

     1、简介

     2、框架原理

     3、框架的核心组件

    二、自定义安全配置的加载机制

    1、前提 基于自身业务需要

    2、WebSecurityConfiguration类

    3、AbstractSecurityBuilder类

    4、举例说明如何将一个Configurer转换为filter

    三、用户登录的验证和授权过程


    一、Spring security框架简介

         1、简介

               一个能够为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。   spring security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。

         2、框架原理

         众所周知 想要对对Web资源进行保护,最好的办法莫过于Filter,要想对方法调用进行保护,最好的办法莫过于AOP。所以springSecurity在我们进行用户认证以及授予权限的时候,通过各种各样的拦截器来控制权限的访问,从而实现安全。
            如下为其主要过滤器  

    1.         WebAsyncManagerIntegrationFilter 
    2.         SecurityContextPersistenceFilter 
    3.         HeaderWriterFilter 
    4.         CorsFilter 
    5.         LogoutFilter
    6.         RequestCacheAwareFilter
    7.         SecurityContextHolderAwareRequestFilter
    8.         AnonymousAuthenticationFilter
    9.         SessionManagementFilter
    10.         ExceptionTranslationFilter
    11.         FilterSecurityInterceptor
    12.         UsernamePasswordAuthenticationFilter
    13.         BasicAuthenticationFilter

         3、框架的核心组件

    1.       SecurityContextHolder:提供对SecurityContext的访问
    2.       SecurityContext,:持有Authentication对象和其他可能需要的信息
    3.       AuthenticationManager 其中可以包含多个AuthenticationProvider
    4.       ProviderManager对象为AuthenticationManager接口的实现类
    5.       AuthenticationProvider 主要用来进行认证操作的类 调用其中的authenticate()方法去进行认证操作
    6.       Authentication:Spring Security方式的认证主体
    7.       GrantedAuthority:对认证主题的应用层面的授权,含当前用户的权限信息,通常使用角色表示
    8.      UserDetails:构建Authentication对象必须的信息,可以自定义,可能需要访问DB得到
    9.       UserDetailsService:通过username构建UserDetails对象,通过loadUserByUsername根据userName获取UserDetail对象 (可以在这里基于自身业务进行自定义的实现  如通过数据库,xml,缓存获取等)           

        
         

    二、自定义安全配置的加载机制

        1、前提 基于自身业务需要

    有关springSecrity安全框架的理解参考:springSecurity安全框架介绍

    自定义了一个springSecurity安全框架的配置类 继承WebSecurityConfigurerAdapter,重写其中的方法configure,但是并不清楚自定义的类是如何被加载并起到作用,这里一步步通过debug来了解其中的加载原理。

    其实在我们实现该类后,在web容器启动的过程中该类实例对象会被WebSecurityConfiguration类处理。

    @Configuration
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private AccessDeniedHandler accessDeniedHandler;
    
        @Autowired
        private CustAuthenticationProvider custAuthenticationProvider;
    
        // roles admin allow to access /admin/**
        // roles user allow to access /user/**
        // custom 403 access denied handler
        //重写了其中的configure()方法设置了不同url的不同访问权限
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/home", "/about","/img/*").permitAll()
                    .antMatchers("/admin/**","/upload/**").hasAnyRole("ADMIN")
                    .antMatchers("/order/**").hasAnyRole("USER","ADMIN")
                    .antMatchers("/room/**").hasAnyRole("USER","ADMIN")
                    .anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginPage("/login")
                    .permitAll()
                    .and()
                    .logout()
                    .permitAll()
                    .and()
                    .exceptionHandling().accessDeniedHandler(accessDeniedHandler);
        }
    
        // create two users, admin and user
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    
    //        auth.inMemoryAuthentication()
    //                .withUser("user").password("user").roles("USER")
    //                .and()
    //                .withUser("admin").password("admin").roles("ADMIN");
    
    //        auth.jdbcAuthentication()
    
            auth.authenticationProvider(custAuthenticationProvider);
        }
    

      2、WebSecurityConfiguration类

    @Configuration
    public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
        private WebSecurity webSecurity;
        private Boolean debugEnabled;
        private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
        private ClassLoader beanClassLoader;
       
       ...省略部分代码
    
        @Bean(
            name = {"springSecurityFilterChain"}
        )
        public Filter springSecurityFilterChain() throws Exception {
            boolean hasConfigurers = this.webSecurityConfigurers != null
             && !this.webSecurityConfigurers.isEmpty();
            if(!hasConfigurers) {
                WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)
                this.objectObjectPostProcessor
                  .postProcess(new WebSecurityConfigurerAdapter() {
                });
                this.webSecurity.apply(adapter);
            }
    
            return (Filter)this.webSecurity.build();
        }
    
      
        
        /*1、先执行该方法将我们自定义springSecurity配置实例
           (可能还有系统默认的有关安全的配置实例 ) 配置实例中含有我们自定义业务的权限控制配置信息
           放入到该对象的list数组中webSecurityConfigurers中
           使用@Value注解来将实例对象作为形参注入
         */   
     @Autowired(
            required = false
        )
        public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> 
        objectPostProcessor,
       @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") 
      List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) 
    throws Exception {
        
        //创建一个webSecurity对象    
        this.webSecurity = (WebSecurity)objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
            if(this.debugEnabled != null) {
                this.webSecurity.debug(this.debugEnabled.booleanValue());
            }
    
            //对所有配置类的实例进行排序
            Collections.sort(webSecurityConfigurers, WebSecurityConfiguration.AnnotationAwareOrderComparator.INSTANCE);
            Integer previousOrder = null;
            Object previousConfig = null;
    
    
            //迭代所有配置类的实例 判断其order必须唯一
            Iterator var5;
            SecurityConfigurer config;
            for(var5 = webSecurityConfigurers.iterator(); var5.hasNext(); previousConfig = config) {
                config = (SecurityConfigurer)var5.next();
                Integer order = Integer.valueOf(WebSecurityConfiguration.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;
            }
    
    
            //将所有的配置实例添加到创建的webSecutity对象中
            var5 = webSecurityConfigurers.iterator();
    
            while(var5.hasNext()) {
                config = (SecurityConfigurer)var5.next();
                this.webSecurity.apply(config);
            }
            //将webSercurityConfigures 实例放入该对象的webSecurityConfigurers属性中
            this.webSecurityConfigurers = webSecurityConfigurers;
        }
    
       
    }
    

      2.1、 setFilterChainProxySecurityConfigurer()方法

    @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers

       该参数webSecurityConfigurers会将所有的配置实例放入该形参中

     

    该方法中 主要执行如下

         1、创建webSecurity对象

         2、主要检验了配置实例的order顺序(order唯一 否则会报错)

         3、将所有的配置实例存放进入到webSecurity对象中,其中配置实例中含有我们自定义业务的权限控制配置信息

    2.2、springSecurityFilterChain()方法

       调用springSecurityFilterChain()方法,这个方法会判断我们上一个方法中有没有获取到webSecurityConfigurers,没有的话这边会创建一个WebSecurityConfigurerAdapter实例,并追加到websecurity中。接着调用websecurity的build方法。实际调用的是websecurity的父类AbstractSecurityBuilder的build方法 ,最终返回一个名称为springSecurityFilterChain的过滤器链。里面有众多Filter(springSecurity其实就是依靠很多的Filter来拦截url从而实现权限的控制的安全框架)

    3、AbstractSecurityBuilder类

    public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
        private AtomicBoolean building = new AtomicBoolean();
        private O object;
    
      
    
        //调用build方法来返回过滤器链,还是调用SecurityBuilder的dobuild()方法
    
        public final O build() throws Exception {
            if(this.building.compareAndSet(false, true)) {
                this.object = this.doBuild();
                return this.object;
            } else {
                throw new AlreadyBuiltException("This object has already been built");
            }
        }
    
       //...省略部分代码
    }

      3.1 调用子类的doBuild()方法

    public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>> extends AbstractSecurityBuilder<O> {
        private final Log logger;
        private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers;
        private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing;
        private final Map<Class<? extends Object>, Object> sharedObjects;
        private final boolean allowConfigurersOfSameType;
        private AbstractConfiguredSecurityBuilder.BuildState buildState;
        private ObjectPostProcessor<Object> objectPostProcessor;
    
    
        //doBuild()核心方法 init(),configure(),perFormBuild()
        protected final O doBuild() throws Exception {
            LinkedHashMap var1 = this.configurers;
            synchronized(this.configurers) {
                this.buildState = AbstractConfiguredSecurityBuilder.BuildState.INITIALIZING;
                this.beforeInit();
                this.init();
                this.buildState = AbstractConfiguredSecurityBuilder.BuildState.CONFIGURING;
                this.beforeConfigure();
                this.configure();
                this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILDING;
                O result = this.performBuild();
                this.buildState = AbstractConfiguredSecurityBuilder.BuildState.BUILT;
                return result;
            }
        }
    
        protected abstract O performBuild() throws Exception;
        
        //调用init方法 调用配置类WebSecurityConfigurerAdapter的init()方法
        private void init() throws Exception {
            Collection<SecurityConfigurer<O, B>> configurers = this.getConfigurers();
            Iterator var2 = configurers.iterator();
    
            SecurityConfigurer configurer;
            while(var2.hasNext()) {
                configurer = (SecurityConfigurer)var2.next();
                configurer.init(this);
            }
    
            var2 = this.configurersAddedInInitializing.iterator();
    
            while(var2.hasNext()) {
                configurer = (SecurityConfigurer)var2.next();
                configurer.init(this);
            }
    
        }
    
        private void configure() throws Exception {
            Collection<SecurityConfigurer<O, B>> configurers = this.getConfigurers();
            Iterator var2 = configurers.iterator();
    
            while(var2.hasNext()) {
                SecurityConfigurer<O, B> configurer = (SecurityConfigurer)var2.next();
                configurer.configure(this);
            }
    
        }
    
        private Collection<SecurityConfigurer<O, B>> getConfigurers() {
            List<SecurityConfigurer<O, B>> result = new ArrayList();
            Iterator var2 = this.configurers.values().iterator();
    
            while(var2.hasNext()) {
                List<SecurityConfigurer<O, B>> configs = (List)var2.next();
                result.addAll(configs);
            }
    
            return result;
        }
    
        //...省略部分代码
    }
    

    3.2 先调用本类的init()方法

    build过程主要分三步,init->configure->peformBuild 

    • 1  init方法做了两件事,一个就是调用getHttp()方法获取一个http实例,并通过web.addSecurityFilterChainBuilder方法把获取到的实例赋值给WebSecurity的securityFilterChainBuilders属性,这个属性在我们执行build的时候会用到,第二个就是为WebSecurity追加了一个postBuildAction,在build都完成后从http中拿出FilterSecurityInterceptor对象并赋值给WebSecurity。 
    • 2  getHttp()方法,这个方法在当我们使用默认配置时(大多数情况下)会为我们追加各种SecurityConfigurer的具体实现类到httpSecurity中,如exceptionHandling()方法会追加一个ExceptionHandlingConfigurer,sessionManagement()方法会追加一个SessionManagementConfigurer,securityContext()方法会追加一个SecurityContextConfigurer对象,这些SecurityConfigurer的具体实现类最终会为我们配置各种具体的filter。
    • 3 另外getHttp()方法的最后会调用configure(http),这个方法也是我们继承WebSecurityConfigurerAdapter类后最可能会重写的方法 。
    • 4 configure(HttpSecurity http)方法,默认的configure(HttpSecurity http)方法继续向httpSecurity类中追加SecurityConfigurer的具体实现类,如authorizeRequests()方法追加一个ExpressionUrlAuthorizationConfigurer,formLogin()方法追加一个FormLoginConfigurer。 其中ExpressionUrlAuthorizationConfigurer这个实现类比较重要,因为他会给我们创建一个非常重要的对象FilterSecurityInterceptor对象,FormLoginConfigurer对象比较简单,但是也会为我们提供一个在安全认证过程中经常用到会用的一个Filter:UsernamePasswordAuthenticationFilter。 

    以上三个方法就是WebSecurityConfigurerAdapter类中init方法的主要逻辑,

    public abstract class WebSecurityConfigurerAdapter implements 
       WebSecurityConfigurer<WebSecurity> {
    
        public void init(final WebSecurity web) throws Exception {
            final HttpSecurity http = this.getHttp();
            web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
                public void run() {
                    FilterSecurityInterceptor securityInterceptor = (FilterSecurityInterceptor)http.getSharedObject(FilterSecurityInterceptor.class);
                    web.securityInterceptor(securityInterceptor);
                }
            });
        }
    
    
     protected final HttpSecurity getHttp() throws Exception {
            if(this.http != null) {
                return this.http;
            } else {
                DefaultAuthenticationEventPublisher eventPublisher = (DefaultAuthenticationEventPublisher)this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
           
    
    //添加认证的事件的发布者
    this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
    //获取AuthenticationManager对象其中一至多个进行认证处理的对象实例,后面会进行讲解          
    AuthenticationManager authenticationManager = this.authenticationManager();
                this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
                Map<Class<? extends Object>, Object> sharedObjects = this.createSharedObjects();
                this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
                if(!this.disableDefaults) {
                    ((HttpSecurity)((DefaultLoginPageConfigurer)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)((HttpSecurity)this.http.csrf().and()).addFilter(new WebAsyncManagerIntegrationFilter()).exceptionHandling().and()).headers().and()).sessionManagement().and()).securityContext().and()).requestCache().and()).anonymous().and()).servletApi().and()).apply(new DefaultLoginPageConfigurer())).and()).logout();
                    ClassLoader classLoader = this.context.getClassLoader();
                    List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
                    Iterator var6 = defaultHttpConfigurers.iterator();
    
                    while(var6.hasNext()) {
                        AbstractHttpConfigurer configurer = (AbstractHttpConfigurer)var6.next();
                        this.http.apply(configurer);
                    }
                }
    
                //最终调用我们的继承的WebSecurityConfigurerAdapter中重写的configure()
                //将我们业务相关的权限配置规则信息进行初始化操作
                this.configure(this.http);
                return this.http;
            }
        }
    
    
     protected AuthenticationManager authenticationManager() throws Exception {
            if(!this.authenticationManagerInitialized) {
                this.configure(this.localConfigureAuthenticationBldr);
                if(this.disableLocalConfigureAuthenticationBldr) {
                    this.authenticationManager = this.authenticationConfiguration.getAuthenticationManager();
                } else {
                    this.authenticationManager = (AuthenticationManager)this.localConfigureAuthenticationBldr.build();
                }
    
                this.authenticationManagerInitialized = true;
            }
    
            return this.authenticationManager;
        }
    
    
    
    }

    3.3、第二步configure

    • configure方法最终也调用到了WebSecurityConfigurerAdapter的configure(WebSecurity web)方法,默认实现中这个是一个空方法,具体应用中也经常重写这个方法来实现特定需求。 

    3.4、第三步 peformBuild

    • 具体的实现逻辑在WebSecurity类中 
    • 这个方法中最主要的任务就是遍历securityFilterChainBuilders属性中的SecurityBuilder对象,并调用他的build方法。 
      这个securityFilterChainBuilders属性我们前面也有提到过,就是在WebSecurityConfigurerAdapter类的init方法中获取http后赋值给了WebSecurity。因此这个地方就是调用httpSecurity的build方法。
    •  httpSecurity的build方式向其中追加一个个过滤器
    
    public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements SecurityBuilder<Filter>, ApplicationContextAware {
        
      ...省略部分代码
    
        //调用该方法通过securityFilterChainBuilder.build()方法来创建securityFilter过滤器
        //并添加到securityFilterChains对象中,包装成FilterChainProxy 返回
        protected Filter performBuild() throws Exception {
            Assert.state(!this.securityFilterChainBuilders.isEmpty(), "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke " + WebSecurity.class.getSimpleName() + ".addSecurityFilterChainBuilder directly");
            int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
            List<SecurityFilterChain> securityFilterChains = new ArrayList(chainSize);
            Iterator var3 = this.ignoredRequests.iterator();
    
            while(var3.hasNext()) {
                RequestMatcher ignoredRequest = (RequestMatcher)var3.next();
                securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest, new Filter[0]));
            }
    
            var3 = this.securityFilterChainBuilders.iterator();
    
            while(var3.hasNext()) {
                SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder = (SecurityBuilder)var3.next();
                securityFilterChains.add(securityFilterChainBuilder.build());
            }
    
            FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
            if(this.httpFirewall != null) {
                filterChainProxy.setFirewall(this.httpFirewall);
            }
    
            filterChainProxy.afterPropertiesSet();
            Filter result = filterChainProxy;
            if(this.debugEnabled) {
                this.logger.warn("\n\n********************************************************************\n**********        Security debugging is enabled.       *************\n**********    This may include sensitive information.  *************\n**********      Do not use in a production system!     *************\n********************************************************************\n\n");
                result = new DebugFilter(filterChainProxy);
            }
    
            this.postBuildAction.run();
            return (Filter)result;
        }
    
       
    }
    

     4、举例说明如何将一个Configurer转换为filter

    ExpressionUrlAuthorizationConfigurer的继承关系 
    ExpressionUrlAuthorizationConfigurer->AbstractInterceptUrlConfigurer->AbstractHttpConfigurer->SecurityConfigurerAdapter->SecurityConfigurer 
    对应的init方法在SecurityConfigurerAdapter类中,是个空实现,什么也没有做,configure方法在SecurityConfigurerAdapter类中也有一个空实现,在AbstractInterceptUrlConfigurer类中进行了重写 

    Abstractintercepturlconfigurer.java代码 

    @Override  
        public void configure(H http) throws Exception {  
            FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http);  
            if (metadataSource == null) {  
                return;  
            }  
            FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor(  
                    http, metadataSource, http.getSharedObject(AuthenticationManager.class));  
            if (filterSecurityInterceptorOncePerRequest != null) {  
                securityInterceptor  
                        .setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest);  
            }  
            securityInterceptor = postProcess(securityInterceptor);  
            http.addFilter(securityInterceptor);  
            http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor);  
        }  
    ...  
    private AccessDecisionManager createDefaultAccessDecisionManager(H http) {  
            AffirmativeBased result = new AffirmativeBased(getDecisionVoters(http));  
            return postProcess(result);  
        }  
    ...  
    private FilterSecurityInterceptor createFilterSecurityInterceptor(H http,  
                FilterInvocationSecurityMetadataSource metadataSource,  
                AuthenticationManager authenticationManager) throws Exception {  
            FilterSecurityInterceptor securityInterceptor = new FilterSecurityInterceptor();  
            securityInterceptor.setSecurityMetadataSource(metadataSource);  
            securityInterceptor.setAccessDecisionManager(getAccessDecisionManager(http));  
            securityInterceptor.setAuthenticationManager(authenticationManager);  
            securityInterceptor.afterPropertiesSet();  
            return securityInterceptor;  
        }  
    

    4.1、 在这个类的configure中创建了一个FilterSecurityInterceptor,并且也可以明确看到spring security默认给我们创建的AccessDecisionManager是AffirmativeBased。 

    4.2、.最后再看下HttpSecurity类执行build的最后一步 performBuild,这个方法就是在HttpSecurity中实现的 

    Httpsecurity.java代码 

    @Override  
        protected DefaultSecurityFilterChain performBuild() throws Exception {  
            Collections.sort(filters, comparator);  
            return new DefaultSecurityFilterChain(requestMatcher, filters);  
        }  


    可以看到,这个类只是把我们追加到HttpSecurity中的security进行了排序,用的排序类是FilterComparator,从而保证我们的filter按照正确的顺序执行。接着将filters构建成filterChian返回。在前面WebSecurity的performBuild方法中,这个返回值会被包装成FilterChainProxy,并作为WebSecurity的build方法的放回值。从而以springSecurityFilterChain这个名称注册到springContext中(在WebSecurityConfiguration中做的) 

    4.3.在WebSecurity的performBuild方法的最后一步还执行了一个postBuildAction.run,这个方法也是spring security给我们提供的一个hooks,可以在build完成后再做一些事情,比如我们在WebSecurityConfigurerAdapter类的init方法中我们利用这个hook在构建完成后将FilterSecurityInterceptor赋值给了webSecurity类的filterSecurityInterceptor属性

    三、用户登录的验证和授权过程

          1、用户一次完整的登录验证和授权,是一个请求经过 层层拦截器从而实现权限控制,整个web端配置为DelegatingFilterProxy(springSecurity的委托过滤其代理类 ),它并不实现真正的过滤,而是所有过滤器链的代理类,真正执行拦截处理的是由spring 容器管理的个个filter bean组成的filterChain.

    调用实际的FilterChainProxy 的doFilterInternal()方法 去获取所有的拦截器并进行过滤处理如下是DelegatingFilterProxy的doFilter()方法

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            Filter delegateToUse = this.delegate;
            if(delegateToUse == null) {
                Object var5 = this.delegateMonitor;
                synchronized(this.delegateMonitor) {
                    delegateToUse = this.delegate;
                    if(delegateToUse == null) {
                        WebApplicationContext wac = this.findWebApplicationContext();
                        if(wac == null) {
                            throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                        }
    
                        delegateToUse = this.initDelegate(wac);
                    }
    
                    this.delegate = delegateToUse;
                }
            }
    
    //调用实际的FilterChainProxy 的doFilterInternal()方法 去获取所有的拦截器并进行过滤处理
            this.invokeDelegate(delegateToUse, request, response, filterChain);
        }
    

    调用实际的FilterChainProxy 的doFilter()方法 去获取所有的拦截器并进行过滤处理。

    2、FilterChainProxy类

        最终调用FilterChainProxy 的doFilterInternal()方法,获取所有的过滤器实例

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
            if(clearContext) {
                try {
                    request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                    //doFilter 调用doFilterInternal方法
                    this.doFilterInternal(request, response, chain);
                } finally {
                    SecurityContextHolder.clearContext();
                    request.removeAttribute(FILTER_APPLIED);
                }
            } else {
                this.doFilterInternal(request, response, chain);
            }
    
        }
    
        private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
            HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
             //过去所有的过滤器
            List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
            if(filters != null && filters.size() != 0) {
                FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
                vfc.doFilter(fwRequest, fwResponse);
            } else {
                if(logger.isDebugEnabled()) {
                    logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null?" has no matching filters":" has an empty filter list"));
                }
    
                fwRequest.reset();
                chain.doFilter(fwRequest, fwResponse);
            }
        }
    
    
      private List<Filter> getFilters(HttpServletRequest request) {
           //遍历所有的matcher类 如果支持就继续获取
            Iterator var2 = this.filterChains.iterator();
    
            SecurityFilterChain chain;
            do {
                if(!var2.hasNext()) {
                    return null;
                }
    
                chain = (SecurityFilterChain)var2.next();
            } while(!chain.matches(request));
            //后去匹配中的所有过滤器
            return chain.getFilters();
        }

    如上 其实是获取到本次请求的所有filter 并安装指定顺序进行执行doFilter()方法

    这是笔者本次业务请求所要执行的所有过滤器 

    1.     WebAsyncManagerIntegrationFilter
    2.      SecurityContextPersistenceFilter
    3.      HeaderWriterFilter     
    4.      LogoutFilter
    5.      UsernamePasswordAuthenticationFilter
    6.      RequestCacheAwareFilter
    7.      SecurityContextHolderAwareRequestFilter
    8.      AnonymousAuthenticationFilter
    9.      SessionManagementFilter
    10.      ExceptionTranslationFilter
    11.      FilterSecurityInterceptor

    关于springSecutity拦截器的介绍请参考如下链接地址

    Spring Security 核心过滤器链分析_CatalpaFlat的博客-CSDN博客

    Spring Security(四)--核心过滤器源码分析 | 程序猿DD

    Spring Security的核心拦截器 - 丿少女梦丶 - 博客园

    spring security 11种过滤器介绍_大师出版必是精品-CSDN博客

    Spring Security核心过滤器源码分析-(三)_m0_37834471的博客-CSDN博客

    Spring Security 学习笔记-授权控制过滤器 - Joyen.fu - 博客园

    展开全文
  • spring security——基本介绍(一)

    万次阅读 多人点赞 2019-01-16 10:31:23
    一、spring security 简介 spring security 的核心功能主要包括: 认证 (你是谁) 授权 (你能干什么) 攻击防护 (防止伪造身份) 其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic ...

    一、spring security 简介

            spring security 的核心功能主要包括:

    • 认证 (你是谁)
    • 授权 (你能干什么)
    • 攻击防护 (防止伪造身份)

         其核心就是一组过滤器链,项目启动后将会自动配置。最核心的就是 Basic Authentication Filter 用来认证用户的身份,一个在spring security中一种过滤器处理一种认证方式。

    比如,对于username password认证过滤器来说, 

    会检查是否是一个登录请求;

    是否包含username 和 password (也就是该过滤器需要的一些认证信息) ;

    如果不满足则放行给下一个。

         下一个按照自身职责判定是否是自身需要的信息,basic的特征就是在请求头中有 Authorization:Basic eHh4Onh4 的信息。中间可能还有更多的认证过滤器。最后一环是 FilterSecurityInterceptor,这里会判定该请求是否能进行访问rest服务,判断的依据是 BrowserSecurityConfig中的配置,如果被拒绝了就会抛出不同的异常(根据具体的原因)。Exception Translation Filter 会捕获抛出的错误,然后根据不同的认证方式进行信息的返回提示。

    注意:绿色的过滤器可以配置是否生效,其他的都不能控制。

    二、入门项目

         首先创建spring boot项目HelloSecurity,其pom主要依赖如下:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    然后在src/main/resources/templates/目录下创建页面:

    home.html

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
            <title>Spring Security Example</title>
        </head>
        <body>
            <h1>Welcome!</h1>
    ​
            <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
        </body>
    </html>

    我们可以看到, 在这个简单的视图中包含了一个链接: “/hello”. 链接到了如下的页面,Thymeleaf模板如下:

    hello.html

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
            <title>Hello World!</title>
        </head>
        <body>
            <h1>Hello world!</h1>
        </body>
    </html>

    Web应用程序基于Spring MVC。 因此,你需要配置Spring MVC并设置视图控制器来暴露这些模板。 如下是一个典型的Spring MVC配置类。在src/main/java/hello目录下(所以java都在这里):

    @Configuration
    public class MvcConfig extends WebMvcConfigurerAdapter {
        @Override
        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("/home").setViewName("home");
            registry.addViewController("/").setViewName("home");
            registry.addViewController("/hello").setViewName("hello");
            registry.addViewController("/login").setViewName("login");
        }
    }

         addViewControllers()方法(覆盖WebMvcConfigurerAdapter中同名的方法)添加了四个视图控制器。 两个视图控制器引用名称为“home”的视图(在home.html中定义),另一个引用名为“hello”的视图(在hello.html中定义)。 第四个视图控制器引用另一个名为“login”的视图。 将在下一部分中创建该视图。此时,可以跳过来使应用程序可执行并运行应用程序,而无需登录任何内容。然后启动程序如下:

    @SpringBootApplication
    public class Application {
    ​
        public static void main(String[] args) throws Throwable {
            SpringApplication.run(Application.class, args);
        }
    }

    2、加入Spring Security

         假设你希望防止未经授权的用户访问“/ hello”。 此时,如果用户点击主页上的链接,他们会看到问候语,请求被没有被拦截。 你需要添加一个障碍,使得用户在看到该页面之前登录。您可以通过在应用程序中配置Spring Security来实现。 如果Spring Security在类路径上,则Spring Boot会使用“Basic认证”来自动保护所有HTTP端点。 同时,你可以进一步自定义安全设置。首先在pom文件中引入:

    <dependencies>
        ...
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
            </dependency>
        ...
    </dependencies>

    如下是安全配置,使得只有认证过的用户才可以访问到问候页面:

    @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("user").password("password").roles("USER");
        }
    }

         WebSecurityConfig类使用了@EnableWebSecurity注解 ,以启用Spring Security的Web安全支持,并提供Spring MVC集成。它还扩展了WebSecurityConfigurerAdapter,并覆盖了一些方法来设置Web安全配置的一些细节。

         configure(HttpSecurity)方法定义了哪些URL路径应该被保护,哪些不应该。具体来说,“/”和“/ home”路径被配置为不需要任何身份验证。所有其他路径必须经过身份验证。

         当用户成功登录时,它们将被重定向到先前请求的需要身份认证的页面。有一个由 loginPage()指定的自定义“/登录”页面,每个人都可以查看它。

         对于configureGlobal(AuthenticationManagerBuilder) 方法,它将单个用户设置在内存中。该用户的用户名为“user”,密码为“password”,角色为“USER”。

         现在我们需要创建登录页面。前面我们已经配置了“login”的视图控制器,因此现在只需要创建登录页面即可:

    login.html

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
            <title>Spring Security Example </title>
        </head>
        <body>
            <div th:if="${param.error}">
                Invalid username and password.
            </div>
            <div th:if="${param.logout}">
                You have been logged out.
            </div>
            <form th:action="@{/login}" method="post">
                <div><label> User Name : <input type="text" name="username"/> </label></div>
                <div><label> Password: <input type="password" name="password"/> </label></div>
                <div><input type="submit" value="Sign In"/></div>
            </form>
        </body>
    </html>

         你可以看到,这个Thymeleaf模板只是提供一个表单来获取用户名和密码,并将它们提交到“/ login”。 根据配置,Spring Security提供了一个拦截该请求并验证用户的过滤器。 如果用户未通过认证,该页面将重定向到“/ login?error”,并在页面显示相应的错误消息。 注销成功后,我们的应用程序将发送到“/ login?logout”,我们的页面显示相应的登出成功消息。最后,我们需要向用户提供一个显示当前用户名和登出的方法。 更新hello.html 向当前用户打印一句hello,并包含一个“注销”表单,如下所示:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
        <head>
            <title>Hello World!</title>
        </head>
        <body>
            <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
            <form th:action="@{/logout}" method="post">
                <input type="submit" value="Sign Out"/>
            </form>
        </body>
    </html>

    三、参数详解

    1、注解 @EnableWebSecurity

         在 Spring boot 应用中使用 Spring Security,用到了 @EnableWebSecurity注解,官方说明为,该注解和 @Configuration 注解一起使用, 注解 WebSecurityConfigurer 类型的类,或者利用@EnableWebSecurity 注解继承 WebSecurityConfigurerAdapter的类,这样就构成了 Spring Security 的配置。

    2、抽象类 WebSecurityConfigurerAdapter

         一般情况,会选择继承 WebSecurityConfigurerAdapter 类,其官方说明为:WebSecurityConfigurerAdapter 提供了一种便利的方式去创建 WebSecurityConfigurer的实例,只需要重写 WebSecurityConfigurerAdapter 的方法,即可配置拦截什么URL、设置什么权限等安全控制。

    3、方法 configure(AuthenticationManagerBuilder auth) 和 configure(HttpSecurity http)

         Demo 中重写了 WebSecurityConfigurerAdapter 的两个方法:

       /**
         * 通过 {@link #authenticationManager()} 方法的默认实现尝试获取一个 {@link AuthenticationManager}.
         * 如果被复写, 应该使用{@link AuthenticationManagerBuilder} 来指定 {@link AuthenticationManager}.
         *
         * 例如, 可以使用以下配置在内存中进行注册公开内存的身份验证{@link UserDetailsService}:
         *
         * // 在内存中添加 user 和 admin 用户
         * @Override
         * protected void configure(AuthenticationManagerBuilder auth) {
         *     auth
         *       .inMemoryAuthentication().withUser("user").password("password").roles("USER").and()
         *         .withUser("admin").password("password").roles("USER", "ADMIN");
         * }
         *
         * // 将 UserDetailsService 显示为 Bean
         * @Bean
         * @Override
         * public UserDetailsService userDetailsServiceBean() throws Exception {
         *     return super.userDetailsServiceBean();
         * }
         *
         */
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            this.disableLocalConfigureAuthenticationBldr = true;
        }
    
    
        /**
         * 复写这个方法来配置 {@link HttpSecurity}. 
         * 通常,子类不能通过调用 super 来调用此方法,因为它可能会覆盖其配置。 默认配置为:
         * 
         * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
         *
         */
        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();
        }

    4、final 类 HttpSecurity

    HttpSecurity 常用方法及说明:

    方法说明
    openidLogin()用于基于 OpenId 的验证
    headers()将安全标头添加到响应
    cors()配置跨域资源共享( CORS )
    sessionManagement()允许配置会话管理
    portMapper()允许配置一个PortMapper(HttpSecurity#(getSharedObject(class))),其他提供SecurityConfigurer的对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443
    jee()配置基于容器的预认证。 在这种情况下,认证由Servlet容器管理
    x509()配置基于x509的认证
    rememberMe允许配置“记住我”的验证
    authorizeRequests()允许基于使用HttpServletRequest限制访问
    requestCache()允许配置请求缓存
    exceptionHandling()允许配置错误处理
    securityContext()HttpServletRequests之间的SecurityContextHolder上设置SecurityContext的管理。 当使用WebSecurityConfigurerAdapter时,这将自动应用
    servletApi()HttpServletRequest方法与在其上找到的值集成到SecurityContext中。 当使用WebSecurityConfigurerAdapter时,这将自动应用
    csrf()添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用
    logout()添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success”
    anonymous()允许配置匿名用户的表示方法。 当与WebSecurityConfigurerAdapter结合使用时,这将自动应用。 默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS”
    formLogin()指定支持基于表单的身份验证。如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面
    oauth2Login()根据外部OAuth 2.0或OpenID Connect 1.0提供程序配置身份验证
    requiresChannel()配置通道安全。为了使该配置有用,必须提供至少一个到所需信道的映射
    httpBasic()配置 Http Basic 验证
    addFilterAt()在指定的Filter类的位置添加过滤器

    5、类 AuthenticationManagerBuilder

    /**
    * {@link SecurityBuilder} used to create an {@link AuthenticationManager}. Allows for
    * easily building in memory authentication, LDAP authentication, JDBC based
    * authentication, adding {@link UserDetailsService}, and adding
    * {@link AuthenticationProvider}'s.
    */

            意思是,AuthenticationManagerBuilder 用于创建一个 AuthenticationManager,让我能够轻松的实现内存验证、LADP验证、基于JDBC的验证、添加UserDetailsService、添加AuthenticationProvider。

    四、原理讲解

    1、校验流程图

     

    2、源码分析

    • AbstractAuthenticationProcessingFilter 抽象类
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                throws IOException, ServletException {
    ​
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
    ​
            if (!requiresAuthentication(request, response)) {
                chain.doFilter(request, response);
    ​
                return;
            }
    ​
            if (logger.isDebugEnabled()) {
                logger.debug("Request is to process authentication");
            }
    ​
            Authentication authResult;
    ​
            try {
                authResult = attemptAuthentication(request, response);
                if (authResult == null) {
                    // return immediately as subclass has indicated that it hasn't completed
                    // authentication
                    return;
                }
                sessionStrategy.onAuthentication(authResult, request, response);
            }
            catch (InternalAuthenticationServiceException failed) {
                logger.error(
                        "An internal error occurred while trying to authenticate the user.",
                        failed);
                unsuccessfulAuthentication(request, response, failed);
    ​
                return;
            }
            catch (AuthenticationException failed) {
                // Authentication failed
                unsuccessfulAuthentication(request, response, failed);
    ​
                return;
            }
    ​
            // Authentication success
            if (continueChainBeforeSuccessfulAuthentication) {
                chain.doFilter(request, response);
            }
    ​
            successfulAuthentication(request, response, chain, authResult);
        }

            调用 requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。如果需要验证,则会调用 attemptAuthentication(HttpServletRequest, HttpServletResponse) 方法,有三种结果:

    1. 返回一个 Authentication 对象。配置的 SessionAuthenticationStrategy` 将被调用,然后 然后调用 successfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication) 方法。
    2. 验证时发生 AuthenticationException。unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException) 方法将被调用。
    3. 返回Null,表示身份验证不完整。假设子类做了一些必要的工作(如重定向)来继续处理验证,方法将立即返回。假设后一个请求将被这种方法接收,其中返回的Authentication对象不为空。
    • UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子类)
    public Authentication attemptAuthentication(HttpServletRequest request,
                HttpServletResponse response) throws AuthenticationException {
            if (postOnly && !request.getMethod().equals("POST")) {
                throw new AuthenticationServiceException(
                        "Authentication method not supported: " + request.getMethod());
            }
    ​
            String username = obtainUsername(request);
            String password = obtainPassword(request);
    ​
            if (username == null) {
                username = "";
            }
    ​
            if (password == null) {
                password = "";
            }
    ​
            username = username.trim();
    ​
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                    username, password);
    ​
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
    ​
            return this.getAuthenticationManager().authenticate(authRequest);
        }

            attemptAuthentication () 方法将 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 对象,用于 AuthenticationManager 的验证(即 this.getAuthenticationManager().authenticate(authRequest) )。默认情况下注入 Spring 容器的 AuthenticationManager 是 ProviderManager。

    • ProviderManager(AuthenticationManager的实现类)
    public Authentication authenticate(Authentication authentication)
        throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
    ​
        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }
    ​
            if (debug) {
                logger.debug("Authentication attempt using "
                             + provider.getClass().getName());
            }
    ​
            try {
                result = provider.authenticate(authentication);
    ​
                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            catch (AccountStatusException e) {
                prepareException(e, authentication);
                // SEC-546: Avoid polling additional providers if auth failure is due to
                // invalid account status
                throw e;
            }
            catch (InternalAuthenticationServiceException e) {
                prepareException(e, authentication);
                throw e;
            }
            catch (AuthenticationException e) {
                lastException = e;
            }
        }
    ​
        if (result == null && parent != null) {
            // Allow the parent to try.
            try {
                result = parent.authenticate(authentication);
            }
            catch (ProviderNotFoundException e) {
                // ignore as we will throw below if no other exception occurred prior to
                // calling parent and the parent
                // may throw ProviderNotFound even though a provider in the child already
                // handled the request
            }
            catch (AuthenticationException e) {
                lastException = e;
            }
        }
    ​
        if (result != null) {
            if (eraseCredentialsAfterAuthentication
                && (result instanceof CredentialsContainer)) {
                // Authentication is complete. Remove credentials and other secret data
                // from authentication
                ((CredentialsContainer) result).eraseCredentials();
            }
    ​
            eventPublisher.publishAuthenticationSuccess(result);
            return result;
        }
    ​
        // Parent was null, or didn't authenticate (or throw an exception).
    ​
        if (lastException == null) {
            lastException = new ProviderNotFoundException(messages.getMessage(
                "ProviderManager.providerNotFound",
                new Object[] { toTest.getName() },
                "No AuthenticationProvider found for {0}"));
        }
    ​
        prepareException(lastException, authentication);
    ​
        throw lastException;
    }

            尝试验证 Authentication 对象。AuthenticationProvider 列表将被连续尝试,直到 AuthenticationProvider 表示它能够认证传递的过来的Authentication 对象。然后将使用该 AuthenticationProvider 尝试身份验证。如果有多个 AuthenticationProvider 支持验证传递过来的Authentication 对象,那么由第一个来确定结果,覆盖早期支持AuthenticationProviders 所引发的任何可能的AuthenticationException。 成功验证后,将不会尝试后续的AuthenticationProvider。如果最后所有的 AuthenticationProviders 都没有成功验证 Authentication 对象,将抛出 AuthenticationException。从代码中不难看出,由 provider 来验证 authentication, 核心点方法是:

    Authentication result = provider.authenticate(authentication);

    此处的 provider 是 AbstractUserDetailsAuthenticationProvider,AbstractUserDetailsAuthenticationProvider 是AuthenticationProvider的实现,看看它的 authenticate(authentication) 方法:

    // 验证 authentication
    public Authentication authenticate(Authentication authentication)
                throws AuthenticationException {
            Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                    messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.onlySupports",
                            "Only UsernamePasswordAuthenticationToken is supported"));
    ​
            // Determine username
            String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                    : authentication.getName();
    ​
            boolean cacheWasUsed = true;
            UserDetails user = this.userCache.getUserFromCache(username);
    ​
            if (user == null) {
                cacheWasUsed = false;
    ​
                try {
                    user = retrieveUser(username,
                            (UsernamePasswordAuthenticationToken) authentication);
                }
                catch (UsernameNotFoundException notFound) {
                    logger.debug("User '" + username + "' not found");
    ​
                    if (hideUserNotFoundExceptions) {
                        throw new BadCredentialsException(messages.getMessage(
                                "AbstractUserDetailsAuthenticationProvider.badCredentials",
                                "Bad credentials"));
                    }
                    else {
                        throw notFound;
                    }
                }
    ​
                Assert.notNull(user,
                        "retrieveUser returned null - a violation of the interface contract");
            }
    ​
            try {
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (AuthenticationException exception) {
                if (cacheWasUsed) {
                    // There was a problem, so try again after checking
                    // we're using latest data (i.e. not from the cache)
                    cacheWasUsed = false;
                    user = retrieveUser(username,
                            (UsernamePasswordAuthenticationToken) authentication);
                    preAuthenticationChecks.check(user);
                    additionalAuthenticationChecks(user,
                            (UsernamePasswordAuthenticationToken) authentication);
                }
                else {
                    throw exception;
                }
            }
    ​
            postAuthenticationChecks.check(user);
    ​
            if (!cacheWasUsed) {
                this.userCache.putUserInCache(user);
            }
    ​
            Object principalToReturn = user;
    ​
            if (forcePrincipalAsString) {
                principalToReturn = user.getUsername();
            }
    ​
            return createSuccessAuthentication(principalToReturn, authentication, user);
        }

    AbstractUserDetailsAuthenticationProvider 内置了缓存机制,从缓存中获取不到的 UserDetails 信息的话,就调用如下方法获取用户信息,然后和 用户传来的信息进行对比来判断是否验证成功。

    // 获取用户信息
    UserDetails user = retrieveUser(username,
     (UsernamePasswordAuthenticationToken) authentication);

    retrieveUser() 方法在 DaoAuthenticationProvider 中实现,DaoAuthenticationProvider 是 AbstractUserDetailsAuthenticationProvider的子类。具体实现如下:

    protected final UserDetails retrieveUser(String username,
                UsernamePasswordAuthenticationToken authentication)
                throws AuthenticationException {
            UserDetails loadedUser;
    ​
            try {
                loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            }
            catch (UsernameNotFoundException notFound) {
                if (authentication.getCredentials() != null) {
                    String presentedPassword = authentication.getCredentials().toString();
                    passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
                            presentedPassword, null);
                }
                throw notFound;
            }
            catch (Exception repositoryProblem) {
                throw new InternalAuthenticationServiceException(
                        repositoryProblem.getMessage(), repositoryProblem);
            }
    ​
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }

    可以看到此处的返回对象 userDetails 是由 UserDetailsService 的 #loadUserByUsername(username) 来获取的。

     

    对于看本文比较困难的同学可以移步:https://www.lanqiao.cn/courses/3013

    邀请码:STyLDQzM

    其实内容和博客差不多,只不过更详细(就是一步一步的来,所有步骤有详细过程记录和截图,按照课程步骤最终能完整的操作完整个项目过程,适合小白),版本上也有所升级,还有就是有问题可以直接沟通,谢谢支持!

    展开全文
  • Springboot+Spring-Security+JWT 实现用户登录和权限认证

    万次阅读 多人点赞 2019-06-08 22:26:17
    如今,互联网项目对于安全的要求越来越...Security,该框架提供了一整套比较成熟,也很完整的机制用于处理各类场景下的可以基于权限,资源路径,以及授权方面的解决方案,部分模块支持定制化,而且在和oauth2.0进...

    如今,互联网项目对于安全的要求越来越严格,这就是对后端开发提出了更多的要求,目前比较成熟的几种大家比较熟悉的模式,像RBAC 基于角色权限的验证,shiro框架专门用于处理权限方面的,另一个比较流行的后端框架是Spring-Security,该框架提供了一整套比较成熟,也很完整的机制用于处理各类场景下的可以基于权限,资源路径,以及授权方面的解决方案,部分模块支持定制化,而且在和oauth2.0进行了很好的无缝连接,在移动互联网的授权认证方面有很强的优势,具体的使用大家可以结合自己的业务场景进行选取和使用

    下面来说说关于单点登录中目前比较流行的一种使用方式,就是springsecurity+jwt实现无状态下用户登录;

    JWT

    在之前的篇章中大致提到过,使用jwt在分布式项目中进行用户信息的认证很方便,各个模块只需要知道配置的秘钥,就可以解密token中用户的基本信息,完成认证,很方便,关于使用jwt的基本内容可以查阅相关资料,或者参考我之前的一篇;

    整理一下思路

    1、搭建springboot工程
    2、导入springSecurity跟jwt的依赖
    3、用户的实体类,dao层,service层(真正开发时再写,这里就直接调用dao层操作数据库)
    4、实现UserDetailsService接口
    5、实现UserDetails接口
    6、验证用户登录信息的拦截器
    7、验证用户权限的拦截器
    8、springSecurity配置

    展开全文
  • spring security原理和机制

    千次阅读 多人点赞 2021-06-25 21:35:08
    一、SpringSecurity 框架简介 Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的 成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方 案。 正如你...
  • SpringBoot集成Spring Security(1)——入门程序

    万次阅读 多人点赞 2018-05-09 09:47:20
    因为项目需要,第一次接触Spring Security,早就听闻Spring Security强大但上手困难,今天学习了一天,翻遍了全网资料,才仅仅出入门道,特整理这篇文章来让后来者少踩一点坑(本文附带实例程序,请放心食用) ...
  • springboot+security实现权限管理

    万次阅读 多人点赞 2019-03-21 14:06:45
    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,...
  • 在Spring Boot中使用Spring Security实现权限控制

    万次阅读 多人点赞 2017-01-12 15:52:37
    Spring Boot框架我们前面已经介绍了很多了,相信看了前面的博客的小伙伴对Spring Boot...OK,那我们今天要说的是Spring Boot中另外一个比较重要的东西,那就是Spring Security,这是一个专门针对基于Spring的项目的安全
  • Spring Security

    万次阅读 2019-07-18 16:05:00
    Spring Security 简介 Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解 决方案的安全框架。它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC,DI...
  • Spring Security 工作原理概览

    万次阅读 多人点赞 2019-04-27 08:02:58
    本文由读者 muggle 投稿,muggle 是一位具备极客精神的90后单身老实猿,对 Spring Security 有丰富的使用经验,muggle 个人博客地址是 h...
  • Spring Security中,安全构建器HttpSecurity和WebSecurity的区别是 : WebSecurity不仅通过HttpSecurity定义某些请求的安全控制,也定义其他某些请求可以忽略安全控制; HttpSecurity仅用于定义需要安全控制的请求; ...
  • Spring Security 和 SpringCloud Security 有什么区别?
  • SpringSecurity

    千次阅读 2020-12-13 21:56:20
    SpringSecurity(spring安全)SpringSecurity是用来干嘛的?Spring Security 核心功能Spring Security 工作流程1.导入POM坐标,thymeleaf用来测试Restful,security2.访问资源3.AOP横切进入,不用繁琐配置,来一个...
  • 前面我们已经分析了Spring Security的核心过滤器FilterChainProxy的创建和运行过程,认识了建造者和配置器的作用。 现在我们知道WebSecurity作为一个建造者就是用来创建核心过滤器FilterChainProxy实例的。 Web...
  • SpringSecurity入门到入土教程_1

    万次阅读 2020-12-30 21:45:59
    文章目录SpringSecurity 教程1. 简介1.1 概念1.2 入门案例1.3 自定义登录逻辑1.4 自定义登录页面1.5 自定用户名参数1.5 自定义成功处理器1.6 登录失败处理器1.7 认证anyRequestantMatchersregexMatchers1.8 授权基于...
  • Spring Security 简介

    万次阅读 多人点赞 2018-04-21 09:53:02
    该类的构造方法需要 3 个参数,分别是 javax.sql.DataSource表示的数据源、org.springframework.security.acls.jdbc.LookupStrategy表示的数据库的查询策略和org.springframework.security.acls.model.AclCache表示...
  • springboot2.0+,整合security配置security关闭http基本验证

    万次阅读 多人点赞 2019-01-12 15:14:34
    今天学习security,遇到了很多坑例如: spring boot1.5配置security关闭http基本验证,只需要在application.properites中配置 security.basic.enabled=false即可, 但是spring boot 2.0+之后这样配置就不能生效了。 ...
  • Spring Security 中的四种权限控制方式

    万次阅读 多人点赞 2020-06-17 09:21:49
    Spring Security 中对于权限控制默认已经提供了很多了,但是,一个优秀的框架必须具备良好的扩展性,恰好,Spring Security 的扩展性就非常棒,我们既可以使用 Spring Security 提供的方式做授权,也可以自定义授权...
  • 华为HCIE-Security考试心得

    万次阅读 多人点赞 2019-10-02 13:26:41
    从2016年刚上大学对网络的一无所知,到2019-09-09一次通过HCIE-Security认证的一次记录与总结。 也是对我一个阶段的一次学习总结。 对HCIE的认识: 因为从小对计算机技术的热爱,特别羡慕那电影里的电脑高手,对着...
  • Springboot集成SpringSecurity 附代码

    万次阅读 多人点赞 2018-02-12 17:37:06
    一、Spring security 是什么? Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。 它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI...
  • 基于Spring Security实现权限管理系统

    万次阅读 多人点赞 2018-11-06 16:49:49
    基于Spring Security实现权限管理系统 稍微复杂一点的后台系统都会涉及到用户权限管理。何谓用户权限?我的理解就是,权限就是对数据(系统的实体类)和数据可进行的操作(增删查改)的集中管理。要构建一个可用的...
  • spring security中可以通过表达式控制方法权限: Spring Security中定义了四个支持使用表达式的注解,分别是@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。其中前两者可以用来在方法调用前或者调用后...
  • springboot + spring security验证token进行用户认证

    万次阅读 多人点赞 2018-11-23 15:40:56
    SecurityContextHolder是spring security最基本的组件。用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限等这些都被保存在SecurityContextHolder中。...
  • 错误信息: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provid...
  • Shiro和Spring Security对比

    万次阅读 多人点赞 2017-09-04 16:27:04
    目前,使用Apache Shiro的人越来越多,因为它相当简单,对比Spring Security,可能没有Spring Security做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的Shiro就足够了。对于它俩到底...
  • HttpSecurity初步理解

    万次阅读 多人点赞 2019-06-19 10:49:40
    Spring Security是一个强大的、可根据需求高度自定义的用户认证和访问控制框架。Spring Security怎么保证所有向Spring application发送请求的用户必须先通过认证;怎么保证它自己支持用户通过表单的方式进行认证。...
  • SpringSecurity的配置详解——websecurity

    千次阅读 2019-06-09 17:08:19
    Websecurity类主要的继承关系为自Websecurity->AbstractConfiguredSecurityBuilder->AbstractSecurityBuilder,其中上一步调用的build方法就在AbstractSecurityBuilder中 SpringSecurity在这个类中实现了创建...
  • Cyber security和Network security的区别

    万次阅读 2017-10-10 14:30:40
    主要是关于network security 和cyber security的区别。
  • Spring Security介绍

    千次阅读 多人点赞 2020-11-03 12:59:24
    1、Spring Security的架构及核心组件:(1)认证;(2)权限拦截;(3)数据库管理;(4)权限缓存;(5)自定义决策; 2、环境搭建与使用,使用当前热门的Spring Boot来搭建环境,结合项目中实际的例子来做几个Case; 3、...
  • HttpSecurity是Spring Security Config用于配置http请求安全控制的安全构建器(类似于Spring Security XML配置中的http命名空间配置部分),它的构建目标是一...该目标SecurityFilterChain最终会被Spring Security的安...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 898,475
精华内容 359,390
关键字:

security