核心配置解读
配置介绍
回顾篇二中的 Security 安全核心配置
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.and()
.logout()
.permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
当配置了上述的javaconfig之后,我们的应用便具备了如下的功能:
-
除了“/”,”/home”(首页),”/login”(登录),”/logout”(注销),之外,其他路径都需要认证。
-
指定“/login”该路径为登录页面,当未认证的用户尝试访问任何受保护的资源时,都会跳转到“/login”。
-
默认指定“/logout”为注销页面
-
配置一个内存中的用户认证器,使用admin/admin作为用户名和密码,具有USER角色
-
防止CSRF攻击
-
Session Fixation protection
-
Security Header(添加一系列和Header相关的控制)
- HTTP Strict Transport Security for secure requests
- 集成X-Content-Type-Options
- 缓存控制
- 集成X-XSS-Protection.aspx)
- X-Frame-Options integration to help prevent Clickjacking(iframe被默认禁止使用)
-
为Servlet API集成了如下的几个方法
- HttpServletRequest#getRemoteUser())
- HttpServletRequest.html#getUserPrincipal())
- HttpServletRequest.html#isUserInRole(java.lang.String))
- HttpServletRequest.html#login(java.lang.String, java.lang.String))
- HttpServletRequest.html#logout())
一个 Restful 配置
动态从数据库加载权限,这边因为 Spring Security
的机制是在模块启动的时候进行加载的,如果想要动态 reload
权限,调查来看Spring 并没有提供相关的接口,需要动态 reload Spring
相关的 bean
是一种比较危险暴力的做法,需要多加注意。
@Override
protected void configure(HttpSecurity http) throws Exception {
List<String> permitAllEndpointList = Arrays.asList(AUTHENTICATION_URL,"/api/*/webjars/**","/api/*/swagger**","/api/*/swagger-resources/**","/api/*/v2/api-docs");
//load Authorities from DB
ResponseData<List<Authority>> authorityRules = authorityService.getAuthority();
try {
for (AuthorityModel rule : authorityRules.getData()) {
//这里因为 Restful API url 有相同的情况,因此需要URL和方法名组合来区分,注意一种特殊情况,
//因为是使用Ant语法来匹配URL,如果出现请求方法相同且URL是匹配子集关系时,要把最具体的URL放在
//前面,比如同是 GET 方法,Api1:/api/user/paged 和 Api2:/api/user/{id},在Ant语法里面
//Api2是能匹配Api1的,如果用户有Api2的权限而没有Api1的权限,在如下初始化权限时Api2初始化的
//顺序在Api1的前面,就会导致用户即使没有Api1的权限也能访问,因此要确保数据库加载的时候Api1在Api2
//前面
if (HttpMethod.POST.name().equals(rule.getMethod().name())) {
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, rule.getPattern()).hasAuthority(rule.getSystemName());
} else if (HttpMethod.GET.name().equals(rule.getMethod().name())) {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, rule.getPattern()).hasAuthority(rule.getSystemName());
} else if (HttpMethod.PUT.name().equals(rule.getMethod().name())) {
http
.authorizeRequests()
.antMatchers(HttpMethod.PUT, rule.getPattern()).hasAuthority(rule.getSystemName());
} else if (HttpMethod.DELETE.name().equals(rule.getMethod().name())) {
http
.authorizeRequests()
.antMatchers(HttpMethod.DELETE, rule.getPattern()).hasAuthority(rule.getSystemName());
}
}
} catch (Exception e) {
log.error("", e);
}
http
.csrf().disable() // We don't need CSRF for JWT based authentication
.exceptionHandling().authenticationEntryPoint(this.authenticationEntryPoint)
//自定义没有权限时的处理,一般根据业务封装自定义的返回结果
.accessDeniedHandler(ajaxAccessDeniedHandler)
.and()
//Restful API 完全无状态,使用JWT token
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//定义哪些API不需要认证,比如获取 Token 的接口
.authorizeRequests()
.antMatchers(permitAllEndpointList.toArray(new String[permitAllEndpointList.size()])).permitAll()
.and()
// Protected API End-points
.authorizeRequests()
.antMatchers(API_ROOT_URL, API_ATTACHMENT_URL).authenticated()
.and()
//处理跨域过滤器
.addFilterBefore(new CustomCorsFilter(), UsernamePasswordAuthenticationFilter.class)
//处理登录获取token的过滤器
.addFilterBefore(buildAjaxLoginProcessingFilter(AUTHENTICATION_URL), UsernamePasswordAuthenticationFilter.class)
//验证用户token,以及用户是否有接口访问权限的过滤器
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(permitAllEndpointList, Arrays.asList(API_ROOT_URL,API_ATTACHMENT_URL)), UsernamePasswordAuthenticationFilter.class);
}
具体解释见代码上的注释
@EnableWebSecurity
我们自己定义的配置类 WebSecurityConfig
加上了 @EnableWebSecurity
注解,同时继承了 WebSecurityConfigurerAdapter
。你可能会在想谁的作用大一点,毫无疑问 @EnableWebSecurity
起到决定性的配置作用,它其实是个组合注解。
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,// <2>
SpringWebMvcImportSelector.class })// <1>
@EnableGlobalAuthentication // <3>
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
- <1>
@Import
是springboot
提供的用于引入外部的配置的注解,可以理解为:@EnableWebSecurity
注解激活了@Import
注解中包含的配置类。 - <2>
WebSecurityConfiguration
顾名思义,是用来配置web
安全的,下面的小节会详细介绍。 - <3>
@EnableGlobalAuthentication
注解的源码如下:
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
注意点同样在 @Import
之中,它实际上激活了 AuthenticationConfiguration
这样的一个配置类,用来配置认证相关的核心类。
也就是说:@EnableWebSecurity
完成的工作便是加载了WebSecurityConfiguration
,AuthenticationConfiguration
这两个核心配置类,也就此将 spring security
的职责划分为了配置安全信息,配置认证信息两部分。
WebSecurityConfiguration
在这个配置类中,有一个非常重要的Bean被注册了。
@Configuration
public class WebSecurityConfiguration {
//DEFAULT_FILTER_NAME = "springSecurityFilterChain"
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
...
}
}
在未使用springboot
之前,大多数人都应该对springSecurityFilterChain
这个名词不会陌生,他是spring security
的核心过滤器,是整个认证的入口。在曾经的XML
配置中,想要启用spring security
,需要在web.xml
中进行如下配置:
<!-- Spring Security -->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
而在springboot
集成之后,这样的XML
被java
配置取代。WebSecurityConfiguration
中完成了声明springSecurityFilterChain
的作用,并且最终交给DelegatingFilterProxy
这个代理类,负责拦截请求(注意DelegatingFilterProxy
这个类不是spring security
包中的,而是存在于web
包中,spring
使用了代理模式来实现安全过滤的解耦)。
AuthenticationConfiguration
@Configuration
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(
ObjectPostProcessor<Object> objectPostProcessor) {
return new AuthenticationManagerBuilder(objectPostProcessor);
}
public AuthenticationManager getAuthenticationManager() throws Exception {
...
}
}
AuthenticationConfiguration
的主要任务,便是负责生成全局的身份认证管理者 AuthenticationManager
。还记得在《初识 Spring Security 篇一》中,介绍了 Spring Security
的认证体系,AuthenticationManager
便是最核心的身份认证管理器。
WebSecurityConfigurerAdapter
适配器模式在 spring
中被广泛的使用,在配置中使用 Adapter
的好处便是,我们可以选择性的配置想要修改的那一部分配置,而不用覆盖其他不相关的配置。WebSecurityConfigurerAdapter
中我们可以选择自己想要修改的内容,来进行重写,而其提供了三个 configure
重载方法,是我们主要关心的:
/**WebSecurityConfigurerAdapter**/
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
...
/**
* Override this method to configure {@link WebSecurity}. For example, if you wish to
* ignore certain requests.
*/
public void configure(WebSecurity web) throws Exception {
}
/**
* Override this method to configure the {@link HttpSecurity}. Typically subclasses
* should not invoke this method by calling super as it may override their
* configuration. The default configuration is:
*
* <pre>
* http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
* </pre>
*
* @param http the {@link HttpSecurity} to modify
* @throws Exception if an error occurs
*/
// @formatter:off
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();
}
由参数就可以知道,分别是对 AuthenticationManagerBuilder
,WebSecurity
,HttpSecurity
进行个性化的配置。
HttpSecurity常用配置
@Configuration
@EnableWebSecurity
public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.failureForwardUrl("/login?error")
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index")
.permitAll()
.and()
.httpBasic()
.disable();
}
}
上述是一个使用 Java Configuration
配置 HttpSecurity
的典型配置,其中 http
作为根开始配置,每一个 and()
对应了一个模块的配置(等同于xml配置中的结束标签),并且 and()
返回了 HttpSecurity
本身,于是可以连续进行配置。他们配置的含义也非常容易通过变量本身来推测
- authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息。
- formLogin()对应表单认证相关的配置
- logout()对应了注销相关的配置
- httpBasic()可以配置basic登录
他们分别代表了 http
请求相关的安全配置,这些配置项无一例外的返回了 Configurer
类,而所有的 http
相关配置可以通过查看HttpSecurity
的主要方法得知:
public LogoutConfigurer<HttpSecurity> logout() throws Exception {
return getOrApply(new LogoutConfigurer<HttpSecurity>());
}
......
public CsrfConfigurer<HttpSecurity> csrf() throws Exception {
ApplicationContext context = getContext();
return getOrApply(new CsrfConfigurer<HttpSecurity>(context));
}
需要对 http
协议有一定的了解才能完全掌握所有的配置,不过,springboot
和spring security
的自动配置已经足够使用了。其中每一项 Configurer
(e.g.FormLoginConfigurer,CsrfConfigurer)都是 HttpConfigurer
的细化配置项。
WebSecurityBuilder
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
}
以笔者的经验,这个配置中并不会出现太多的配置信息。
AuthenticationManagerBuilder
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
想要在 WebSecurityConfigurerAdapter
中进行认证相关的配置,可以使用 configure(AuthenticationManagerBuilder auth)
暴露一个 AuthenticationManager
的建造器:AuthenticationManagerBuilder
。如上所示,我们便完成了内存中用户的配置。
细心的朋友会发现,在前面的文章中我们配置内存中的用户时,似乎不是这么配置的,而是:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
如果你的应用只有唯一一个 WebSecurityConfigurerAdapter
,那么他们之间的差距可以被忽略,从方法名可以看出两者的区别:使用@Autowired
注入的 AuthenticationManagerBuilder
是全局的身份认证器,作用域可以跨越多个WebSecurityConfigurerAdapter
,以及影响到基于Method
的安全控制;而 protected configure()
的方式则类似于一个匿名内部类,它的作用域局限于一个WebSecurityConfigurerAdapter
内部。关于这一点的区别,可以参考我曾经提出的issuespring-security#issues4571。官方文档中,也给出了配置多个WebSecurityConfigurerAdapter
的场景以及demo
,将在该系列的后续文章中解读
原文链接:https://www.cnkirito.moe/2017/09/20/spring-security-3/
本文由 zealzhangz 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为:
2019/06/15 16:16