第二章 Java配置SpringSecurity
从 Spring 3.1 以后,Spring框架支持了对 Java 配置的支持,所以这里就不再细述 XML 的配置方式了。感兴趣的可以直接 查看 SpringSecurity 文档,里面详细描述了 Java Configuration 对应的XML Configuration。
Spring Security 提供了许多 Java Configuration 的 Samples 示例,详见 Samples。
一、创建 Spring Security Java 配置
@EnableWebSecurity
public class WebSecurityConfig implements WebMvcConfigurer {
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
);
return manager;
}
}
查看 @EnableWebSecurity
的代码,可以发现其引入了 WebSecurityConfiguration.class
的配置类。
该配置创建了一个 SpringSecurityFilterChain
的过滤器,负责应用程序内部的所有安全(如保护应用的URL、验证提交的用户名密码、重定向至登录表单等等)。
其具有的主要功能如下:
- 默认对应用程序中的每个 URL 进行身份验证
- 生成一个登录表单
- 允许用户使用表单的username、password进行身份认证
- 允许用户使用 logout 注销
- 预防 CSRF 攻击
- 维持会话固定(Session Fixation)
- 集成安全报头(Security Header)
- X-Content-Type-Options
- Cache Control - 缓存控制
- X-XSS-Protection integration - 保护X-XSS一体化
- X-Frame-Options integration to help prevent - 防止点击劫持
- 与 Servlet API 集成
- HttpServletRequset#getRemoteUser()
- HttpServletRequest#getUserPrincipal()
- HttpServletRequest#isUserInRole(String)
- HttpServletRequest#login(String,String)
- HttpServletRequest#logout()
二、HttpSecurity 配置
在开启 Java 配置之后,会默认开启所有路径的验证、表单登录、退出支持等等操作,但是我们并没有提供配置,这是因为 WebSecurityConfigurerAdapter 提供了一个默认的 configure(HttpSecurity http)
配置,如下所示:
/**
* 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(); // 允许使用 Http Basic 方式登录
}
三、Java 配置和表单登录
一般情况下,我们会使用自己定义的登录页面,而不是SpringSecurity默认提供的登录页面。因此,我们可以修改 HttpSecurity 的配置:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") // 指定登录页位置
.permitAll(); // 允许所有用户访问基于表单登录关联的所有url权限
}
四、授权请求
默认情况下, HttpSecurity选择拦截所有的请求,并且只要有身份认证即可访问。我们可以通过 http.authorizeRequest()
方法添加多个子类来指定 url 的自定义需求。
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests() // 按照子类的顺序进行匹配
.antMatcher("/resources/**","signup","/about").permitAll() // 允许指定路径无需认证
.antMatcher("/admin/**").hasRole("ADMIN") // 指定路径需要 ADMIN 权限
.antMatcher("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") // 使用 hasRole 表达式,这样不需要 ROLE 前缀
.anyRequest().authenticated() // 其他所有请求都需要身份验证
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
五、登出处理
在使用 WebSecurityConfigurerAdapter
时,将会自动开启注销功能,访问 /logout
后会执行如下操作使用户注销:
- 使用 Http Session 无效
- 清理任何与 RememberMe 相关的身份验证
- 清理 SecurityContextHolder
- 跳转到 “/login?logout”
或者,也可以自定义注销需求:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatcher("/resources/**","signup","/about").permitAll()
.antMatcher("/admin/**").hasRole("ADMIN")
.antMatcher("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout() // 提供注销支持
.logoutUrl("/my/logout") // 触发注销操作的 url,如果开启了 CSRF保护,则此请求需要是POST
.logoutSuccessUrl("/my/index") // 注销后要重定向的URL,默认为 /login?logout
.logoutSuccessHandler(logoutSuccessHandler) // 自定义登出成功处理器
.invalidateHttpSession(true) // 在注销时是否需要使 HttpSession 无效
.addLogoutHandler(logoutHandler) // 添加登出处理器
.deleteCookies(cookieNamesToClear); // 注销成功时删除指定的 cookie,这是一种显式添加 CookieClearingLogoutHandler 的方式
}
5.1 LogoutHandler 登出处理器
LogoutHandler 实现能够参与注销处理的类,通过调用它们来执行必要的清理,且不会引发异常。各种实现如下:
- PersistentTokenBasedRememberMeServices
- TokenBasedRememberMeServices
- CookieClearingLogoutHandler
- CsrfLogoutHandler
- SecurityContextLogoutHandler
以上处理器实现,大致能做到见名识义。
5.2 LogoutSuccessHandler 登出成功处理器
与 LogoutHandler 类似,但是可能会引发异常。在 LogoutFilter 成功注销之后,LogoutSuccessHandler 会处理例如重定向或转发到适当的目标,具体实现如下:
- SimpleUrlLogoutSuccessHandler
- 注销成功之后返回到指定 url,默认为 /login?logout
- HttpStatusReturningLogoutSuccessHandler
- 允许在注销成功之后返回一个普通的 HTTP 状态码,而不是重定向至 URL
5.3 其他的登出考虑
- 测试登录
- csrf注意事项
- 单点登出
六、WebFlux Security
关于 Spring-WebFlux 具体是什么,可以详见 《Spring-WebFlux》。
@EnableWebFluxSecurity
public class HelloWebfluxSecurityConfig {
@Bean
public SecurityWebFilterChain spirngSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange().authenticated()
.and()
.httpBasic()
.and()
.formLogin();
}
}
七、OAuth 2.0
TODO:@see
八、Authentication 认证
8.1 内存认证(In-Memory Authentication)
直接在内存中配置多个用户,示例如下:
@Bean
public UserDetailsService userDetailsService() throws Exception {
UserBuilder user = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("USER").build());
manager.createUser(users.username("admin").password("password").roles("ADMIN").build());
return manager;
}
8.2 JDBC认证
@Autowired
private DataSource dataSource;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
UserBuilder user = User.withDefaultPasswordEncoder();
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser(users.username("user").password("password").roles("USER"))
.withUser(users.username("admin").password("password").roles("ADMIN"));
}
其他如:LDAP认证、AuthenticationProvider、UseDetailsService不再赘述。
九、多重 HttpSecurity 配置
@EnableWebSecurity
public class MultiHttpSecurityConfig {
@Configuration
@Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatch("/api/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
@Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
}
如果请求的url以 /api/ 开头,将使用 ApiWebSecurityConfigurationAdapter
配置。其他请求使用 FormLoginWebSecurityConfigurerAdapter
配置。