SpringSecurity(四)认证流程

认证流程图

相关类

UsernamePasswordAuthenticationFilter是一个过滤器,它继承了抽象类AbstractAuthenticationProcessingFilter,负责表单登录请求,

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 {
        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();
            // 用户username和password构造一个UsernamePasswordAuthenticationToken对象
            // UsernamePasswordAuthenticationToken也是Authentication几口的一个实现
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            
            // 将用户的一些信息设置到authRequest
            this.setDetails(request, authRequest);
            // 调用AuthenticationManager的authenticate方法进行验证,如果验证过程中失败就会抛出异常,如果认证成功则会返回一个经过认证的Authentication对象
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(this.passwordParameter);
    }

    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(this.usernameParameter);
    }

    protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    public void setUsernameParameter(String usernameParameter) {
        Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
        this.usernameParameter = usernameParameter;
    }

    public void setPasswordParameter(String passwordParameter) {
        Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
        this.passwordParameter = passwordParameter;
    }

    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getUsernameParameter() {
        return this.usernameParameter;
    }

    public final String getPasswordParameter() {
        return this.passwordParameter;
    }
}

Authentication接口

public interface Authentication extends Principal, Serializable {
    // 1. 权限结合,可使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_ADMIN")返回字符串权限集合
    Collection<? extends GrantedAuthority> getAuthorities();
    // 2. 用户名密码认证时可以理解为密码
    Object getCredentials();
    // 3. 认证时包含的一些信息。
    Object getDetails();
    // 4. 用户名密码认证时可理解时用户名
    Object getPrincipal();
    // 5. 是否被认证,认证为true 
    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

AuthenticationManager

// 认证方法的入口,没有验证逻辑,是用来管理AuthenticationProvider
public interface AuthenticationManager {
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

ProviderManager实现了AuthenticationManager,用来获取AuthenticationProvider,AuthenticationProvider实现验证逻辑。

它提供了基本的认证逻辑和方法;它包含了一个 List<AuthenticationProvider>对象,通过 AuthenticationProvider 接口来扩展出不同的认证提供者(当Spring Security默认提供的实现类不能满足需求的时候可以扩展AuthenticationProvider 覆盖supports(Class<?> authentication)方法);

ProviderManager部分代码如下

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
        Iterator var6 = this.getProviders().iterator();

        // 由于可能存在多种认证方法,例如用户名密码认证、短信认证,所以选择当前是属于哪种认证方法
        while(var6.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var6.next();
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    // 认证过程
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var11) {
                    this.prepareException(var11, authentication);
                    throw var11;
                } catch (InternalAuthenticationServiceException var12) {
                    this.prepareException(var12, authentication);
                    throw var12;
                } catch (AuthenticationException var13) {
                    lastException = var13;
                }
            }
        }
}

AuthenticationProvider也是一个接口,AbstractUserDetailsAuthenticationProvider和DaoAuthenticationProvider都是它的子类

public interface AuthenticationProvider {
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    boolean supports(Class<?> var1);
}

AbstractUserDetailsAuthenticationProvider是一个抽象类,AbstractUserDetailsAuthenticationProvider主要实现了AuthenticationProvider的接口方法authenticate并提供了相关的验证逻辑,部分代码如下。

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
        String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                // retrieveUserretrieveUser是AbstractUserDetailsAuthenticationProvider的一个抽象方法,它是由
                // 子类DaoAuthenticationProvider实现的
                // 通过用户名获取用户信息
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User '" + username + "' not found");
                if (this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            // 检查账户是否locked、disabled、expired
            this.preAuthenticationChecks.check(user);
            // 校验密码是否正确
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if (!cacheWasUsed) {
                throw var7;
            }

            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }
        // 校验账户credentials是否过期
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        // 到这里说明已经登陆成功了,返回一个已经经过认证的Authentication对象
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
}

DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider抽象类,
DaoAuthenticationProvider部分代码如下

protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();

        try {
            // getUserDetailsService()返回一个UserDetailsService对象,这里就和前面介绍
            // 如何实现UserDetailsService接口返回一个用户对象连接上了,就相当于程序运行到这里,就会调用
            // 之前我们自己实现的MyUserService类中的loadUserByUsername方法
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
}

身份认证成功后,最后在UsernamePasswordAuthenticationFilter返回后会进入一个AbstractAuthenticationProcessingFilter类中调用successfulAuthentication方法中,这个方法最后会返回我们自己定义的登录成功处理器handler,在返回之前,它会调用SecurityContext,最后将认证的结果放入SecurityContextHolder中,SecurityContext类很简单,重写了equals方法和hascode方法,保证了authentication的唯一性。SecurityContextHolder类实际上对ThreadLocal的一个封装。因此可以在同一个线程中的不同方法中获取到认证信息。最后会被SecurityContextPersistenceFilter过滤器使用,这个过滤器的作用是:当一个请求来的时候,它会将session中的值传入到该线程中,当请求返回的时候,它会判断该请求线程是否有SecurityContext,如果有它会将其放入到session中,因此保证了请求结果可以在不同的请求之间共享。 

如果身份认证失败,最后在UsernamePasswordAuthenticationFilter返回后会进入一个AbstractAuthenticationProcessingFilter类中调用unsuccessfulAuthentication方法中,这个方法最后会返回我们自己定义的登录失败处理器handler

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);
        }
    }

    // 该方法由子类UsernamePasswordAuthenticationFilter实现
    public abstract Authentication attemptAuthentication(HttpServletRequest var1, HttpServletResponse var2) throws AuthenticationException, IOException, ServletException;

    // 认证成功
    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);
        }
        // 将认证信息存在SecurityContextHolder中
        SecurityContextHolder.getContext().setAuthentication(authResult);
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        // 认证成功后,将会调用我们前面实现过的MyAuthenticationSuccessHandler类
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

    // 认证失败
    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);
        // 认证失败后,将会调用我们前面实现过的MyAuthenticationSuccessHandler类
        this.failureHandler.onAuthenticationFailure(request, response, failed);
    }

SecurityContextPersistenceFilter
请求进来时,检查Session中是否有SecurityContext,如果有就从Session中拿出并放到线程中,如果没有就往下执行
当认证过程走完,最后回到这里时,检查线程,如果线程中存在SecurityContext,就将其取出并存到Session中

获取认证信息

    @GetMapping("/me1")
    public Object me1() {
        return SecurityContextHolder.getContext().getAuthentication();
    }

    @GetMapping("/me2")
    public Object me2(Authentication authentication) {
        return authentication;
    }

    @GetMapping("/me3")
    public Object me3(Principal principal) {
        return principal;
    }

参考文章   http://www.spring4all.com/article/439

猜你喜欢

转载自blog.csdn.net/lizc_lizc/article/details/84061657