首页 > 数据库 > 数据库开发技术 > SpringSecurity 用户名密码登录认证
AbstractUserDetailsAuthenticationProvider提供了一些模板方法利用子类DaoAuthenticationProvider加载用户信息,DaoAuthenticationProvider通过UserDetailsService来加载用户信息。
UsernamePasswordAuthenticationFilter(AbstractAuthenticationFilterConfigurer的子类)拦截指定请求,获取请求中的用户名密码封装为UsernamePasswordAuthenticationToken(Authentication的实现类) 并调用AuthenticationManager的authenticate方法去做认证。如果认证成功,认证成功调用successHandler,认证失败调用failureHandler
FormLoginConfigurer:父类为AbstractAuthenticationFilterConfigurer,表单登录配置,可配置用户名密码参数名称、登录的url、登录成功的处理器AuthenticationSuccessHandler和AuthenticationFailureHandler等等的配置。
FormLoginConfigurer本身也是一个SecurityConfigurer(为了构建SecurityBuilder),此处构建的对象为HttpSecurityBuilder,而HttpSecurity的目的是为了构建一个DefaultSecurityFilterChain
UserDetailsService:加载用户信息
public interface UserDetailsService {
// 根据用户名加载用户信息
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
DaoAuthenticationProvider:做认证,认证加载到的用户信息
@Override
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// SpringSecurity为了防止攻击者根据请求时间差确定到底是用户名还是密码出错,
// 对一个固定的字符串userNotFoundPassword进行编码(第一次请求只编码一次)
// 如果用户名未找到,则会使用原始密码和编码后的字符串userNotFoundPassword做匹配。
// 这样攻击者就不能根据时间差的漏洞获取用户账号信息
// 说明:每次密码匹配耗费的时间比较长一般都是70ms-80ms之间
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
// 根据用户名未找出用户,Spring中不建议直接返回null,返回null就违反了Spring的约定
// Spring Security中建议找不到则抛出用户未找到异常
if (loadedUser == null) {
throw new InternalAuthenticationServiceException();
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
return loadedUser;
}
// 登录成功后的认证
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,UserDetails user) {
// UserDetailsPasswordService:密码升级,可自定义UserDetailsPasswordService的实现
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
AbstractUserDetailsAuthenticationProvider:是DaoAuthenticationProvider的父类,设计来就是为了响应UsernamePasswordAuthenticationToken认证请求。
public abstract class AbstractUserDetailsAuthenticationProvider
implements AuthenticationProvider, InitializingBean, MessageSourceAware {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取authentication中的用户名
String username = determineUsername(authentication);
// 是否使用缓存,默认使用缓存,却存储用户信息。因为此处使用的是NullUserCache(默认是不存储用户信息的)
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}catch (UsernameNotFoundException ex) {
// 是否隐藏用户名异常,目的是为了防止攻击者根据服务端抛出的提示攻击系统
if (!this.hideUserNotFoundExceptions) {
throw ex;
}
// 如果隐藏了就统一抛出错误认证信息的异常
throw new BadCredentialsException();
}
}
try {
// 检查用户状态
this.preAuthenticationChecks.check(user);
// 模板方法,一般是一些凭证类型的校验,比如校验用户名密码是否一致
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException ex) {
if (!cacheWasUsed) {
throw ex;
}
// 如果对象是从缓存中获取的,则有可能因为用户凭证信息或者其他数据未更新导致检查出错,在此处需要重新从数据源中再次加载一次用户数据。
cacheWasUsed = false;
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
this.preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
}
this.postAuthenticationChecks.check(user);
// 用户信息是否需要存放到缓存中
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
// 是否需要强制将principal信息转为String
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// 认证成功,创建一个Authentication
return createSuccessAuthentication(principalToReturn, authentication, user);
}
}
PasswordEncoder:密码加密器,校验用户凭证时会调用
public interface PasswordEncoder {
// 密码加密
String encode(CharSequence rawPassword);
// 原生密码和加密密码是否匹配
boolean matches(CharSequence rawPassword, String encodedPassword);
// 升级加密算法
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
UsernamePasswordAuthenticationFilter:拦截指定请求,获取请求中的用户名密码封装为UsernamePasswordAuthenticationToken(Authentication的实现类) 并调用AuthenticationManager的authenticate方法去做认证。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
// 从请求中获取用户名和密码构建UsernamePasswordAuthenticationToken对象并验证
String username = obtainUsername(request);
String password = obtainPassword(request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 存储一些额外信息
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
AbstractAuthenticationProcessingFilter:为UsernamePasswordAuthenticationFilter的父类,提供了模板方法,由子类去实现具体的认证逻辑。认证成功调用successHandler,认证失败调用failureHandler
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
// 如果不需要认证,则直接放过
if (!requiresAuthentication(request, response)) {
chain.doFilter(request, response);
return;
}
try {
// 由子类实现认证逻辑,返回认证对象
Authentication authenticationResult = attemptAuthentication(request, response);
if (authenticationResult == null) {
return;
}
// session策略,是否为并发登录(一个账号登录多次)等等
this.sessionStrategy.onAuthentication(authenticationResult, request, response);
// Authentication success
if (this.continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 认证成功处理
successfulAuthentication(request, response, chain, authenticationResult);
}
// 发生异常后的处理
catch (InternalAuthenticationServiceException failed) {
unsuccessfulAuthentication(request, response, failed);
}
catch (AuthenticationException ex) {
// Authentication failed
unsuccessfulAuthentication(request, response, ex);
}
}
// 认证成功处理
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authResult);
SecurityContextHolder.setContext(context);
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
// 认证成功处理(可自定义认证成功处理器),比如认证成功给前端返回认证成功json串
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
// 认证失败的处理
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();
this.rememberMeServices.loginFail(request, response);
// 认证失败处理(可自定义)
this.failureHandler.onAuthenticationFailure(request, response, failed);
}
}
FormLoginConfigurer:表单登录配置是一个SecurityConfigurer,构建的对象为HttpSecurityBuilder。而HttpSecurity最终构建的对象就是DefaultSecurityFilterChain。其父类为AbstractAuthenticationFilterConfigurer
public final class FormLoginConfigurer<H extends HttpSecurityBuilder<H>> extends
AbstractAuthenticationFilterConfigurer<H, FormLoginConfigurer<H>, UsernamePasswordAuthenticationFilter> {
public FormLoginConfigurer() {
// 创建了一个UsernamePasswordAuthenticationFilter对象
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
}
AbstractAuthenticationFilterConfigurer:实现了AbstractHttpConfigurer **(主要方法init()和configure())**,认证过滤器的抽象配置
public abstract class AbstractAuthenticationFilterConfigurer<B extends HttpSecurityBuilder<B>, T extends AbstractAuthenticationFilterConfigurer<B, T, F>, F extends AbstractAuthenticationProcessingFilter>
extends AbstractHttpConfigurer<T, B> {
// authFilter就是需要添加到HttpSecurity配置对象中filters属性中
// 这个authFilter的实现类为UsernamePasswordAuthenticationFilter是由子类FormLoginConfigurer通过构造方法传来的
private F authFilter;
public void init(B http);
@Override
public void configure(B http) throws Exception {
PortMapper portMapper = http.getSharedObject(PortMapper.class);
if (portMapper != null) {
this.authenticationEntryPoint.setPortMapper(portMapper);
}
RequestCache requestCache = http.getSharedObject(RequestCache.class);
if (requestCache != null) {
this.defaultSuccessHandler.setRequestCache(requestCache);
}
// 对authFilter的各种配置
this.authFilter.setXxx();
this.authFilter.setXxx();
... ...
// authFilter的扩展配置
F filter = postProcess(this.authFilter);
// 将filter添加到HttpSecurityBuilder中
http.addFilter(filter);
}
}
HttpSecurity 目的就是为了构建一个DefaultSecurityFilterChain。最终添加的filter也会添加到DefaultSecurityFilterChain中
@Override
public HttpSecurity HttpSecurity#addFilter(Filter filter) {
Integer order = this.filterOrders.getOrder(filter.getClass());
this.filters.add(new OrderedFilter(filter, order));
return this;
}
欢迎加入 DevXTalk 的技术交流群,在这里你可以和大佬们一起交流分享技术,行业经验,提升自我。
结识新的业内朋友,扩大自己的圈子,当你想换工作的时候也许就能拿到不错的 offer 。总之期待你的加入...
「看完本文有收获?转发给更多的朋友吧」
「DevXTalk 不止于技术」
原文作者:DevXTalk;如有侵权,请联系管理员删除。