Spring Security,这是一种基于 Spring AOP 和 Servlet 过滤器的安全框架,应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
想要对对Web资源进行保护,最好的办法莫过于Filter,要想对方法调用进行保护,最好的办法莫过于AOP。所以springSecurity在我们进行用户认证以及授予权限的时候,通过各种各样的拦截器来控制权限的访问,从而实现安全。
如下为其主要过滤器
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CorsFilter
LogoutFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
UsernamePasswordAuthenticationFilter
BasicAuthenticationFilter
SecurityContextHolder:提供对SecurityContext的访问
SecurityContext,:持有Authentication对象和其他可能需要的信息
AuthenticationManager 其中可以包含多个AuthenticationProvider
ProviderManager对象为AuthenticationManager接口的实现类
AuthenticationProvider 主要用来进行认证操作的类 调用其中的authenticate()方法去进行认证操作
Authentication:Spring Security方式的认证主体
GrantedAuthority:对认证主题的应用层面的授权,含当前用户的权限信息,通常使用角色表示
UserDetails:构建Authentication对象必须的信息,可以自定义,可能需要访问DB得到
UserDetailsService:通过username构建UserDetails对象,通过loadUserByUsername根据userName获取UserDetail对象 (可以在这里基于自身业务进行自定义的实现 如通过数据库,xml,缓存获取等)
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> 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
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()方法
(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
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.springframework.security
spring-security-test
test
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)主路径请求不用拦截
(3)权限访问,使用user1登录,访问/roleAuth,因为没有权限会禁止访问
使用admin登录,访问/roleAuth,访问成功
借鉴博客:https://blog.csdn.net/liushangzaibeijing/article/details/81220610