Spring Security 基本认证
快速入门
我们只需要引入web和spring security依赖即可,如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
提供一个测试接口,代码如下:
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "Hello Spring Security!";
}
}
当用户访问/hello接口时,会自动跳转到登录页面,登录成功后,才能访问/hello接口。
默认的登录用户名是user,密码则是随机生成的UUID字符串,在启动日志中可以看到,如下:
流程分析
如上图所示:
- 客户端(浏览器)发起请求去访问 /hello 接口,这个接口默认是需要认证之后才能访问的。
- 这个请求会走一遍Spring Security中的过滤器链,在最后FilterSecurityIntercepor过滤器中被拦截下来,因为系统发现用户未认证。请求拦截下来之后,接下来会抛出AccessDecniedException异常。
- 抛出的AccessDecniedException异常在ExceptionTranslationFilter过滤器通过调用LoginUrlAuthenticationEntryPoin#commence方法给客户端返回302,要求客户端重定向到/login页面。
- 客户端发送/login请求
- /login请求被DefaultLoginPageGeneratingFilter过滤器拦截下来,并在该过滤器中返回登录页面。所以当用户访问/login接口时会首先看到登录页面。
在整个过程中浏览器发送了两次请求,第一个请求是/hello,服务端收到后,返回302,要求客户端重定向到/login,于是客户端又发送了/login请求。
原理分析
虽然只是引入了一个依赖,但是Spring Boot 背后却默默做了很多事情:
- 开启Spring Security 自动化配置,开启后,会自动创建一个名为springSecurityFilterChain的过滤器,并注入到bean容器中,这个过滤器负责所有的安全管理,包括用户的认证、授权、重定向到登录页等(springSecurityFilterChain 实际上代理了Spring Security 中的过滤器链)。
- 创建一个userDetailsService实例,UserDetailsService 负责提供用户数据,默认的用户数据是基于内存的用户,用户名为user,密码为随机的UUID字符串。
- 给用户生成一个登录页面。
- 开启CSRF攻击防御。
- 开启会话固定攻击防御。
- 集成X-XSS-Protection。
- 集成X-Frame-Options以防止单击劫持。
默认用户生成
Spring Security中定义了UserDetails接口来规范开发者自定义的对象。
UserDetails接口定义如下:
public interface UserDetails extends Serializable {
//返回当前账户所具备的权限
Collection<? extends GrantedAuthority> getAuthorities();
//返回当前账户的密码
String getPassword();
//返回当前账户的用户名
String getUsername();
//返回当前账户是否过期
boolean isAccountNonExpired();
//返回当前账户是否锁定
boolean isAccountNonLocked();
//返回当前账户凭证(如密码)是否过期
boolean isCredentialsNonExpired();
//当前账户是否可用
boolean isEnabled();
}
负责提供用户数据源的接口是UserDetailsService,它只有一个查询用户的方法,如下:
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
loadUserByUsername有一个参数是username,这是用户在认证时传入的用户名,最常见的就是用户在登录表单中输入的用户名(实际开发时还可能存在其他情况,例如使用CAS单点登录时,usernme并非表单输入的用户名,而是CAS Server认证成功之后回调的用户名参数),开发者在这里拿到数据之后,再去数据库中查询用户,最终返回一个UserDetails实例。在实际开发过程中需要我们自定义UserDetailsService的。