完成了基本配置后,我们需要自定义Spring Security的一些进阶配置,才能应用于我们的项目。
和SpringBoot项目中其他插件一样,我们一般也是写一个配置类来存放对Spring Security的配置。
@EnableWebSecurity
我们自己定义的配置类 WebSecurityConfig 加上了 @EnableWebSecurity 注解,同时继承了 WebSecurityConfigurerAdapter。你可能会在想谁的作用大一点,毫无疑问 @EnableWebSecurity 起到决定性的配置作用,它其实是个组合注解。
@Import({ WebSecurityConfiguration.class, // <2>
SpringWebMvcImportSelector.class }) // <1>
@EnableGlobalAuthentication // <3>
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}
@Import
是 springboot 提供的用于引入外部的配置的注解,可以理解为:@EnableWebSecurity
注解激活了 @Import
注解中包含的配置类。
<1> SpringWebMvcImportSelector
的作用是判断当前的环境是否包含 springmvc,因为 spring security 可以在非 spring 环境下使用,为了避免 DispatcherServlet 的重复配置,所以使用了这个注解来区分。
<2> WebSecurityConfiguration
顾名思义,是用来配置 web 安全的,下面的小节会详细介绍。
<3> @EnableGlobalAuthentication
注解的源码如下:
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
注意点同样在 @Import
之中,它实际上激活了 AuthenticationConfiguration
这样的一个配置类,用来配置认证相关的核心类。
也就是说:@EnableWebSecurity
完成的工作便是加载了 WebSecurityConfiguration
,AuthenticationConfiguration
这两个核心配置类,也就此将 spring security 的职责划分为了配置安全信息,配置认证信息两部分。
WebSecurityConfigurerAdapter
适配器模式在 spring 中被广泛的使用,在配置中使用 Adapter 的好处便是,我们可以选择性的配置想要修改的那一部分配置,而不用覆盖其他不相关的配置。WebSecurityConfigurerAdapter 中我们可以选择自己想要修改的内容,来进行重写,而其提供了三个 configure 重载方法,是我们主要关心的:
由参数就可以知道,分别是对 AuthenticationManagerBuilder,WebSecurity,HttpSecurity 进行个性化的配置。
配置SecurityConfig
在项目源代码目录下建立config包,创建 SecurityConfig 配置类,继承 WebSecurityConfigurerAdapter 抽象类,实现 Spring Security 在 Web 场景下的自定义配置。
重写configure(AuthenticationManagerBuilder auth)
想要在 WebSecurityConfigurerAdapter
中进行认证相关的配置,可以使用 configure(AuthenticationManagerBuilder auth)
暴露一个 AuthenticationManager
的建造器:AuthenticationManagerBuilder
如下所示:
// SecurityConfig.java
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.
// <X> 使用内存中的 InMemoryUserDetailsManager
inMemoryAuthentication()
// <Y> 不使用 PasswordEncoder 密码编码器
.passwordEncoder(NoOpPasswordEncoder.getInstance())
// <Z> 配置 admin 用户
.withUser("admin").password("admin").roles("ADMIN")
// <Z> 配置 normal 用户
.and().withUser("normal").password("normal").roles("NORMAL");
}
说明
AuthenticationManagerBuilder#inMemoryAuthentication()
方法,使用内存级别的
InMemoryUserDetailsManager Bean 对象,提供认证的用户信息。
Spring 内置了两种 UserDetailsManager 实现:
- InMemoryUserDetailsManager
- JdbcUserDetailsManager ,基于 JDBC的 JdbcUserDetailsManager 。
实际项目中,我们更多采用调用AuthenticationManagerBuilder#userDetailsService(userDetailsService)
方法,使用自定义实现的 UserDetailsService 实现类,更加灵活、自由地实现认证的用户信息的读取。
AbstractDaoAuthenticationConfigurer#passwordEncoder(passwordEncoder)
方法,设置 PasswordEncoder 密码编码器。在这里,为了方便,我们使用
NoOpPasswordEncoder 。实际上,等于不使用 PasswordEncoder 。
NoOpPasswordEncoder的
encode方法就只是简单地把字符序列转成字符串,也就是说,你输入的密码”123456”存储在数据库里仍然是”123456”,这样如果数据库被攻破的话,密码就直接泄露了,十分不安全。
不过,正因其十分简单,所以在Spring Security 5.0 之前NoOpPasswordEncoder是作为默认的密码编码器而存在的,它可以是你没有主动加密时的一个默认选择。
生产环境下,推荐使用 BCryptPasswordEncoder
。更多关于 PasswordEncoder 的内容,推荐阅读《该如何设计你的 PasswordEncoder?》文章。
重写configure(HttpSecurity http)
然后,我们重写 configure(HttpSecurity http)
方法,主要配置 URL 的权限控制。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("test/demo").permitAll()
// 配置请求地址的权限
.antMatchers("test/admin").hasRole("ADMIN")
.antMatchers("test/normal").hasRole("NORMAL")
.anyRequest().authenticated()
// 设置 Form 表单登陆
.and().formLogin()
//loginPage("/login")
.permitAll()
.and().logout()
//logoutUrl("/logout")
.permitAll();
}
上述是一个使用 Java Configuration 配置 HttpSecurity 的典型配置,其中 http 作为根开始配置,每一个 and()对应了一个模块的配置(等同于 xml 配置中的结束标签),并且 and() 返回了 HttpSecurity 本身,于是可以连续进行配置。(链式编程调用)
他们配置的含义也非常容易通过变量本身来推测,
- authorizeRequests() 配置路径拦截,表明路径访问所对应的权限,角色,认证信息。
- formLogin() 对应表单认证相关的配置
- logout() 对应了注销相关的配置
- httpBasic() 可以配置 basic 登录
- etc
他们分别代表了 http 请求相关的安全配置,这些配置项无一例外的返回了 Configurer 类,而所有的 http 相关配置可以通过查看 HttpSecurity 的主要方法得知:
这里要特别注意:如果配置了loginPage和logoutUrl这两项,就必须提供html文件来构成 登陆页面,否则就会无法登录。所以这里不配置这两项,还是使用系统默认给的登陆页面。
需要对 http 协议有一定的了解才能完全掌握所有的配置,不过,springboot 和 spring security 的自动配置已经足够使用了。其中每一项 Configurer(e.g.FormLoginConfigurer,CsrfConfigurer)都是 HttpConfigurer 的细化配置项。
调用 HttpSecurity.authorizeRequests()
方法,开始配置 URL 的权限控制。
下面,是配置权限控制会使用到的方法:
#String... antPatterns)
方法,配置匹配的 URL 地址,基于 Ant 风格路径表达式 ,可传入多个。- 【常用】
permitAll()
方法,所有用户可访问。 - 【常用】
denyAll()
方法,所有用户不可访问。 - 【常用】
authenticated()
方法,登录用户可访问。 anonymous()
方法,无需登录,即匿名用户可访问。rememberMe()
方法,通过 remember me 登录的用户可访问。fullyAuthenticated()
方法,非 remember me 登录的用户可访问。hasIpAddress(String ipaddressExpression)
方法,来自指定 IP 表达式的用户可访问。- 【常用】
hasRole(String role)
方法, 拥有指定角色的用户可访问。 - 【常用】
hasAnyRole(String... roles)
方法,拥有指定任一角色的用户可访问。 - 【常用】
hasAuthority(String authority)
方法,拥有指定权限(authority
)的用户可访问。 - 【常用】
hasAuthority(String... authorities)
方法,拥有指定任一权限(authority
)的用户可访问。 - 【最牛】
access(String attribute)
方法,当 Spring EL 表达式的执行结果为true
时,可以访问。
@PermitAll
注解,等价于 permitAll()
方法,所有用户可访问。
重要!!!因为在SecurityConfig中,配置了
.anyRequest().authenticated()
,任何请求,访问的用户都需要经过认证。所以这里@PermitAll
注解实际是不生效的。也就是说,Java Config 配置的权限,和注解配置的权限,两者是叠加的。
@PreAuthorize
注解,等价于 access(String attribute)
方法,,当 Spring EL 表达式的执行结果为 true 时,可以访问。
重写configure(WebSecurity web)
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
}
这个方法看起来不是太常用……