Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于spring的应用程序的实际标准。
Spring Security是一个侧重于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于可以很容易地扩展它以满足定制需求
特点:
- 对身份验证和授权的全面和可扩展支持
- 防止攻击,如会话固定,点击劫持,跨站点请求伪造等
- Servlet API的集成
- 与Spring Web MVC的可选集成
在官网快速创建一个SpringBoot项目并添加Security依赖
下载或者导入本地的idea即可,打开idea打开之后就是一个常规的SpringBoot项目
添加接口:
@RestController
public class HelloController {
@GetMapping("hello")
public String Hello() {
return "hello security";
}
}
启动项目访问接口时:
当我们添加了Security的依赖之后,系统所有资源默认就受到保护,访问时需要登录 。未配置用户和密码时,默认用户名为 user ,默认密码在项目启动时就打印出来如图:
现在只需要使用user 和以上密码 登录,即可访问到我们的所访问的接口。
疑问:为什么我们添加了Security的依赖之后整个项目的资源都被拦截了?
SpringBoot自动配置原理:
我们都知道SpringBoot的优势就是默认提供了许多框架的自动配置,在spring-boot-autoconfigure.jar的META-INF/spring.factories文件中定义了哪些默认自动配置的框架,搜索security相关的配置如下图所示:
我们点击打开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的自动配置中所需要的条件如下图所示:
DefaultAuthenticationEventPublisher所在的包为:
正是在我们添加的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 表单类型登录,所有当我们访问接口是需要一个填写身份信息的表单.
当我们打上断点后启动项目时就进入了这个方法如图所示:
如何弃用默认配置?
如果在项目中添加了security的依赖,那么自然就走了默认配置了所有的资源都会被拦截,那该如何配置才不会走默认配置呢?
在SpringBootWebSecurityConfiguration 类中的注释里面提到:
大致意思是说:如果用户自定了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 {}
}
此时启动项目:
新增了自定义配置类之后,启动项目时就不进入断点了,之前的随机密码也没有再打印出来了。说明但我们自定义配置类去继承 WebSecurityConfigurerAdapter类时,请求的访问拦截不再走默认的自动配置逻辑,而是把权限访问的的控制权交予用户自己。
此时访问所定义的接口则无需登录验证了,因为我们只是继承了WebSecurityConfigurerAdapter接口并没有重写其拦截和认证方法。