Spring Security ② HTTP配置

深入介绍Spring Security关于HTTP的一些配置。

HTTP相关的安全配置需要重写WebSecurityConfigurerAdapter的方法如下:

@Component
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().and()......
        // ......
    }
}

下面就针对该方法的配置进行一些介绍

登录相关

配置说明
formLogin()使用默认登录页
loginPage(String)修改默认登录页的URL,默认是/login
defaultSuccessUrl(String)登录成功后默认跳转的页面

TIP

如果采用前后端分离模式开发,例如前端使用vue框架,可以关闭默认的登录页。这时Spring提供的登录接口并没有关闭,只需前端按照与默认登录一致的参数提交请求,还是可以使用Spring Security进行登录认证。

权限相关

匹配请求

配置说明
and().authorizeRequests()表示紧接着要配置请求的权限,直到下一个and()
antMatchers(String...)使用ant规则匹配请求
regexMatchers(String...)使用正则表达式匹配请求
mvcMatchers(String...)使用mvc规则匹配请求
anyRequest()匹配剩下的任意请求(当上面几种情况都不匹配时)

匹配请求后需要设置相应的访问权限,权限类型说明如下:

权限类型

配置说明
hasRole(String)允许拥有给定角色的用户访问,角色名不需要加"ROLE_",框架会自动加上
hasAnyRole(String...)拥有给定的任意一个角色即可访问
hasAuthority(String)允许拥有给定权限的用户访问,框架不会自动加前缀
hasAnyAuthority(String...)拥有给定的任意一个权限即可访问
anonymous()允许匿名访问
authenticated()允许登录用户访问,如果用户未登录会默认重定向到登录页
hasIpAddress(String)允许给定IP地址访问
permitAll()无条件允许访问
denyAll()无条件拒绝访问
access(SpEL)给定的表达式结果为true就允许访问,SpEL表达式允许组合上面的多种权限类型

例:

protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .and().authorizeRequests()
            .antMatchers("/h2-console/**").hasRole(Roles.ADMIN)
            .anyRequest().authenticated();
}

TIP

Spring Security除了能在配置类集中配置权限外,还能支持在方法级别设置权限,参考限制方法访问权限

对未登录用户的处理

默认情况下,如果加了.anyRequest().authenticated()配置,用户未登录会默认重定向到登录页。但是,对于前端的ajax请求则会收到302状态码,也无法进行异常处理(例如给出错误提示等)。

这种情况下,你可能会希望当用户未登录时发起ajax请求则返回401状态,非ajax请求则重定向到登录页;或者在前后端完全分离的开发模式下,希望所有未登录请求都返回401,是否重定向交由前端来控制。可以改写配置如下:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()......
        .anyRequest().authenticated();

    http.exceptionHandling()
          .authenticationEntryPoint(authenticationEntryPoint());
}

private AuthenticationEntryPoint authenticationEntryPoint() {
    // 对于未登录的请求,全部返回HTTP 401状态
    return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}

需要注意,这会导致默认登录页失效! 就需要自定义登录页。
因为默认登录页是在web filter中自动生成的,在DefaultLoginPageConfigurer类中有这样一段代码:

if (loginPageGeneratingFilter.isEnabled() && authenticationEntryPoint == null) {
    loginPageGeneratingFilter = postProcess(loginPageGeneratingFilter);
    http.addFilter(loginPageGeneratingFilter);
    http.addFilter(this.logoutPageGeneratingFilter);
}

因此当配置了authenticationEntryPoint时,自动生成登录页的filter就失效了。 注意只是默认登录页失效,但/login和/logout这两个接口还是可以使用。

自定义登录页

默认情况下,在登录页点击登录会发送'/login'请求到后台进行校验,登陆失败会重定向回到默认的登录页,登录成功则重定向到defaultSuccessUrl(url)所配置的url。

可以在Spring Security后台不变的情况下自定义登录页,只要登录表单满足以下条件:

  1. 登录接口使用POST方式提交
  2. Content-Type必须是x-www-form-urlencoded,因此数据也只能以表单方式来发送,不能是json
  3. 默认的字段名称是:用户名 username,密码 password

定制登录成功、失败的处理

在完全前后端分离的模式中,登录失败时前端希望收到json数据返回,而不是一个重定向,这时就需要指定.failureHandler();同样,如果希望定制登录成功时返回json,就指定.successHandler():

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
        .successHandler(successHandler())
        .failureHandler(failureHandler())
        ...;
}

private AuthenticationFailureHandler failureHandler() {
    // 处理登录失败的情况
    // 重写 AuthenticationFailureHandler 接口
    return (request, response, exception) -> {
        response.setStatus(HttpStatus.OK.value());
        response.setContentType("application/json");
        response.getWriter().write("<json格式的字符串>");
    };
}

private AuthenticationSuccessHandler successHandler() {
    // 处理登录成功的情况
    // 重写 AuthenticationSuccessHandler 接口
    return (request, response, authentication) -> {
        response.setStatus(HttpStatus.OK.value());
        response.setContentType("application/json");
        response.getWriter().write("<json格式的字符串>");
    };
}

总结

如果采用完全前后端分离(使用vue/react等框架开发前端)的模式,就需要同时定制对未登录用户的处理、自定义登录页、定制登录成功、失败的处理

另一种选项是,使用Thymeleaf或FreeMarker等前端模板来开发(其本质是数据在后台组装成页面后返回给浏览器),那就不需要进行以上定制。

定制HTTP响应头部

and().headers()用于定制HTTP头部,框架提供了一些默认的头部设置,例如Content-Type,Cache-Control等,可以直接查看源码,不详述。

强制使用HTTPS

protected void configure(HttpSecurity http) throws Exception {
    http.formLogin()
            .and().authorizeRequests()
            .antMatchers("/h2-console/**").hasRole(Roles.ADMIN)
            .anyRequest().authenticated()
            .and().requiresChannel()
            .antMatchers("/h2-console/**").requiresSecure();
}

requiresChannel()声明接下来的配置要使用通道,requiresSecure()要求匹配的请求强制使用HTTPS,requiresInsecure()则使用HTTP。

使用HTTPS需要在服务器上安装证书并开通HTTPS端口。

Last Updated 2024/11/24 20:50:53