认证源码分析与自定义后端认证逻辑

简介: 本文深入分析Spring Security认证流程,从UsernamePasswordAuthenticationFilter入手,解析用户登录请求如何通过AuthenticationManager委托给AuthenticationProvider进行认证,最终由UserDetailsService加载用户信息并完成身份验证。重点揭示了自定义认证逻辑的关键——实现UserDetailsService并返回包含权限的UserDetails对象,同时追踪认证成功后SecurityContext的更新机制及“记住我”功能的触发过程,全面梳理了整个认证链路的核心实现。

1.认证流程分析
UsernamePasswordAuthenticationFilter
先看主要负责认证的过滤器UsernamePasswordAuthenticationFilter,有删减,注意注释。
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter
{
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true;

public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
}

public Authentication attemptAuthentication(HttpServletRequest request, 
                                            HttpServletResponse response) throws AuthenticationException {
    //必须为POST请求
    if (this.postOnly && !request.getMethod().equals("POST")) {
        throw new AuthenticationServiceException("Authentication method not supported: " +
                                                 request.getMethod());
    } else {

        String username = this.obtainUsername(request);
        String password = this.obtainPassword(request);

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        //将填写的用户名和密码封装到了UsernamePasswordAuthenticationToken中
        UsernamePasswordAuthenticationToken authRequest = new
        UsernamePasswordAuthenticationToken(username, password);

        this.setDetails(request, authRequest);
        //调用AuthenticationManager对象实现认证
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

}
AuthenticationManager
由上面源码得知,真正认证操作在AuthenticationManager里面!
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {

private static final Log logger = LogFactory.getLog(ProviderManager.class);
private AuthenticationEventPublisher eventPublisher;
private List<AuthenticationProvider> providers;
protected MessageSourceAccessor messages;
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication;

//注意AuthenticationProvider这个对象,SpringSecurity针对每一种认证,什么qq登录啊,
//用户名密码登陆啊,微信登录啊都封装了一个AuthenticationProvider对象。
public ProviderManager(List<AuthenticationProvider> providers) {
    this(providers, (AuthenticationManager)null);
}

public Authentication authenticate(Authentication authentication) throws
AuthenticationException {

    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    AuthenticationException parentException = null;
    Authentication result = null;
    Authentication parentResult = null;
    boolean debug = logger.isDebugEnabled();
    Iterator var8 = this.getProviders().iterator();

    //循环所有AuthenticationProvider,匹配当前认证类型。
    while(var8.hasNext()) {
        AuthenticationProvider provider = (AuthenticationProvider)var8.next();
        if (provider.supports(toTest)) {
            if (debug) {
                logger.debug("Authentication attempt using " +
                             provider.getClass().getName());
            }
            try {
                //找到了对应认证类型就继续调用AuthenticationProvider对象完成认证业务。
                result = provider.authenticate(authentication);
                if (result != null) {
                    this.copyDetails(authentication, result);
                    break;
                }
            } catch (AccountStatusException var13) {
                this.prepareException(var13, authentication);
                throw var13;
            } catch (InternalAuthenticationServiceException var14) {
                this.prepareException(var14, authentication);
                throw var14;
            } catch (AuthenticationException var15) {
                lastException = var15;
            }
        }
    }

    if (result == null && this.parent != null) {
        try {
            result = parentResult = this.parent.authenticate(authentication);
        } catch (ProviderNotFoundException var11) {
        } catch (AuthenticationException var12) {
            parentException = var12;
            lastException = var12;
        }
    }

    if (result != null) {
        if (this.eraseCredentialsAfterAuthentication && result instanceof
            CredentialsContainer) {
            ((CredentialsContainer)result).eraseCredentials();
        }
        if (parentResult == null) {
            this.eventPublisher.publishAuthenticationSuccess(result);
        }
        return result;
    } else {
        if (lastException == null) {
            lastException = new
            ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new
                                                               Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
        }
        if (parentException == null) {
            this.prepareException((AuthenticationException)lastException, authentication);
        }
        throw lastException;
    }
}

}
AbstractUserDetailsAuthenticationProvider
咱们继续再找到AuthenticationProvider的实现类AbstractUserDetailsAuthenticationProvider:
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
this.prepareTimingAttackProtection();
try {
//重点来了!主要就在这里了!
//可别忘了,咱们为什么要翻源码,是想用自己数据库中的数据实现认证操作啊!
//UserDetails就是SpringSecurity自己的用户对象。
//this.getUserDetailsService()其实就是得到UserDetailsService的一个实现类
//loadUserByUsername里面就是真正的认证逻辑
//也就是说我们可以直接编写一个UserDetailsService的实现类,告诉SpringSecurity就可以了!
//loadUserByUsername方法中只需要返回一个UserDetails对象即可
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
//若返回null,就抛出异常,认证失败。
if (loadedUser == null) {
throw new InternalAuthenticationServiceException("UserDetailsService returned
null, which is an interface contract violation");
} else {
//若有得到了UserDetails对象,返回即可。
return loadedUser;
}
} catch (UsernameNotFoundException var4) {
this.mitigateAgainstTimingAttack(authentication);
throw var4;
} catch (InternalAuthenticationServiceException var5) {
throw var5;
} catch (Exception var6) {
throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
}
}
}
AbstractUserDetailsAuthenticationProvider
按理说到此已经知道自定义认证方法的怎么写了,但咱们把返回的流程也大概走一遍,上面不是说到返回了一个 UserDetails对象对象吗?
跟着它就又回到AbstractUserDetailsAuthenticationProvider对象中authenticate方法最后一行了。
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {

public Authentication authenticate(Authentication authentication) throws
AuthenticationException {
    //最后一行返回值,调用了createSuccessAuthentication方法,此方法就在下面!
    return this.createSuccessAuthentication(principalToReturn, authentication, user);
}

//咿!?怎么又封装了一次UsernamePasswordAuthenticationToken,开局不是已经封装过了吗?
protected Authentication createSuccessAuthentication(Object principal, 
                                                     Authentication authentication, 
                                                     UserDetails user) {
    //那就从构造方法点进去看看,这才干啥了。
    UsernamePasswordAuthenticationToken result = new
    UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(),
                                        this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
    result.setDetails(authentication.getDetails());
    return result;
}

}
UsernamePasswordAuthenticationToken
来到UsernamePasswordAuthenticationToken对象发现里面有两个构造方法
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {

private static final long serialVersionUID = 510L;
private final Object principal;
private Object credentials;

//认证成功前,调用的是这个带有两个参数的。
public UsernamePasswordAuthenticationToken(Object principal, 
                                           Object credentials) {
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    this.setAuthenticated(false);
}

//认证成功后,调用的是这个带有三个参数的。
public UsernamePasswordAuthenticationToken(Object principal, 
                                           Object credentials,
                                           Collection<? extends GrantedAuthority> authorities) {
    //看看父类干了什么!
    super(authorities);
    this.principal = principal;
    this.credentials = credentials;
    super.setAuthenticated(true);
}

}
AbstractAuthenticationToken
再点进去super(authorities)看看:
public abstract class AbstractAuthenticationToken implements Authentication,
CredentialsContainer {

private final Collection<GrantedAuthority> authorities;
private Object details;
private boolean authenticated = false;

public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {

    //这时两个参数那个分支!
    if (authorities == null) {
        this.authorities = AuthorityUtils.NO_AUTHORITIES;
    } else {
        //三个参数的,看这里!
        Iterator var2 = authorities.iterator();
        //原来是多个了添加权限信息的步骤
        GrantedAuthority a;
        do {
            if (!var2.hasNext()) {
                ArrayList<GrantedAuthority> temp = new ArrayList(authorities.size());
                temp.addAll(authorities);
                this.authorities = Collections.unmodifiableList(temp);
                return;
            }
            a = (GrantedAuthority)var2.next();
        } while(a != null);
        //若没有权限信息,是会抛出异常的!
        throw new IllegalArgumentException("Authorities collection cannot contain any null
        elements");
    }
}

}
由此,咱们需要牢记自定义认证业务逻辑返回的UserDetails对象中一定要放置权限信息! 现在可以结束源码分析了?先不要着急! 咱们回到最初的地方UsernamePasswordAuthenticationFilter,你看好看了,这可是个过滤器,咱们分析这么 久,都没提到doFilter方法,你不觉得心里不踏实?可是这里面也没有doFilter呀?那就从父类找!
AbstractAuthenticationProcessingFilter
点开AbstractAuthenticationProcessingFilter,删掉不必要的代码!
public abstract class AbstractAuthenticationProcessingFilter
extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {

//doFilter再次!
public void doFilter(ServletRequest req, 
                     ServletResponse res, 
                     FilterChain chain) throws IOException, ServletException {

    HttpServletRequest request = (HttpServletRequest)req;
    HttpServletResponse response = (HttpServletResponse)res;

    if (!this.requiresAuthentication(request, response)) {
        chain.doFilter(request, response);
    } else {

        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Request is to process authentication");
        }

        Authentication authResult;
        try {
            authResult = this.attemptAuthentication(request, response);
            if (authResult == null) {
                return;
            }
            this.sessionStrategy.onAuthentication(authResult, request, response);
        } catch (InternalAuthenticationServiceException var8) {
            this.logger.error("An internal error occurred while trying to authenticate the
            user.", var8);
            this.unsuccessfulAuthentication(request, response, var8);
            return;
        } catch (AuthenticationException var9) {
            this.unsuccessfulAuthentication(request, response, var9);
            return;
        }
        if (this.continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        this.successfulAuthentication(request, response, chain, authResult);
    }
}

protected boolean requiresAuthentication(HttpServletRequest request, 
                                         HttpServletResponse response) {
    return this.requiresAuthenticationRequestMatcher.matches(request);
}

//成功走successfulAuthentication
protected void successfulAuthentication(HttpServletRequest request, 
                                        HttpServletResponse response, 
                                        FilterChain chain, 
                                        Authentication authResult) throws IOException, ServletException {

    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Authentication success. Updating SecurityContextHolder to
                          contain: " + authResult);
    }

    //认证成功,将认证信息存储到SecurityContext中!
    SecurityContextHolder.getContext().setAuthentication(authResult);
    //登录成功调用rememberMeServices
    this.rememberMeServices.loginSuccess(request, response, authResult);
    if (this.eventPublisher != null) {
        this.eventPublisher.publishEvent(new
                                         InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
    }
    this.successHandler.onAuthenticationSuccess(request, response, authResult);
}

//失败走unsuccessfulAuthentication
protected void unsuccessfulAuthentication(HttpServletRequest request, 
                                          HttpServletResponse response, 
                                          AuthenticationException failed) throws IOException, ServletException {
    SecurityContextHolder.clearContext();
    if (this.logger.isDebugEnabled()) {
        this.logger.debug("Authentication request failed: " + failed.toString(), failed);
        this.logger.debug("Updated SecurityContextHolder to contain null Authentication");
        this.logger.debug("Delegating to authentication failure handler " +
                          this.failureHandler);
    }
    this.rememberMeServices.loginFail(request, response);
    this.failureHandler.onAuthenticationFailure(request, response, failed);
}

}
可见AbstractAuthenticationProcessingFilter这个过滤器对于认证成功与否,做了两个分支,成功执行 successfulAuthentication,失败执行unsuccessfulAuthentication。
在successfulAuthentication内部,将认证信息存储到了SecurityContext中。并调用了loginSuccess方法,这就是常见的“记住我”功能!此功能具体应用,咱们后续再研究!

相关文章
|
3月前
|
SQL JSON Java
案例实操 IService的Lambda查询
本课程深入讲解MyBatis-Plus核心功能:实现复杂条件查询、余额扣减更新、批量插入性能优化;涵盖代码生成、逻辑删除、枚举与JSON处理器;重点实现分页插件及通用分页实体,完成用户分页查询接口,助力高效开发。
|
3月前
|
JSON 监控 API
日本股票 API 对接实战指南(实时行情与 IPO 专题)
随着巴菲特增持日本商社及日经225强势表现,日本股市备受关注。本文介绍如何通过StockTV API(countryId=35)快速接入日本股市实时行情、IPO新股日历与K线数据,支持秒级更新与多周期技术分析,助力开发者高效构建金融应用。
770 156
|
3月前
|
人工智能 开发者
【25.12.12】AI 原生应用开源开发者沙龙·杭州站 用户洞察
【25.12.12】AI 原生应用开源开发者沙龙·杭州站 用户洞察
136 0
|
3月前
|
存储 编解码 API
3步搞定!阿里云视频点播入门保姆级教程
本文是阿里云视频点播系列教程首篇,面向零基础用户,详细介绍从开通服务、上传视频、转码处理到播放器嵌入的完整流程,涵盖控制台操作与SDK开发,助你快速搭建音视频应用。
597 1
3步搞定!阿里云视频点播入门保姆级教程
|
3月前
|
机器学习/深度学习 JSON 文字识别
还在研究部署PaddleOCR?数眼智能OCR文档解析API上线:免费使用!
数眼智能推出新一代OCR文档解析API,突破传统OCR局限,融合多模态识别与深度学习,精准还原PDF、扫描件中的文字、表格、公式等结构,直接输出带层级的Markdown与JSON数据。实现标题、段落、表格自动分离,助力大模型知识库构建、金融报表分析、学术文献处理等场景,10秒内高效响应,让纸质文档秒变智能数据资产。
218 1
|
3月前
|
机器学习/深度学习 自然语言处理 网络架构
Transformer基础结构
Transformer是Vaswani等人于2017年提出的基于注意力机制的神经网络架构,摒弃了传统RNN,实现并行化处理,显著提升自然语言处理性能。其核心为编码器-解码器结构,通过自注意力机制捕捉长距离依赖,结合位置编码、残差连接与层归一化,有效训练深层模型。多头注意力机制增强特征表达能力,广泛应用于机器翻译、文本生成等任务,成为现代大模型基石。
|
3月前
|
Java Maven 开发工具
2 IDEA环境配置
安装IDEA并配置编码、自动导包、忽略大小写提示及Java编译级别;安装Maven并配置本地仓库与阿里云镜像;在IDEA中设置Maven环境;安装Git并配置用户信息与IDEA集成;Fork并克隆黑马商城项目,创建数据库hmall并导入SQL;修改application-dev.yaml中的数据库连接信息;启动后端项目与Nginx前端服务,确保端口无冲突。
|
3月前
|
消息中间件 Java 数据安全/隐私保护
RabbitMQ集群部署
本文介绍RabbitMQ集群部署及高可用方案,涵盖普通集群搭建、镜像模式配置与仲裁队列使用。通过Docker部署三节点集群,配置Erlang Cookie与rabbitmq.conf实现节点通信;利用镜像模式实现数据冗余,支持主从切换;引入3.8版本后的仲裁队列,简化高可用配置,提升系统容错能力。
|
12月前
|
机器学习/深度学习 人工智能 自然语言处理
关于LLM-as-a-judge范式,终于有综述讲明白了
《From Generation to Judgment: Opportunities and Challenges of LLM-as-a-judge》探讨了大型语言模型(LLM)在评估和判断任务中的应用。传统方法存在不足,而LLM凭借强大的语言理解和生成能力,展现了广阔的应用前景。论文从输入输出角度定义LLM-as-a-judge,提出三维度分类体系,并汇编评估基准,指出关键挑战如偏见、可解释性和对抗性攻击,展望未来改进方向,强调其潜力与价值。论文链接:https://arxiv.org/abs/2411.16594
825 1
|
Prometheus Kubernetes 负载均衡
Opentelemetry collector用法
本文详细介绍了Opentelemetry Collector的使用方法及其各个组件(receiver、processor、exporter、connector和服务配置)的功能与配置。Collector的核心组件通过官方仓库提供丰富的实现,涵盖了认证、健康监控等功能。
2234 63
Opentelemetry collector用法

热门文章

最新文章