SpringSecurity快速入门

670 阅读5分钟

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于spring的应用程序的实际标准。

Spring Security是一个侧重于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于可以很容易地扩展它以满足定制需求

特点:

  1. 对身份验证和授权的全面和可扩展支持
  2. 防止攻击,如会话固定,点击劫持,跨站点请求伪造等
  3. Servlet API的集成
  4. 与Spring Web MVC的可选集成

在官网快速创建一个SpringBoot项目并添加Security依赖

1.png

下载或者导入本地的idea即可,打开idea打开之后就是一个常规的SpringBoot项目

添加接口:

@RestController
public class HelloController {

    @GetMapping("hello")
    public String Hello() {
        return "hello security";
    }
}

启动项目访问接口时:

2.png

当我们添加了Security的依赖之后,系统所有资源默认就受到保护,访问时需要登录 。未配置用户和密码时,默认用户名为 user ,默认密码在项目启动时就打印出来如图:

3.png

现在只需要使用user 和以上密码 登录,即可访问到我们的所访问的接口。

疑问:为什么我们添加了Security的依赖之后整个项目的资源都被拦截了?

SpringBoot自动配置原理:

我们都知道SpringBoot的优势就是默认提供了许多框架的自动配置,在spring-boot-autoconfigure.jar的META-INF/spring.factories文件中定义了哪些默认自动配置的框架,搜索security相关的配置如下图所示:

4-1.png

我们点击打开SecurityAutoConfiguration类源码如下:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}

源码分析:

注解@Condition

//指定组件只有在所有匹配时才有资格注册
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

使用该注解的类/方法只有满足某种条件,才会被注册进入Spring容器中,其value就是需要满足的条件

注解: @ConditionalOnClass()

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

	Class<?>[] value() default {};
	
	String[] name() default {};

}

@ConditionalOnClass()注解类上添加了@Condition注解,使用此注解时需要指定其value,value类型为Class类对象集合,使用该注解的类/方法只有满足某种条件,才会被注册进入Spring容器中。而完成spring-security的自动配置中所需要的条件如下图所示:

4.png

DefaultAuthenticationEventPublisher所在的包为:

5.png

正是在我们添加的security依赖里面!!

SpringBoot默认提供了许多常用框架的Bean在spring-boot-autoconfigure.jar的META-INF/spring.factories文件中,当我们引入相关依赖时会通过注解@ConditionOnClass()注解判断,是否将已经bean注册到Spring容器中。

SpringSecurity提供的默认配置:

源码:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}

源码分析:

@EnableConfigurationProperties(SecurityProperties.class),@EnableConfigurationProperties()配置类支持注解,注解里面对应的就是配置类

SecurityProperties.class源码:

// application.yml配置文件中security的配置
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
    // 过滤器执行等级
    public static final int BASIC_AUTH_ORDER = Ordered.LOWEST_PRECEDENCE - 5;
    
    public static final int IGNORED_ORDER = Ordered.HIGHEST_PRECEDENCE;
    //Spring Security过滤器在servlet容器中的默认顺序
    public static final int DEFAULT_FILTER_ORDER = OrderedFilter.REQUEST_WRAPPER_FILTER_MAX_ORDER - 100;
    // 过滤器
	private final Filter filter = new Filter();
    // 用户
	private final User user = new User();
    
    
	public static class Filter {

		/**
		 *过滤器链执行顺序
		 */
		private int order = DEFAULT_FILTER_ORDER;

		/**
		 * 过滤器链Dispatcher类型
		 */
		private Set<DispatcherType> dispatcherTypes = new HashSet<>(
				Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
				
        // getter and setter

	}
	public static class User {

        // 默认账号
		private String name = "user";

		// 默认密码,
		private String password = UUID.randomUUID().toString();

        // 默认角色
		private List<String> roles = new ArrayList<>();

		private boolean passwordGenerated = true;
        
        // getter and setter

	}
}

在这个配置类中,定义了过滤器执行等级、过滤器链执行等级和默认的用户账号和密码,此密码就是我们启动项目时控制台打印的那一串字符

注解分析:

@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,SecurityDataConfiguration.class })

@Import注解: 指示要导入的一个或多个组件类,

SpringBootWebSecurityConfiguration.class源码

/*
web安全的默认配置。它依赖于Spring Security
内容协商策略,以确定使用何种身份验证。如果
user指定自己的WebSecurityConfigurerAdapter或
SecurityFilterChain bean,这将完全退出,用户应该
指定他们希望作为自定义安全性的一部分配置的所有位
配置。
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {

	@Bean
	@Order(SecurityProperties.BASIC_AUTH_ORDER)
	SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
		http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
		return http.build();
	}

}

具体看defaultSecurityFilterChain()方法:在方法上贴了一个@Bean注解说明当项目启动扫描包时将运行此方法,从方法中可以看到security默认把所有的http请求都拦截了,而且是formLogin 表单类型登录,所有当我们访问接口是需要一个填写身份信息的表单.

当我们打上断点后启动项目时就进入了这个方法如图所示:

6.png

如何弃用默认配置?

如果在项目中添加了security的依赖,那么自然就走了默认配置了所有的资源都会被拦截,那该如何配置才不会走默认配置呢?

在SpringBootWebSecurityConfiguration 类中的注释里面提到:

7.png

大致意思是说:如果用户自定了WebSecurityConfigureAdapter 或者 Security FillterChanin 的bean,这里的代码将退出(不起作用)

新建自定义配置类SecurityConfig,并继承WebSecurityConfigurerAdapter和在类上添加@Configuration注解:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {}
}

此时启动项目:

8.png

新增了自定义配置类之后,启动项目时就不进入断点了,之前的随机密码也没有再打印出来了。说明但我们自定义配置类去继承 WebSecurityConfigurerAdapter类时,请求的访问拦截不再走默认的自动配置逻辑,而是把权限访问的的控制权交予用户自己。

此时访问所定义的接口则无需登录验证了,因为我们只是继承了WebSecurityConfigurerAdapter接口并没有重写其拦截和认证方法。