《Spring Security》第二章 Java配置SpringSecurity

第二章 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、验证提交的用户名密码、重定向至登录表单等等)。

其具有的主要功能如下:

  1. 默认对应用程序中的每个 URL 进行身份验证
  2. 生成一个登录表单
  3. 允许用户使用表单的username、password进行身份认证
  4. 允许用户使用 logout 注销
  5. 预防 CSRF 攻击
  6. 维持会话固定(Session Fixation)
  7. 集成安全报头(Security Header)
    1. X-Content-Type-Options
    2. Cache Control - 缓存控制
    3. X-XSS-Protection integration - 保护X-XSS一体化
    4. X-Frame-Options integration to help prevent - 防止点击劫持
  8. 与 Servlet API 集成
    1. HttpServletRequset#getRemoteUser()
    2. HttpServletRequest#getUserPrincipal()
    3. HttpServletRequest#isUserInRole(String)
    4. HttpServletRequest#login(String,String)
    5. 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 后会执行如下操作使用户注销:

  1. 使用 Http Session 无效
  2. 清理任何与 RememberMe 相关的身份验证
  3. 清理 SecurityContextHolder
  4. 跳转到 “/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 实现能够参与注销处理的类,通过调用它们来执行必要的清理,且不会引发异常。各种实现如下:

  1. PersistentTokenBasedRememberMeServices
  2. TokenBasedRememberMeServices
  3. CookieClearingLogoutHandler
  4. CsrfLogoutHandler
  5. SecurityContextLogoutHandler

以上处理器实现,大致能做到见名识义。

5.2 LogoutSuccessHandler 登出成功处理器

与 LogoutHandler 类似,但是可能会引发异常。在 LogoutFilter 成功注销之后,LogoutSuccessHandler 会处理例如重定向或转发到适当的目标,具体实现如下:

  1. SimpleUrlLogoutSuccessHandler
    1. 注销成功之后返回到指定 url,默认为 /login?logout
  2. HttpStatusReturningLogoutSuccessHandler
    1. 允许在注销成功之后返回一个普通的 HTTP 状态码,而不是重定向至 URL

5.3 其他的登出考虑

  1. 测试登录
  2. csrf注意事项
  3. 单点登出

六、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 配置。

文章作者: koral
文章链接: http://luokaiii.github.io/2019/07/18/读书笔记/《SpringSecurity》/2.配置/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自