博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Security基于数据库验证流程详解
阅读量:6084 次
发布时间:2019-06-20

本文共 11483 字,大约阅读时间需要 38 分钟。

hot3.png

Spring Security 校验流程图

Spring Security 登录流程.jpg

相关解释说明(认真看哦)

AbstractAuthenticationProcessingFilter 抽象类

/**     * 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。     * 如果需要验证,则会调用 #attemptAuthentication(HttpServletRequest, HttpServletResponse) 方法。     * 有三种结果:     * 1、返回一个 Authentication 对象。     * 配置的 SessionAuthenticationStrategy` 将被调用,     * 然后 然后调用 #successfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication) 方法。     * 2、验证时发生 AuthenticationException。     * #unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException) 方法将被调用。     * 3、返回Null,表示身份验证不完整。假设子类做了一些必要的工作(如重定向)来继续处理验证,方法将立即返回。     * 假设后一个请求将被这种方法接收,其中返回的Authentication对象不为空。     */    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)            throws IOException, ServletException {        HttpServletRequest request = (HttpServletRequest) req;        HttpServletResponse response = (HttpServletResponse) res;        if (!requiresAuthentication(request, response)) {            chain.doFilter(request, response);            return;        }        if (logger.isDebugEnabled()) {            logger.debug("Request is to process authentication");        }        Authentication authResult;        try {            authResult = attemptAuthentication(request, response);            if (authResult == null) {                // return immediately as subclass has indicated that it hasn't completed                // authentication                return;            }            sessionStrategy.onAuthentication(authResult, request, response);        }        catch (InternalAuthenticationServiceException failed) {            logger.error(                    "An internal error occurred while trying to authenticate the user.",                    failed);            unsuccessfulAuthentication(request, response, failed);            return;        }        catch (AuthenticationException failed) {            // Authentication failed            unsuccessfulAuthentication(request, response, failed);            return;        }        // Authentication success        if (continueChainBeforeSuccessfulAuthentication) {            chain.doFilter(request, response);        }        successfulAuthentication(request, response, chain, authResult);    }

UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子类)

public Authentication attemptAuthentication(HttpServletRequest request,            HttpServletResponse response) throws AuthenticationException {        if (postOnly && !request.getMethod().equals("POST")) {            throw new AuthenticationServiceException(                    "Authentication method not supported: " + request.getMethod());        }        String username = obtainUsername(request);        String password = obtainPassword(request);        if (username == null) {            username = "";        }        if (password == null) {            password = "";        }        username = username.trim();        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(                username, password);        // Allow subclasses to set the "details" property        setDetails(request, authRequest);        return this.getAuthenticationManager().authenticate(authRequest);    }

#attemptAuthentication () 方法将 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 对象,用于 AuthenticationManager 的验证(即 this.getAuthenticationManager().authenticate(authRequest) )。

默认情况下注入 Spring 容器的 AuthenticationManager 是 ProviderManager

ProviderManager(AuthenticationManager的实现类)

/**     * 尝试验证 Authentication 对象     * AuthenticationProvider 列表将被连续尝试,直到 AuthenticationProvider 表示它能够认证传递的过来的Authentication 对象。然后将使用该 AuthenticationProvider 尝试身份验证。     * 如果有多个 AuthenticationProvider 支持验证传递过来的Authentication 对象,那么由第一个来确定结果,覆盖早期支持AuthenticationProviders 所引发的任何可能的AuthenticationException。 成功验证后,将不会尝试后续的AuthenticationProvider。     * 如果最后所有的 AuthenticationProviders 都没有成功验证 Authentication 对象,将抛出 AuthenticationException。     */    public Authentication authenticate(Authentication authentication)            throws AuthenticationException {        Class
toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); for (AuthenticationProvider provider : getProviders()) { if (!provider.supports(toTest)) { continue; } if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { result = provider.authenticate(authentication); if (result != null) { copyDetails(authentication, result); break; } } catch (AccountStatusException e) { prepareException(e, authentication); // SEC-546: Avoid polling additional providers if auth failure is due to // invalid account status throw e; } catch (InternalAuthenticationServiceException e) { prepareException(e, authentication); throw e; } catch (AuthenticationException e) { lastException = e; } } if (result == null && parent != null) { // Allow the parent to try. try { result = parent.authenticate(authentication); } catch (ProviderNotFoundException e) { // ignore as we will throw below if no other exception occurred prior to // calling parent and the parent // may throw ProviderNotFound even though a provider in the child already // handled the request } catch (AuthenticationException e) { lastException = e; } } if (result != null) { if (eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) { // Authentication is complete. Remove credentials and other secret data // from authentication ((CredentialsContainer) result).eraseCredentials(); } eventPublisher.publishAuthenticationSuccess(result); return result; } // Parent was null, or didn't authenticate (or throw an exception). if (lastException == null) { lastException = new ProviderNotFoundException(messages.getMessage( "ProviderManager.providerNotFound", new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}")); } prepareException(lastException, authentication); throw lastException; }

从代码中不难看出,由 provider 来验证 authentication, 核心点方法是:

Authentication result = provider.authenticate(authentication);

此处的 provider 是 AbstractUserDetailsAuthenticationProvider

AbstractUserDetailsAuthenticationProvider 是AuthenticationProvider的实现,看看它的 #authenticate(authentication) 方法:

// 验证 authenticationpublic Authentication authenticate(Authentication authentication)            throws AuthenticationException {        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,                messages.getMessage(                        "AbstractUserDetailsAuthenticationProvider.onlySupports",                        "Only UsernamePasswordAuthenticationToken is supported"));        // Determine username        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"                : authentication.getName();        boolean cacheWasUsed = true;        UserDetails user = this.userCache.getUserFromCache(username);        if (user == null) {            cacheWasUsed = false;            try {                user = retrieveUser(username,                        (UsernamePasswordAuthenticationToken) authentication);            }            catch (UsernameNotFoundException notFound) {                logger.debug("User '" + username + "' not found");                if (hideUserNotFoundExceptions) {                    throw new BadCredentialsException(messages.getMessage(                            "AbstractUserDetailsAuthenticationProvider.badCredentials",                            "Bad credentials"));                }                else {                    throw notFound;                }            }            Assert.notNull(user,                    "retrieveUser returned null - a violation of the interface contract");        }        try {            preAuthenticationChecks.check(user);            additionalAuthenticationChecks(user,                    (UsernamePasswordAuthenticationToken) authentication);        }        catch (AuthenticationException exception) {            if (cacheWasUsed) {                // There was a problem, so try again after checking                // we're using latest data (i.e. not from the cache)                cacheWasUsed = false;                user = retrieveUser(username,                        (UsernamePasswordAuthenticationToken) authentication);                preAuthenticationChecks.check(user);                additionalAuthenticationChecks(user,                        (UsernamePasswordAuthenticationToken) authentication);            }            else {                throw exception;            }        }        postAuthenticationChecks.check(user);        if (!cacheWasUsed) {            this.userCache.putUserInCache(user);        }        Object principalToReturn = user;        if (forcePrincipalAsString) {            principalToReturn = user.getUsername();        }        return createSuccessAuthentication(principalToReturn, authentication, user);    }

AbstractUserDetailsAuthenticationProvider 内置了缓存机制,从缓存中获取不到的 UserDetails 信息的话,就调用如下方法获取用户信息,然后和 用户传来的信息进行对比来判断是否验证成功。

// 获取用户信息UserDetails user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);

#retrieveUser() 方法在 DaoAuthenticationProvider 中实现,DaoAuthenticationProvider 是 AbstractUserDetailsAuthenticationProvider的子类。具体实现如下:

protected final UserDetails retrieveUser(String username,            UsernamePasswordAuthenticationToken authentication)            throws AuthenticationException {        UserDetails loadedUser;        try {            loadedUser = this.getUserDetailsService().loadUserByUsername(username);        }        catch (UsernameNotFoundException notFound) {            if (authentication.getCredentials() != null) {                String presentedPassword = authentication.getCredentials().toString();                passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,                        presentedPassword, null);            }            throw notFound;        }        catch (Exception repositoryProblem) {            throw new InternalAuthenticationServiceException(                    repositoryProblem.getMessage(), repositoryProblem);        }        if (loadedUser == null) {            throw new InternalAuthenticationServiceException(                    "UserDetailsService returned null, which is an interface contract violation");        }        return loadedUser;    }

可以看到此处的返回对象 userDetails 是由 UserDetailsService 的 #loadUserByUsername(username) 来获取的。

转载于:https://my.oschina.net/liuyuantao/blog/1922365

你可能感兴趣的文章
PostgreSQL 10.1 手册_部分 II. SQL 语言_第 9 章 函数和操作符_9.19. 范围函数和操作符...
查看>>
以太坊系列之六: p2p模块--以太坊源码学习
查看>>
使用scikit-learn解决文本多分类问题(附python演练)
查看>>
2018 年最值得关注的 JavaScript 趋势
查看>>
什么是区块链?超级账本 Brian Behlendorf 从五个方面教你认识
查看>>
Linux中的帮助功能
查看>>
针对Android的Pegasus恶意软件版本和针对iOS的有什么不同?
查看>>
全局探色器
查看>>
Hive Export和Import介绍及操作示例
查看>>
http://mongoexplorer.com/ 一个不错的 mongodb 客户端工具。。。
查看>>
Xcode 4.3 使用xcodebuild命令编译项目环境设置
查看>>
上传jar包到nexus私服
查看>>
Android LruCache 二级缓存
查看>>
Java使用Redis
查看>>
Why Namespace? - 每天5分钟玩转 OpenStack(102)
查看>>
Nuget帮助说明
查看>>
基于linux的ekho(余音)安装与开发
查看>>
Java基础---Java中无参数带返回值方法的使用(三十七)
查看>>
MySQL性能优化的最佳20+条经验(1)
查看>>
对Ansible二次开发来检查服务器的Java程序占用端口数量
查看>>