项目是使用springboot搭建
目录
目录
4、举例说明如何将一个Configurer转换为filter
一、Spring security框架简介
1、简介
一个能够为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 spring security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。
2、框架原理
众所周知 想要对对Web资源进行保护,最好的办法莫过于Filter,要想对方法调用进行保护,最好的办法莫过于AOP。
所以springSecurity在我们进行用户认证以及授予权限的时候,通过各种各样的拦截器来控制权限的访问,从而实现安全。
对Web资源的保护,就是靠Filter实现的。如下图:
如下为其主要过滤器
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,缓存获取等)
二、自定义安全配置的加载机制
@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration //@Configuration用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
@EnableWebSecurity //要使Spring Security生效,主要是需要配置@EnableWebSecurity注解; (在SpringBoot中,默认的Spring Security就是生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问)
@EnableGlobalMethodSecurity(prePostEnabled = true) //Spring Security默认是禁用注解的,要想开启注解,需要在继承WebSecurityConfigurerAdapter的类上加@EnableGlobalMethodSecurity注解,并在该类中将AuthenticationManager定义为Bean。
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
/**
* 这是必须写法 配合 @EnableGlobalMethodSecurity 实现spring security 开启注解
*/
@Bean(name = BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 装载BCrypt密码编码器(加密密码)
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* 注意这里用的是@Autowired :(通过byType形式,用来给指定的字段或方法注入所需的外部资源) 以此来构建 AuthenticationManager
*/
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder //AuthenticationManagerBuilder 用于创建一个 AuthenticationManager,让我能够轻松的实现内存验证、LADP验证、基于JDBC的验证、添加UserDetailsService、添加AuthenticationProvider
// 设置UserDetailsService
.userDetailsService(this.userDetailsService)
// 使用BCrypt进行密码的hash
.passwordEncoder(passwordEncoder());
}
/**
* 具体实现请求过滤,校验权限 限定发法
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
// wizard 如果注释掉下面 这一行, 由于ConcurrentSessionFilter 在springSecurity过滤器链条中 位于自定义JwtAuthenticationTokenFilter 之前,所以就相当于废掉了jwt自定义的JwtAuthenticationTokenFilter过滤器了
// .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() //wizard 基于token,所以不需要session 如果用jwt加密 这行应该放开
.authorizeRequests() // 这里的意思是指通过authorizeRequests()方法来开始请求权限配置。
.antMatchers( "/index.do").permitAll()
.antMatchers( "/auth/**").permitAll()
.antMatchers( "/druid/**").permitAll()
.antMatchers( "/aliyun/**").permitAll()
// 允许对于网站静态资源的无授权访问
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/**/*.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/swagger-ui.html").permitAll()
.antMatchers("/swagger-resources").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/*").permitAll()
.antMatchers("/configuration/*").permitAll()
.anyRequest().authenticated(); // 除上面外的所有请求外全部需要鉴权认证
// wizard 在 UsernamePasswordAuthenticationFilter 前添加 JwtAuthenticationTokenFilter 自定义的过滤器(其实他本身还有很多过滤器,这里只能定义 自定义过滤器的位置)
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// 禁用缓存
httpSecurity.headers().cacheControl();
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
}
三、用户登录的验证和授权过程
1、用户一次完整的登录验证和授权,是一个请求经过 层层拦截器从而实现权限控制,整个web端配置为DelegatingFilterProxy(springSecurity的委托过滤其代理类 ),它并不实现真正的过滤,而是所有过滤器链的代理类,真正执行拦截处理的是由spring 容器管理的个个filter bean组成的filterChain.
调用实际的FilterChainProxy 的doFilterInternal()方法 去获取所有的拦截器并进行过滤处理如下是DelegatingFilterProxy的doFilter()方法
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if(delegateToUse == null) {
Object var5 = this.delegateMonitor;
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
if(delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if(wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
}
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
//调用实际的FilterChainProxy 的doFilterInternal()方法 去获取所有的拦截器并进行过滤处理
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
调用实际的FilterChainProxy 的doFilter()方法 去获取所有的拦截器并进行过滤处理。
2、FilterChainProxy类
最终调用FilterChainProxy 的doFilterInternal()方法,获取所有的过滤器实例
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if(clearContext) {
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//doFilter 调用doFilterInternal方法
this.doFilterInternal(request, response, chain);
} finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
} else {
this.doFilterInternal(request, response, chain);
}
}
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
//过去所有的过滤器
List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
if(filters != null && filters.size() != 0) {
FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
} else {
if(logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null?" has no matching filters":" has an empty filter list"));
}
fwRequest.reset();
chain.doFilter(fwRequest, fwResponse);
}
}
private List<Filter> getFilters(HttpServletRequest request) {
//遍历所有的matcher类 如果支持就继续获取
Iterator var2 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if(!var2.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var2.next();
} while(!chain.matches(request));
//后去匹配中的所有过滤器
return chain.getFilters();
}
如上 其实是获取到本次请求的所有filter 并安装指定顺序进行执行doFilter()方法