1 SpringSecurity
SpringSecurity进行网关安全认证的过程很简单,认证的过程就好比一个员工通过人脸,指纹或工牌等凭证进入公司。这里人脸,指纹,工牌就相当于一个凭证,其中任一凭证经过认证后都可以进入公司。经过以上分析不难猜出SpringSecurity主要包括三个组件:
- Authentication(认证/身份验证)类比 人脸
- AuthenticationProvider(认证提供者)类比 人脸识别应用
- AuthenticationManager(认证管理者)类比 门禁系统(包含多张认证方式)
下面分别介绍各个组件
1.1 SpringSecurity核心组件
1.1.1 Authentication
Authentication直译是“认证”的意思,在Spring Security中,Authentication接口用来表示凭证或者令牌,可以理解为用户的用户名、密码、权限等信息。Authentication的代码如下:
public interface Authentication extends Principal, Serializable {
//权限集合
// 可使用
AuthorityUtils.commaSeparatedStringToAuthorityList("admin, ROLE_ADMIN")
//进行初始化
Collection<? extends GrantedAuthority> getAuthorities();
//用户名和密码认证时,可以理解为密码
Object getCredentials();
// 认证时包含的一些详细信息,可以是一个包含用户信息的POJO实例
Object getDetails();
用户名和密码认证时,可以理解为用户名
Object getPrincipal();
// 是否认证通过,通过为true
boolean isAuthenticated();
// 设置是否认证通过
void setAuthenticated(boolean isAuthenticated) throws IllegalArgum
}
1.1.2 AuthenticationProvider
AuthenticationProvider是一个接口,包含两个函数authenticate和supports,用于完成对凭证进行身份认证操作。
public interface AuthenticationProvider {
//对实参authentication进行身份认证操作
Authentication authenticate(Authentication authentication) throws AuthenticationException;
// 判断是否支持该authentication
boolean supports(Class<?> authentication);
}
1.1.3 AuthenticationManager
认证管理者AuthenticationManager在进行令牌验证时,会对提供者列表进行迭代,找出支持令牌的认证提供者,并交给认证提供者去执行令牌验证。如果该认证提供者的supports方法返回true,就会调用该提供者的authenticate方法。如果验证成功,那么整个认证过程结束;如果不成功,那么继续处理列表中的下一个提供者。只要有一个验证成功,就会认证成功。
1.2 SpringSecurity的请求认证处理流程
(1)定制一个凭证/令牌类。
(2)定制一个认证提供者类和凭证/令牌类进行配套,并完成对自制凭证/令牌实例的验证。
(3)定制一个过滤器类,从请求中获取用户信息组装成定制凭证/令牌,交给认证管理者。
(4)定制一个HTTP的安全认证配置类(AbstractHttpConfigurer子类),将上一步定制的过滤器加入请求的过滤处理责任链。
(5)定义一个Spring Security安全配置类(WebSecurityConfigurerAdapter子类),对Web容器的HTTP安全认证机制进行配置。
为了演示,这里实现一个非常简单的认证处理流程,具体的功能如下:当系统资源被访问时,过滤器从HTTP的token请求头获取用户名和密码,然后与系统中的用户信息进行匹配,如果匹配成功,就可以访问系统资源,否则返回403响应码,表示未授权。演示程序的代码位于本书配套源码的demo-provider模块中。
第一步:定制一个凭证/令牌类。
本演示程序直接使用Spring Security提供的UsernamePasswordAuthenticationToken认证类存放用户名+密码信息。
第二步:定制一个认证提供者类和凭证/令牌类进行配套。
直接使用Spring Security提供的提供者实现类DaoAuthenticationProvider,并在项目的Spring Security的启动配置类(本演示程序中为DemoWebSecurityConfig类)中创建该提供者的Bean实例。需要注意的是,该提供者有两个依赖:一个是UserDetailsService类型的用户信息服务实例;另一个是PasswordEncoder类型的加密器实例。在项目的启动配置类中装配DaoAuthenticationProvider提供者容器实例的参考代码如下:
@EnableWebSecuritypublic
class DemoWebSecurityConfig extends WebSecurityConfigurerAdapter {
//注入全局BCryptPasswordEncoder加密器容器实例
@Resource
private PasswordEncoder passwordEncoder;
// 注入数据源服务容器实例
@Resource
private DemoAuthUserService demoUserAuthService;
@Bean("daoAuthenticationProvider")
protected AuthenticationProvider daoAuthenticationProvider() throws Exception {
// 创建一个数据源提供者 DaoAuthenticationProvider
daoProvider = new DaoAuthenticationProvider();
// 设置加密器
daoProvider.setPasswordEncoder(passwordEncoder);
// 设置用户数据源服务
daoProvider.setUserDetailsService(demoUserAuthService);
return daoProvider;
}
}
数据来源:
@Slf4j
@Servicepublic
class DemoAuthUserService implements UserDetailsService {
//模拟的数据源,实际从DB中获取
private Map<String, String> map = new LinkedHashMap<>();
// 初始化模拟的数据源,放入两个用户
map.put("zhangsan","123456");
map.put("lisi","123456");
/**
* 装载系统配置的加密器
*/
@Resource
private PasswordEncoder passwordEncoder;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 实际场景中需要从数据库加载用户
// 这里出于演示的目的,用map模拟真实的数据源
String password = map.get(username);
if (password == null) {
return null;
}
if (null == passwordEncoder) {
passwordEncoder = CustomAppContext.getBean(PasswordEncoder.class);
}
/** *返回一个用户详细实例,包含用户名、加密后的密码、用户权限清单、用户角色 */
UserDetails userDetails = User.builder().username(username).password(passwordEncoder.encode(password))
.authorities(SessionConstants.USER_INFO).roles("USER").build();
return userDetails;
}
}
第三步:定制一个过滤器类。
从请求中获取用户信息组装成定制凭证/令牌,交给认证管理者。
从请求中获取token头部字段,解析之后组装成UserDetails,然后构造一个“用户名+密码”类型的UsernamePasswordAuthenticationToken令牌实例,提交给AuthenticationManager进行验证。
import java.io.IOException;
public class DemoAuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException,
IOException {
try {
Authentication returnToken = null;
boolean succeed = false;
String token = request.getHeader(SessionConstants.AUTHORIZATION_HEAD);
String[] parts = token.split(",");
//方式二:数据源认证演示
UserDetails userDetails = User.builder().username(parts[0]).password(parts[1]).authorities(SessionConstants.USER_INFO).build();
//创建一个用户名+密码的凭证,一般情况下,令牌中的密码需要明文
Authentication userPassToken = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(),
userDetails.getAuthorities());
//进入认证流程
returnToken = this.getAuthenticationManager().authenticate(userPassToken);
succeed = userPassToken.isAuthenticated();
if (succeed) {
//认证成功,设置上下文令牌
SecurityContextHolder.getContext().setAuthentication(returnToken);
//执行后续的操作
filterChain.doFilter(request, response);
return;
}
} catch (Exception e) {
logger.error("认证有误", e);
failed = new AuthenticationServiceException("请求头认证消息格式错误", e);
} ...} ...
}
步骤四:定制一个HTTP的安全认证配置类(AbstractHttpConfigurer子类),将上一步定制的过滤器加入请求的过滤处理责任链。
步骤五:定义一个Spring Security安全配置类(WebSecurityConfigurerAdapter子类),对Web容器的HTTP的安全认证机制进行配置。
这一步有两项工作:一是应用DemoAuthConfigurer配置类;二是构造AuthenticationManagerBuilder认证管理者实例。定制类DemoWebSecurityConfig的代码如下:
@EnableWebSecuritypublic
class DemoWebSecurityConfig extends WebSecurityConfigurerAdapter {
//配置HTTP请求的安全策略,应用DemoAuthConfigurer配置类实例
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable().and()
.apply(new DemoAuthConfigurer<>())
.and()
.sessionManagement().disable();
} //配置认证Builder,由其负责构造AuthenticationManager认证管理者实例
// Builder将构造AuthenticationManager实例,并且作为HTTP请求的共享对象存储
// 在代码中可以通过http.getSharedObject(AuthenticationManager.class)
// 来获取管理者实例
// @Override
// protected void configure(AuthenticationManagerBuilder auth) throws Exception
{ //加入自定义的Provider认证提供者实例
auth.authenticationProvider(demoAuthProvider());
}
//自定义的认证提供者实例
@Bean("demoAuthProvider")
protected DemoAuthProvider demoAuthProvider() {
return new DemoAuthProvider();
}
}