建议阅读掘金博客:https://juejin.im/post/5d0b1eb35188252f921b1535#heading-3
1. WebSecurityConfigurerAdapter
在使用WebSecurityConfigurerAdapter前,先了解Spring security config。
Spring security config具有三个模块,一共有3个builder,认证相关的AuthenticationManagerBuilder和web相关的WebSecurity、HttpSecurity。
-
AuthenticationManagerBuilder:用来配置全局的认证相关的信息,其实就是AuthenticationProvider和UserDetailsService,前者是认证服务提供商,后者是用户详情查询服务;
-
WebSecurity: 全局请求忽略规则配置(比如说静态文件,比如说注册页面)、全局HttpFirewall配置、是否debug配置、全局SecurityFilterChain配置、privilegeEvaluator、expressionHandler、securityInterceptor;
-
HttpSecurity:具体的权限控制规则配置。一个这个配置相当于xml配置中的一个标签。各种具体的认证机制的相关配置,OpenIDLoginConfigurer、AnonymousConfigurer、FormLoginConfigurer、HttpBasicConfigurer等。
WebSecurityConfigurerAdapter提供了简洁方式来创建WebSecurityConfigurer,其作为基类,可通过实现该类自定义配置类,主要重写这三个方法:
protected void configure(AuthenticationManagerBuilder auth) throws Exception {}
public void configure(WebSecurity web) throws Exception {}
protected void configure(HttpSecurity httpSecurity) throws Exception {}
而且其自动从SpringFactoriesLoader查找AbstractHttpConfigurer让我们去扩展,想要实现必须创建一个AbstractHttpConfigurer的扩展类,并在classpath路径下创建一个文件META-INF/spring.factories。例如:
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer = sample.MyClassThatExtendsAbstractHttpConfigurer
其源码分析:
//1.init初始化:获取HttpSecurity和配置FilterSecurityInterceptor拦截器到WebSecurity
public void init(final WebSecurity web) throws Exception {
//获取HttpSecurity
final HttpSecurity http = getHttp();
//配置FilterSecurityInterceptor拦截器到WebSecurity
web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
public void run() {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
}
});
}
......
//2.获取HttpSecurity的过程
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// 默认的HttpSecurity的配置
http
//添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用,禁用csrf().disable()
.csrf().and()
//添加WebAsyncManagerIntegrationFilter
.addFilter(new WebAsyncManagerIntegrationFilter())
//允许配置异常处理
.exceptionHandling().and()
//将安全标头添加到响应
.headers().and()
//允许配置会话管理
.sessionManagement().and()
//HttpServletRequest之间的SecurityContextHolder创建securityContext管理
.securityContext().and()
//允许配置请求缓存
.requestCache().and()
//允许配置匿名用户
.anonymous().and()
//HttpServletRequestd的方法和属性注册在SecurityContext中
.servletApi().and()
//使用默认登录页面
.apply(new DefaultLoginPageConfigurer<>()).and()
//提供注销支持
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
configure(http);
return http;
}
...
//3.可重写方法实现自定义的HttpSecurity
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
....
从源码init初始化模块中的“获取HttpSecurity”和“配置FilterSecurityInterceptor拦截器到WebSecurity”中可以看出,想要spring Security如何知道我们要求所有用户都经过身份验证? Spring Security如何知道我们想要支持基于表单的身份验证?只要重写protected void configure(HttpSecurity http) throws Exception方法即可。因此我们需要理解HttpSecurity的方法的作用,如何进行配置。下一节来讨论HttpSecurity。
使用.permitAll()将配置授权,以便在该特定路径上允许所有请求(来自匿名用户和登录用户).
.anonymous()表达式主要是指用户的状态(已登录或未登录).
基本上,直到用户被“认证”为止,它就是“匿名用户”.就像每个人都有“默认角色”一样.
2. HttpSecurity
- HttpSecurity基于Web的安全性允许为特定的http请求进行配置。其有很多方法,列举一些常用的如下表:
方法 | 说明 | 使用案例 |
csrf() | 添加 CSRF 支持,使用WebSecurityConfigurerAdapter时,默认启用 | 禁用:csrf().disable() |
openidLogin() | 用于基于 OpenId 的验证 | openidLogin().permitAll(); |
authorizeRequests() | 开启使用HttpServletRequest请求的访问限制 | authorizeRequests().anyRequest().authenticated() |
formLogin() | 开启表单的身份验证,如果未指定FormLoginConfigurer#loginPage(String),则将生成默认登录页面 | formLogin().loginPage("/authentication/login").failureUrl("/authentication/login?failed") |
oauth2Login() | 开启OAuth 2.0或OpenID Connect 1.0身份验证 | authorizeRequests()..anyRequest().authenticated()..and().oauth2Login() |
rememberMe() | 开启配置“记住我”的验证 | authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin().permitAll().and().rememberMe() |
addFilter() | 添加自定义的filter | addFilter(new CustomFilter()) |
addFilterAt() | 在指定filter相同位置上添加自定义filter | addFilterAt(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) |
addFilterAfter() | 在指定filter位置后添加自定义filter | addFilterAfter(new CustomFilter(), UsernamePasswordAuthenticationFilter.class) |
requestMatchers() | 开启配置HttpSecurity,仅当RequestMatcher相匹配时开启 | requestMatchers().antMatchers("/api/**") |
antMatchers() | 其可以与authorizeRequests()、RequestMatcher匹配,如:requestMatchers().antMatchers("/api/**") | |
logout() | 添加退出登录支持。当使用WebSecurityConfigurerAdapter时,这将自动应用。默认情况是,访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?success” | logout().deleteCookies("remove").invalidateHttpSession(false).logoutUrl("/custom-logout").logoutSuccessUrl("/logout-success"); |
HttpSecurity还有很多方法供我们使用,去配置HttpSecurity。由于太多这边就不一一说明,有兴趣可去研究。
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity
// CRSF禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/profile/**").anonymous()
.antMatchers("/common/download**").anonymous()
.antMatchers("/common/download/resource**").anonymous()
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
3. WebSecurityConfigurerAdapter使用
WebSecurityConfigurerAdapter示例:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyFilterSecurityInterceptor myFilterSecurityInterceptor;
protected void configure(HttpSecurity http) throws Exception {
http
//request 设置
.authorizeRequests() //http.authorizeRequests() 方法中的自定义匹配
.antMatchers("/resources/**", "/signup", "/about").permitAll() // 指定所有用户进行访问指定的url ,permitAll()表示这个不需要验证 静态资源链接 登录页面,登录失败页面
.antMatchers("/admin/**").hasRole("ADMIN") //指定具有特定权限的用户才能访问特定目录,hasRole()方法指定用户权限,且不需前缀 “ROLE_“
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")//
.anyRequest().authenticated() //任何请求没匹配的都需要进行验证
.and() //login设置 自定义登录页面且允许所有用户登录
.formLogin()
.loginPage("/login") //The updated configuration specifies the location of the log in page 指定自定义登录页面
.permitAll(); // 允许所有用户访问登录页面. The formLogin().permitAll() 方法
.and
.logout() //logouts 设置
.logoutUrl("/my/logout") // 指定注销路径
.logoutSuccessUrl("/my/index") //指定成功注销后跳转到指定的页面
.logoutSuccessHandler(logoutSuccessHandler) //指定成功注销后处理类 如果使用了logoutSuccessHandler()的话, logoutSuccessUrl()就会失效
.invalidateHttpSession(true) // httpSession是否有效时间,如果使用了 SecurityContextLogoutHandler,其将被覆盖
.addLogoutHandler(logoutHandler) //在最后增加默认的注销处理类LogoutHandler
.deleteCookies(cookieNamesToClear);//指定注销成功后remove cookies
//增加在FilterSecurityInterceptor前添加自定义的myFilterSecurityInterceptor
http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
}
4. filter顺序
Spring Security filter顺序:
Filter Class | 说明 |
ChannelProcessingFilter | 访问协议控制过滤器,可能会将我们重新定向到另外一种协议,从http转换成https |
SecurityContextPersistenceFilter | 创建SecurityContext安全上下文信息和request结束时清空SecurityContextHolder |
ConcurrentSessionFilter | 并发访问控制过滤器,主要功能:SessionRegistry中获取SessionInformation来判断session是否过期,从而实现并发访问控制。 |
HeaderWriterFilter | 给http response添加一些Header |
CsrfFilter | 跨域过滤器,跨站请求伪造保护Filter |
LogoutFilter | 处理退出登录的Filter |
X509AuthenticationFilter | 添加X509预授权处理机制支持 |
CasAuthenticationFilter | 认证filter,经过这些过滤器后SecurityContextHolder中将包含一个完全组装好的Authentication对象,从而使后续鉴权能正常执行 |
UsernamePasswordAuthenticationFilter | 认证的filter,经过这些过滤器后SecurityContextHolder中将包含一个完全组装好的Authentication对象,从而使后续鉴权能正常执行。表单认证是最常用的一个认证方式。 |
BasicAuthenticationFilter | 认证filter,经过这些过滤器后SecurityContextHolder中将包含一个完全组装好的Authentication对象,从而使后续鉴权能正常执行 |
SecurityContextHolderAwareRequestFilter | 此过滤器对ServletRequest进行了一次包装,使得request具有更加丰富的API |
JaasApiIntegrationFilter | (JAAS)认证方式filter |
RememberMeAuthenticationFilter | 记忆认证处理过滤器,即是如果前面认证过滤器没有对当前的请求进行处理,启用了RememberMe功能,会从cookie中解析出用户,并进行认证处理,之后在SecurityContextHolder中存入一个Authentication对象。 |
AnonymousAuthenticationFilter | 匿名认证处理过滤器,当SecurityContextHolder中认证信息为空,则会创建一个匿名用户存入到SecurityContextHolder中 |
SessionManagementFilter | 会话管理Filter,持久化用户登录信息,可以保存到session中,也可以保存到cookie或者redis中 |
ExceptionTranslationFilter | 异常处理过滤器,主要拦截后续过滤器(FilterSecurityInterceptor)操作中抛出的异常。 |
FilterSecurityInterceptor | 安全拦截过滤器类,获取当前请求url对应的ConfigAttribute,并调用accessDecisionManager进行访问授权决策。 |
spring security的默认filter链:
SecurityContextPersistenceFilter
->HeaderWriterFilter
->LogoutFilter
->UsernamePasswordAuthenticationFilter
->RequestCacheAwareFilter
->SecurityContextHolderAwareRequestFilter
->SessionManagementFilter
->ExceptionTranslationFilter
->FilterSecurityInterceptor
5. 引入security 后跳转默认登陆页解决
springboot 2.x 开发调试禁用spring security
开发调试时,不需要每次都登陆,所以要禁用
5.1 启动类排除自动装配类
这样是去除了security 的功能
@SpringBootApplication(exclude = {
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class,
//引入 'org.springframework.boot:spring-boot-starter-actuator'依赖后,也要排除下方的自动装配类
org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration.class
})
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
5.2.配置文件排除自动装配类
spring:
autoconfigure:
#跳过security自动配置
exclude:
- org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
- org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
application.properties配置文件写法
spring.autoconfigure.exclude[0]=org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
spring.autoconfigure.exclude[1]=org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
5.3 使用配置文件的办法(*)
@PreAuthorize 可以正常使用·
@Configuration
// @EnableGlobalMethodSecurity(prePostEnabled = true) 开启后,Spring Security 的 @PreAuthorize,@PostAuthorize 注解才可以使用。
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(101)
public class SecurityConfig
extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.permitAll()
.and()
.logout()
.permitAll();
}
}