一、简介
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