Spring Security的基于内存身份验证主要流程
1.请求被(身份验证过滤器)拦截 2.身份验证职能被委托给(身份验证管理器AuthenticationManager)3.身份验证管理器使用实现身份验证逻辑的(身份验证提供程序DaoAuthenticationProvider)4.身份验证提供程序使用(用户详情服务UserDetailsService)找到用户,并使用(密码编码器NoOpPasswordEncoder)验证密码,5.身份验证的结果返回给过滤器6.已验证实体的详情被储存到安全上下文中。
a.在springboot启动过滤器配置的ApplicationFilterChain中ApplicationFilterConfig[] filters,filters[4]值为springSecurityFilterChain,将其委托给FilterChainProxy类
b.FilterChainProxy类中List<SecurityFilterChain> filterChains,filterChains[0]值为DefaultSecurityFilterChain。DefaultSecurityFilterChain类中List<Filter> filters值为12个主要的过滤器,其中包含BasicAuthenticationFilter过滤器
c.BasicAuthenticationFilter类中有重要的身份认证管理器AuthenticationManager authenticationManager值为ProviderManager类
d.ProviderManager类有两个重要的属性List<AuthenticationProvider> providers 值为AnonymousAuthenticationProvider,AuthenticationManager parent值为ProviderManager类。在parent中的parent为null,providers有一个DaoAuthenticationProvider。
e.DaoAuthenticationProvider类中有两个重要属性PasswordEncoder passwordEncoder=NoOpPasswordEncoder
,UserDetailsService userDetailsService=InMemoryUserDetailsManager,调用Authentication authenticate(Authentication authentication)
f.DaoAuthenticationProvider.authenticate(authentication)方法:将前端传来的用户名,密码初始化为Authentication的实例(authenticated = false)UsernamePasswordAuthenticationToken对象作为入参,返回(authenticated = false)的对象。首先在retrieveUser()中this.InMemoryUserDetailsManager.loadUserByUsername(name)返回内存中的该用户详情,在additionalAuthenticationChecks()方法this.passwordEncoder.matches() 验证密码。
//1. BasicAuthenticationFilter.doFilterInternal()
String[] tokens = this.extractAndDecodeHeader(...)
String username = tokens[0];
UsernamePasswordAuthenticationToken authRequest = new
UsernamePasswordAuthenticationToken(username, tokens[1]);
//2.BasicAuthenticationFilter.authenticationManager=ProviderManager.class
// ProviderManager类
// 属性List<AuthenticationProvider> providers;
// 值providers[0]=AnonymousAuthenticationProvider
// 属性private AuthenticationManager parent;
// 值parent=ProviderManager类
// 属性List<AuthenticationProvider> providers;
// 值providers为null
// 属性private AuthenticationManager parent;
// 值parent=DaoAuthenticationProvider
Authentication authResult = this.authenticationManager.authenticate(authRequest)->{
//3.调用ProviderManager.providers循环调用过程中
// result不为null,则做处理后返回。
result = providers.authenticate(authentication)—>{
// 接口AuthenticationProvide.authenticate(Authentication var1)|supports()
// 实现类DaoAuthenticationProvider
// 实现类AnonymousAuthenticationProvider
// 属性String key
// 只有authentication为AnonymousAuthenticationToken及子类,才能返回ture
// AnonymousAuthenticationToken.class.isAssignableFrom(authentication);
if (!this.supports(authentication.getClass())) {
return null;
// 如果传入的authentication.getKeyHash()与this.key.hashCode()值不相同,则
// 抛出异常BadCredentialsException
} else if (this.key.hashCode() !=
((AnonymousAuthenticationToken)authentication).getKeyHash()) {
throw new BadCredentialsException();
// 相同则返回对象
} else {
return authentication;
}
};
// DaoAuthenticationProvider类
// 属性UserDetailsChecker preAuthenticationChecks
// 属性UserDetailsChecker postAuthenticationChecks
// 接口UserDetailsChecker.check(UserDetails var1)
// 实现类DefaultPreAuthenticationChecks|DefaultPostAuthenticationChecks
// 属性PasswordEncoder passwordEncoder;
// 属性UserDetailsService userDetailsService;
// 接口UserDetailsService.loadUserByUsername()
// 实现类InMemoryUserDetailsManager 内存储存用户详情
// 实现类JdbcUserDetailsManager 数据库储存用户详情
// 两个实现类有公有接口UserDetailsManager.createUser()|updateUser|deleteUser()
// | changePassword() |userExists()
// result为null且parent不为null,则
// 调用ProviderManager.parent.authenticate
// 当result不为null时,返回。结果为null,抛出异常
result = this.parent.authenticate(authentication)—>{
String username = authentication.getPrincipal();
// 根据username,从缓存中取出user
boolean cacheWasUsed = true;
// 接口UserCache.getUserFromCache()|putUserInCache()|removeUserFromCache()
// 实现类SpringCacheBasedUserCache
UserDetails user = this.userCache.getUserFromCache(username);
// 如果缓存为null
if (user == null) {
cacheWasUsed = false;
try {
// 从内存或数据库中,通过username找到user
user = this.retrieveUser(username,
(UsernamePasswordAuthenticationToken)authentication)->{
// userNotFoundEncodedPassword为null时,则
// 调用this.passwordEncoder.encode("userNotFoundPassword");
prepareTimingAttackProtection();
// 从JdbcUserDetailsManager或InMemoryUserDetailsManager
// 检索User
UserDetails loadedUser = this.getUserDetailsService()
.loadUserByUsername(username)—>{
// InMemoryUserDetailsManager 的实现
UserDetails user =
(UserDetails)this.users.get(username.toLowerCase());
if (user == null) {
throw new UsernameNotFoundException(username);
} else {
return new User(user.getUsername(), user.getPassword(),
user.isEnabled(), user.isAccountNonExpired(),
user.isCredentialsNonExpired(),
user.isAccountNonLocked(), user.getAuthorities());
}
// JdbcUserDetailsManager 的实现
...
};
if (loadedUser == null) {
throw new InternalAuthenticationServiceException();
} else {
return loadedUser;
}
};
}catch(UsernameNotFoundException var6){
if (this.hideUserNotFoundExceptions) {
// 隐藏UsernameNotFoundException异常,抛出BadCredentialsException
throw new BadCredentialsException(...));
}
//抛出UsernameNotFoundException异常
throw var6;
}
}
//缓存不为null
try{
// 检查账户是否被locked|disabled|expired
this.preAuthenticationChecks.check(user);
// 密码验证
this.additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken)authentication)->{
// 得到输入的密码
String presentedPassword = authentication.getCredentials().toString();
// 利用passwordEncoder验证
if (!this.passwordEncoder.matches(presentedPassword,
userDetails.getPassword())) {
throw new BadCredentialsException());
}
};
}catch(AuthenticationException var7){
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
// 出现异常时,检索数据库
user = this.retrieveUser(username,
(UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken)authentication);
}
// 密码是否过期
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
// 从内存或数据库中查出来,放入缓存
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
this.createSuccessAuthentication(principalToReturn, authentication, user);
};
};
UserDetailsService 组件
//根据用户名返回用户详情
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
public class User implements UserDetails {
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
//基于内存实现UserDetailsService
public class InMemoryUserDetailsManager{
//储存用户数据的map
private final Map<String, MutableUserDetails> users = new HashMap();
//是一个认证管理器
private AuthenticationManager authenticationManager;
//创建用户,删除用户,更新用户等操作
//重要实现UserDetails loadUserByUsername(String var1)方法
}
身份认证管理器
//认证管理器的认证接口
public interface AuthenticationManager {
Authentication authenticate(Authentication var1) throws AuthenticationException;
}
//实现AuthenticationManager接口,提供认证功能
public class ProviderManager{
private List<AuthenticationProvider> providers;
//parent值为ProviderManager实例,其属性parent为null,
//providers[0]=DaoAuthenticationProvider实例
private AuthenticationManager parent;
public Authentication authenticate(Authentication authentication){
//1.先循环providers数组,调用authenticate(authentication)
Iterator var6 = this.getProviders().iterator();
while(var6.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
result = provider.authenticate(authentication);
}
//2.调用authenticate(authentication);
this.parent.authenticate(authentication);
}
}
AuthenticationProvider
//身份认证提供接口
public interface AuthenticationProvider {
Authentication authenticate(Authentication var1) throws AuthenticationException;
boolean supports(Class<?> var1);
}
//一个重要的AuthenticationProvider接口实现
public class DaoAuthenticationProvider{
private PasswordEncoder passwordEncoder;
private UserDetailsService userDetailsService;
//返回认证过的authentication
Authentication authenticate(Authentication authentication);
}
//认证体接口
public interface Authentication {
//接口GrantedAuthority.getAuthority()
// 实现类SimpleGrantedAuthority
// 属性String role
Collection<? extends GrantedAuthority> getAuthorities();
Object getCredentials();
Object getDetails();
Object getPrincipal();
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
//Authentication接口的实现类
public class UsernamePasswordAuthenticationToken{
private final Object principal;
private Object credentials;
private final Collection<GrantedAuthority> authorities;
private Object details;
private boolean authenticated = false;
}
SpringSecurity中使用方式1,在配置类导入UserDetailService/PasswordEncoder的bean。2,继承WebSecurityConfigurerAdapter,configure(AuthenticationManagerBuilder auth)方法auth.userDetailsService().passwordEncoder()。
自定义身份验证逻辑。1,实现AuthenticationProvider接口2,在configure(AuthenticationManagerBuilder auth)中添加AuthenticationProvider接口的bean。
MVC匹配器
mvcMatchers(HttpMethod method,String... patterns) 允许指定要应用限制的HTTP方法和路径
mvcMatchers(String... patterns) 如果需要应用基于路径的授权限制
/a 仅匹配路径/a,对于mvc,/a与/a/是一样的,对于ant匹配器,则不同。
/a/* 操作符*会替换一个路径名。匹配/a/b或/a/c,而不是/a/b/c
/a/** 操作符**会替换多个路径名。匹配/a或/a/b或/a/b/c
/a/{param} 适用于具有给定路径参数的路径/a。
/a/{param:regex} 只有当参数的值与给定正则表达式匹配时,才应用于适用于具有给定路径参数的路径/a mvcMatchers("/a/{param:^[0-9]*$}")
权限配置方法
hasAuthority/Role(String authority) 只有拥有该权限的用户才能调用端点
hasAnyAuthority/Role(String... authorities) 用户必须拥有至少一个指定的权限才能调用端点
access() 基于SpEL构建授权规则。access("hasAuthority('read') and !hasAuthority('delete')")
1.全局方法安全性:预授权和后授权
在项目中开启@EnableGlobalMethodSecurity(prePostEnabled=true)
预授权 满足条件后,才可以调用方法。
@PreAuthorize("#name == authentication.principal.username") #name 应用方法参数的值
后授权 允许对方法调用,对方法的结果进行验证,如果不满足条件则返回认证失败。
@PostAuthorize("returnObject.roles.contains('reader')")
@PostAuthorize("hasPermission(returnObject,'Role_admin')") 1. 实现PermissionEvaluator接口,重写hasPermission(Authentication authentication,Object target,Object permission) 2.配置Perm滤)
预过滤 框架在调用方法之前过滤参数的值。如果参数违背给定授权规则,则不调用方法。只有符合规则的值会作为参数提供给方法调用
后过滤 框架在调用方法之后过滤返回的值。