SpringSecurity的认证流程中是通过其中的UserNamePasswordAuthenticationFilter实现的。进入该过滤器的doFilter方法:

    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;
        }
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }
        successfulAuthentication(request, response, chain, authResult);
    }

其中的attemptAuthentication方法做了获取认证对象的工作,而successfulAuthentication方法则是把得到的认证对象存在springSecurity的上下文中。我们进入attemptAuthentication方法:

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

从上文中我们可以看出,springSecurity用请求中的username和password(这里参数写死了,只能传这两个字段)参数创建了一个UsernamePasswordAuthenticationToken对象,并调用AuthenticationManager的authenticate方法获取认证对象,这里可以使用我们自定义的AuthenticationManager,而springSecurity默认使用ProviderManager。跟进ProviderManager的authenticate方法,这里方法过长,分开展示:

        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 | InternalAuthenticationServiceException e) {
                prepareException(e, authentication);
                throw e;
            } catch (AuthenticationException e) {
                lastException = e;
            }
        }

该类中有一个类型的为List<AuthenticationProvider>的providers属性,其实就是用这个list里面的AuthenticationProvider的authenticate方法去认证,而其supports方法方法决定是否支持该authention类型。如果没有找到匹配的AuthenticationProvider,则会去parent中寻找。
1.jpg

if (result == null && parent != null) {
            // Allow the parent to try.
            try {
                result = parentResult = parent.authenticate(authentication);
            }
            catch (ProviderNotFoundException e) {
            }
            catch (AuthenticationException e) {
                lastException = parentException = e;
            }
        }

springSecurity默认使用了DaoAuthenticationProvider,其继承了AbstractUserDetailsAuthenticationProvider,但并没有重写authenticate方法,进入其继承了AbstractUserDetailsAuthenticationProvider的authenticate方法:

    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        // 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);
    }

我们可以看到springSecuriy会先去缓存中取,如果没有的话会调用retrieveUser方法获取UserDetail对象,该方法中用UserDetailService的loadUserByUsername方法获取UserDetail对象。之后走到additionalAuthenticationChecks方法,检查请求中的密码与userDetail对象中的密码是否一致,下面贴出代码:

    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }
    protected void additionalAuthenticationChecks(UserDetails userDetails,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            logger.debug("Authentication failed: no credentials provided");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }

        String presentedPassword = authentication.getCredentials().toString();

        if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
            logger.debug("Authentication failed: password does not match stored value");

            throw new BadCredentialsException(messages.getMessage(
                    "AbstractUserDetailsAuthenticationProvider.badCredentials",
                    "Bad credentials"));
        }
    }

自定义使用:

先看一下AuthenticationManager接口:

public interface AuthenticationManager {
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
}

接口里面只定义了一个方法,实现该方法后我们可以在springScurity的配置类WebSecurityConfigurerAdapter中重写authenticationManager方法:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailService userDetailService;

    @Autowired
    private MyAccessDecisionManager accessDecisionManager;

    @Autowired
    private MySecurityMetadataSource securityMetadataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        super.configure(http);
        MyObjectProcessor processor = new MyObjectProcessor(accessDecisionManager, securityMetadataSource);
        http.authorizeRequests().antMatchers("/home").permitAll()
                .anyRequest().authenticated().withObjectPostProcessor(processor)
                .and().formLogin()
                .and().httpBasic();
                http.authenticationProvider(new MyAuthenticationProvider());
    }

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return new MyAuthenticationManager();
    }
}

然后我们重新进入过滤器可以发现ProviderManager的parent以被设置为我们自定义的AuthenticationManager。

2.jpg

回头再来看一下AuthenticationProvider接口,里面定义了两个方法,分别为认证方法和是否支持的认证类型。

public interface AuthenticationProvider {

    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;


    boolean supports(Class<?> authentication);
}

我们可以在springSecurity配置类WebSecurityConfigurerAdapter中配置自定义的AuthenticationProvider,并且可以添加多个,如下图:
1.jpg

小结

简单介绍了springSecurity认证过滤器UsernamePasswordFilter认证流程的主要步骤,简要概述了如何自定义AuthenticationManager和AuthenticationProvider。


jiao个朋友
4 声望0 粉丝