Spring Security 的 Web 应用和指纹登录实践(1)
- UID
- 1066743
|
Spring Security 的 Web 应用和指纹登录实践(1)
前言Java 开发人员在解决 Web 应用安全相关的问题时,通常会采用两个非常流行的安全框架,Shiro 和 Spring Security。Shiro 配置简单,上手快,满足一般应用的安全需求,但是功能相对单一。Spring Security 安全粒度细,与 Spring Framework 无缝集成,满足绝大多数企业级应用的安全需求,但是配置复杂,学习曲线陡峭。
Spring Security 相对 Shiro 功能强大,并且 Spring Framework,Spring Boot,Spring Cloud 对 Spring Security 的支持更加友好 (毕竟是 "亲儿子")。本文将介绍 Spring Security 的架构设计、核心组件,在 Web 应用中的开发方式,最后以一个指纹登录的实例收尾。
Spring Security 核心设计Spring Security 有五个核心组件:SecurityContext、SecurityContextHolder、Authentication、Userdetails 和 AuthenticationManager。下面分别介绍一下各个组件。
SecurityContextSecurityContext 即安全上下文,关联当前用户的安全信息。用户通过 Spring Security 的校验之后,SecurityContext 会存储验证信息,下文提到的 Authentication 对象包含当前用户的身份信息。SecurityContext 的接口签名如清单 1 所示:
清单 1. SecurityContext 的接口签名1
2
3
4
| public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
|
SecurityContext 存储在 SecurityContextHolder 中。
SecurityContextHolderSecurityContextHolder 存储 SecurityContext 对象。SecurityContextHolder 是一个存储代理,有三种存储模式分别是:
- MODE_THREADLOCAL:SecurityContext 存储在线程中。
- MODE_INHERITABLETHREADLOCAL:SecurityContext 存储在线程中,但子线程可以获取到父线程中的 SecurityContext。
- MODE_GLOBAL:SecurityContext 在所有线程中都相同。
SecurityContextHolder 默认使用 MODE_THREADLOCAL 模式,SecurityContext 存储在当前线程中。调用 SecurityContextHolder 时不需要显示的参数传递,在当前线程中可以直接获取到 SecurityContextHolder 对象。但是对于很多 C 端的应用(音乐播放器,游戏等等),用户登录完毕,在软件的整个生命周期中只有当前登录用户,面对这种情况 SecurityContextHolder 更适合采用 MODE_GLOBAL 模式,SecurityContext 相当于存储在应用的进程中,SecurityContext 在所有线程中都相同。
AuthenticationAuthentication 即验证,表明当前用户是谁。什么是验证,比如一组用户名和密码就是验证,当然错误的用户名和密码也是验证,只不过 Spring Security 会校验失败。Authentication 接口签名如清单 2 所示:
清单 2. Authentication 的接口签名1
2
3
4
5
6
7
8
| public interface Authentication extends Principal, Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean isAuthenticated);
}
|
Authentication 是一个接口,实现类都会定义 authorities,credentials,details,principal,authenticated 等字段,具体含义如下:
- getAuthorities: 获取用户权限,一般情况下获取到的是用户的角色信息。
- getCredentials: 获取证明用户认证的信息,通常情况下获取到的是密码等信息。
- getDetails: 获取用户的额外信息,比如 IP 地址、经纬度等。
- getPrincipal: 获取用户身份信息,在未认证的情况下获取到的是用户名,在已认证的情况下获取到的是 UserDetails (暂时理解为,当前应用用户对象的扩展)。
- isAuthenticated: 获取当前 Authentication 是否已认证。
- setAuthenticated: 设置当前 Authentication 是否已认证。
在验证前,principal 填充的是用户名,credentials 填充的是密码,detail 填充的是用户的 IP 或者经纬度之类的信息。通过验证后,Spring Security 对 Authentication 重新注入,principal 填充用户信息(包含用户名、年龄等), authorities 会填充用户的角色信息,authenticated 会被设置为 true。重新注入的 Authentication 会被填充到 SecurityContext 中。
UserDetailsUserDetails 提供 Spring Security 需要的用户核心信息。UserDetails 的接口签名如清单 3 所示:
清单 3. UserDetails 的接口签名1
2
3
4
5
6
7
8
9
| public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
|
UserDetails 用 isAccountNonExpired, isAccountNonLocked,isCredentialsNonExpired,isEnabled 表示用户的状态(与下文中提到的 DisabledException,LockedException,BadCredentialsException 相对应),具体含义如下:
- getAuthorites:获取用户权限,本质上是用户的角色信息。
- getPassword: 获取密码。
- getUserName: 获取用户名。
- isAccountNonExpired: 账户是否过期。
- isAccountNonLocked: 账户是否被锁定。
- isCredentialsNonExpired: 密码是否过期。
- isEnabled: 账户是否可用。
UserDetails 也是一个接口,实现类都会继承当前应用的用户信息类,并实现 UserDetails 的接口。假设应用的用户信息类是 User,自定义的 CustomUserdetails 继承 User 类并实现 UserDetails 接口。
AuthenticationManagerAuthenticationManager 负责校验 Authentication 对象。在 AuthenticationManager 的 authenticate 函数中,开发人员实现对 Authentication 的校验逻辑。如果 authenticate 函数校验通过,正常返回一个重新注入的 Authentication 对象;校验失败,则抛出 AuthenticationException 异常。authenticate 函数签名如清单 4 所示:
清单 4. authenticate 函数签名1
| Authentication authenticate(Authentication authentication)throws AuthenticationException;
|
AuthenticationManager 可以将异常抛出的更加明确:
- 当用户不可用时抛出 DisabledException。
- 当用户被锁定时抛出 LockedException。
- 当用户密码错误时抛出 BadCredentialsException。
重新注入的 Authentication 会包含当前用户的详细信息,并且被填充到 SecurityContext 中,这样 Spring Security 的验证流程就完成了,Spring Security 可以识别到 "你是谁"。
基本校验流程示例下面采用 Spring Security 的核心组件写一个最基本的用户名密码校验示例,如清单 5 所示:
清单 5. Spring Security 核心组件伪代码1
2
3
4
5
6
7
8
| AuthenticationManager amanager = new CustomAuthenticationManager();
Authentication namePwd = new CustomAuthentication(“name”, “password”);
try {
Authentication result = amanager.authenticate(namePwd);
SecurityContextHolder.getContext.setAuthentication(result);
} catch(AuthenticationException e) {
// TODO 验证失败
}
|
Spring Security 的核心组件易于理解,其基本校验流程是: 验证信息传递过来,验证通过,将验证信息存储到 SecurityContext 中;验证失败,做出相应的处理。 |
|
|
|
|
|