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) | 登录成功后默认跳转的页面 |
提示
如果采用前后端分离模式开发,例如前端使用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();
}提示
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后台不变的情况下自定义登录页,只要登录表单满足以下条件:
- 登录接口使用POST方式提交
Content-Type必须是x-www-form-urlencoded,因此数据也只能以表单方式来发送,不能是json- 默认的字段名称是:用户名 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端口。