Spring Security 学习

一、简介

      Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。

  • 用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
  • 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。   
  • spring security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。

 2、框架原理

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

如下为其主要过滤器  

        WebAsyncManagerIntegrationFilter 
        SecurityContextPersistenceFilter 
        HeaderWriterFilter 
        CorsFilter 
        LogoutFilter
        RequestCacheAwareFilter
        SecurityContextHolderAwareRequestFilter
        AnonymousAuthenticationFilter
        SessionManagementFilter
        ExceptionTranslationFilter
        FilterSecurityInterceptor
        UsernamePasswordAuthenticationFilter
        BasicAuthenticationFilter

3、框架的核心组件

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

4、数据库的管理

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

1、前提:基于自身业务需要:自定义了一个springSecurity安全框架的配置类 继承WebSecurityConfigurerAdapter,重写其中的方法configure。

public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
    //该方法决定了那些请求会被拦截,即拦截策略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
       
    }

    //哪些请求不用被检查
    @Override
    public void configure(WebSecurity web) throws Exception {
       
    }
    //可以指定不通过数据库,基于内存的验证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {            
    
    }
}

 在我们实现该类后,在web容器启动的过程中该类实例对象会被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()方法:该方法中主要执行如下:

     1、创建webSecurity对象

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

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

2.2、springSecurityFilterChain()方法

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

2.3、AbstractSecurityBuilder类调用build方法来返回过滤器链,其实是调用子类SecurityBuilder的dobuild()方法,

       doBuild()核心方法 init(),configure(),perFormBuild()

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

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

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

(2)、第二步configure

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

(3)、第三步 peformBuild

具体的实现逻辑在WebSecurity类中 ,这个方法中最主要的任务就是遍历securityFilterChainBuilders属性中的SecurityBuilder对象,并调用他的build方法。 这个securityFilterChainBuilders属性我们前面也有提到过,就是在WebSecurityConfigurerAdapter类的init方法中获取http后赋值给了WebSecurity。因此这个地方就是调用httpSecurity的build方法。httpSecurity的build方式向其中追加一个个过滤器。

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

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

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

例子如下:基于Spring Boot实现,JDK1.8,基于内存存储演示

1、引入依赖,pom.xml

    <dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</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>

2、新建类继承WebSecurityConfigurerAdapter

package com.dongtian.demo;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
@EnableAutoConfiguration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter{
    //该方法决定了那些请求会被拦截,即拦截策略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/").permitAll()//项目主路径不用验证
                .anyRequest().authenticated()//其他请求要被检查
                .and()
                .logout().permitAll()
                .and()
                .formLogin();
        http.csrf().disable();//关闭csrf验证
    }

    //哪些请求不用被检查
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/js/**","/css/**","/image/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        不通过数据库,基于内存的验证
        // 指定内存中有一个角色为ADMIN(admin权限)用户,用户名admin,密码123456,可以指定多个人,使用自己定义的密码解析器
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).
                withUser("admin").
                password(new BCryptPasswordEncoder().encode("123456")).roles("ADMIN");
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).
                withUser("user1").
                password(new BCryptPasswordEncoder().encode("123456")).roles("USER");

    }
}

3、自定义密码解析器

package com.dongtian.demo;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class MyPasswordEncoder extends BCryptPasswordEncoder {
    public static void main(String[] args) {
        MyPasswordEncoder myPasswordEncoder = new MyPasswordEncoder();
        //加密
        String password = myPasswordEncoder.encode("123456");
        System.out.println(password);
        //匹配
        boolean b = myPasswordEncoder.matches("123456",password);
        System.out.println(b);
    }
}

4、测试

package com.dongtian.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
@EnableAutoConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DemoApplication {

	public static void main(String[] args) {
	    SpringApplication.run(DemoApplication.class, args);
	}
    //项目主路径不用验证
    @RequestMapping("/")
	public String home(){
	    return "hello spring boot";
    }
    //其他路径请求要被检查
    @RequestMapping("/hello")
    public String sayhello(){
        return "hello world";
    }

    //@PreAuthorize("hashRole('ROLE_+角色')"):表示必需要有这个角色的权限才能访问的方法
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @RequestMapping("/roleAuth")
    public String adminAuth(){
	    return "admin auth";
    }
}

5、结果

(1)主路径请求不用拦截


(2)其他路径请求需要拦截

(3)权限访问,使用user1登录,访问/roleAuth,因为没有权限会禁止访问

使用admin登录,访问/roleAuth,访问成功

四、优缺点

 

借鉴博客:https://blog.csdn.net/liushangzaibeijing/article/details/81220610

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值