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

简介: 本文深入分析Spring Security认证流程,从UsernamePasswordAuthenticationFilter到AuthenticationManager、ProviderManager,层层解析认证机制。重点剖析UserDetailsService自定义实现原理,详解如何通过重写loadUserByUsername返回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方法,这就是常见的“记住我”功能!此功能具体应用,咱们后续再研究!

2.自定义认证实现流程

1.实现UserDetailService与自定义逻辑

自定义用户类需要实现UserDetails接口,并实现接口的方法,所以我们编写下述代码。

package com.yzxb.SpringSecurity.service;
import com.yzxb.SpringSecurity.pojo.Role;
import com.yzxb.SpringSecurity.pojo.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Service
public class MyUserService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        // 1-本地mysql用户存在性查询
        User user = selectUserFromDb();
        if (Objects.isNull(user)) {
            throw new UsernameNotFoundException("用户不存在");
        }
        // 2-本地查询权限
        List<Role> roles = selectAuthFromDb();
        // 3-设置权限信息
        user.setRoles(roles);
        // 4-返回权限合集
        return user;
    }
    /**
     * TODO 如果需要调用数据库查询,这里接入orm持久层框架即可
     * @return 用户本地权限合集
     */
    private List<Role> selectAuthFromDb() {
        return new ArrayList<>();
    }
    /**
     * TODO 如果需要调用数据库查询,这里接入orm持久层框架即可
     * @return  本地用户信息
     */
    private User selectUserFromDb() {
        return new User();
    }
}

2.注册自定义实现类

package com.yzxb.SpringSecurity.config;
import com.yzxb.SpringSecurity.service.MyUserService;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import javax.annotation.Resource;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Resource
    private MyUserService myUserService;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().formLogin().loginPage("/login.html")
                .loginProcessingUrl("/doLogin")
                .defaultSuccessUrl("/demo/index ")
                .failureUrl("/login.html")
                .usernameParameter("uname")
                .passwordParameter("passwd")
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserService);
    }
}

改完的效果如下图

然后重启项目,就可以实现自定义的数据库认证逻辑。

3.完整代码获取

相关文章
|
4月前
|
安全 Java 数据库
Spring Boot中集成 Shiro
本课程介绍Java安全框架Shiro的核心功能,涵盖认证与授权。重点讲解三大组件:Subject、SecurityManager和Realm,并结合Spring Boot实现自定义Realm、权限控制及过滤器配置,通过实例演示登录、角色权限验证全过程,帮助开发者快速掌握Shiro在实际项目中的应用。
|
4月前
|
Linux 数据安全/隐私保护 虚拟化
虚拟机安装(CentOS7)
准备CentOS7镜像及VMware Workstation(可从百度云下载),提取码已提供。使用VMware创建虚拟机,参考指定教程完成安装。默认登录用户为root,密码由用户自设。确保电脑配置满足运行需求。(238字)
|
10月前
|
运维 Kubernetes Cloud Native
智联招聘 × 阿里云 ACK One:云端弹性算力颠覆传统 IDC 架构,打造春招技术新范式
在 2025 年春季招聘季的激战中,智联招聘凭借阿里云 ACK One 注册集群与弹性 ACS 算力的深度融合,成功突破传统 IDC 机房的算力瓶颈,以云上弹性架构支撑千万级用户的高并发访问,实现招聘服务效率与稳定性的双重跃升。
|
4月前
|
负载均衡 应用服务中间件 Nacos
Nacos配置中心
本文详细讲解了Nacos作为配置中心的核心功能与实践应用,涵盖配置管理、热更新、共享配置及优先级规则,并通过搭建Nacos集群实现高可用部署,帮助开发者掌握微服务环境下配置的集中化管理方案。
 Nacos配置中心
|
4月前
|
负载均衡 Java 数据安全/隐私保护
Gateway服务网关
本文介绍了微服务架构中网关的核心作用,包括请求路由、权限控制和限流等功能,重点讲解了Spring Cloud Gateway的使用方法。内容涵盖网关的快速搭建、路由配置、断言与过滤器工厂的应用,并通过实际案例演示了如何实现全局过滤器与跨域问题的解决方案,帮助开发者掌握微服务统一入口的关键技术。
 Gateway服务网关
|
4月前
|
SpringCloudAlibaba Java Nacos
SpringCloud概述
Spring Cloud是微服务架构的综合解决方案,由Spring团队推出,具备约定大于配置、组件丰富、开箱即用等特点。为解决Netflix组件停更问题,阿里推出Spring Cloud Alibaba,集成Nacos、Sentinel、Seata等高性能中间件,成为主流技术栈选择。
|
4月前
|
NoSQL Linux 网络安全
Redis集群部署指南
本章基于CentOS7搭建Redis集群,涵盖单机安装、主从复制、哨兵高可用及分片集群配置,详细演示Redis集群部署与管理全过程。
Redis集群部署指南
|
Web App开发 Windows
Windows 记录一次磁盘相关的PC卡顿问题
【10月更文挑战第26天】本文记录了一次Windows系统中因磁盘问题导致的PC卡顿现象及其解决过程。通过查看任务管理器发现磁盘使用率高,经磁盘碎片整理、优化启动项与后台程序、更新磁盘驱动等步骤,最终解决了卡顿问题。建议定期进行磁盘维护,合理管理启动项,及时更新驱动以预防类似问题。
438 5
|
Go
Golang语言之切片(slice)快速入门篇
这篇文章是关于Go语言中切片(slice)的快速入门教程,详细介绍了切片的概念、定义方式、遍历、扩容机制、使用注意事项以及相关练习题。
539 6
PreparedStatement 防止 SQL 注入原理
PreparedStatement 对象可以防止 SQL 注入,而 Statement 对象不能防止 SQL 注入,接下来使用一个案例剖析原理。
PreparedStatement 防止 SQL 注入原理