SpringSecurity主要对象及认证流程


前言

    SpringSecurity是一款十分强大的安全框架,它主要具有认证、授权、安全保护等功能。接下来,我将详细讲述SpringSecurity是如何在Web项目中运行,并结合表单登录讲解其认证流程。


一、Servlet Security

1.1过滤器及过滤器链的概念

    Java中的Filter并不是一个标准的Servlet ,它不能处理用户请求,也不能对客户端生成响应。主要用于对HttpServletRequest 进行预处理,也可以对HttpServletResponse 进行后处理。

    Web 应用程序可以根据特定的目的定义若干个不同的过滤器,由此形成过滤器链。过滤链的好处是,执行过程中任何时候都可以打断,只要不执行chain.doFilter()就不会再执行后面的过滤器和请求的内容。而在实际使用时,就要特别注意过滤链的执行顺序问题

1.2DelegatingFilterProxy

    Spring框架提供了一个叫做 DelegatingFilterProxy的类,它是SpringSecurity框架当中的Filter接口的实现类。通过DelegatingFilterProxy可以使Servlet容器和Spring框架的ApplicationContext对象之间建立起“桥梁”。
在这里插入图片描述
    解释上一句:Servlet容器允许使用其自身的标准注册Servlet当中的Filter,但是它并不能直接注册SpringSecurity当中的Fillter。所以,Servlet容器通过注册DelegatingFilterProxy对象,使用DelegatingFilterProxy来代理执行SpringSecurity当中的所有Filter

    通过上述的方式,SpringSecurity成功的“嵌入”到了Servlet的过滤器链当中。

1.3FilterChainProxy

    由上述可知,Servlet容器是使用DelegatingFilterProxy来代理执行SpringSecurity当中的Filter。事实上却并不是如此,而是由DelegatingFilterProxy创建的FilterChainProxy对象来真正代理SpringSecurity当中实例化过的Filter。既然真正的代理对象已经找到了,那么被代理的Filter们存储在哪里呢?
上源码:

public class DelegatingFilterProxy extends GenericFilterBean {
    
    
	@Nullable
    private String contextAttribute;
    @Nullable
    private WebApplicationContext webApplicationContext;
    @Nullable
    private String targetBeanName;
    private boolean targetFilterLifecycle;
    @Nullable
    private volatile Filter delegate;//代理的Filter对象
    private final Object delegateMonitor;
    
	//初始化SpringSecurity当中的Filter
	protected void initFilterBean() throws ServletException {
    
    
        synchronized(this.delegateMonitor) {
    
    
            if (this.delegate == null) {
    
    
                if (this.targetBeanName == null) {
    
    
                    this.targetBeanName = this.getFilterName();
                }

                WebApplicationContext wac = this.findWebApplicationContext();
                if (wac != null) {
    
    
                	//调用initDelegate方法获取当前代理的filter
                    this.delegate = this.initDelegate(wac);
                }
            }

        }
    }

	//doFilter()方法,与Servlet当中Filter的方法是否十分相似呢?
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
        Filter delegateToUse = this.delegate;
        if (delegateToUse == null) {
    
    
            synchronized(this.delegateMonitor) {
    
    
                delegateToUse = this.delegate;
                if (delegateToUse == null) {
    
    
                    WebApplicationContext wac = this.findWebApplicationContext();
                    if (wac == null) {
    
    
                        throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
                    }
					//调用initDelegate方法获取当前代理的filter
                    delegateToUse = this.initDelegate(wac);
                }

                this.delegate = delegateToUse;
            }
        }

        this.invokeDelegate(delegateToUse, request, response, filterChain);
    }
    
	protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
    
    
		//通过debug发现此处的targetBeanName为springSecurityFilterChain
        String targetBeanName = this.getTargetBeanName();
        Assert.state(targetBeanName != null, "No target bean name set");
        //通过debug我们发现delegate当中封装着SpringSecurity的多个Filter
        Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
        if (this.isTargetFilterLifecycle()) {
    
    
            delegate.init(this.getFilterConfig());
        }

        return delegate;
    }
    
	//调用delegate当中封装着的Filter对象的doFilter方法
    protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    
    
        delegate.doFilter(request, response, filterChain);
    }
}


进入FilterChainProxy内部:

public class FilterChainProxy extends GenericFilterBean {
    
    
    private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
    private static final String FILTER_APPLIED = FilterChainProxy.class.getName().concat(".APPLIED");
    private List<SecurityFilterChain> filterChains;//通过分析,发现SpringSecurity当中的Filter都封装在SecurityFilterChain当中
    private FilterChainProxy.FilterChainValidator filterChainValidator;
    private HttpFirewall firewall;
    private RequestRejectedHandler requestRejectedHandler;
	
	//过滤的方法
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
    
        boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
        if (!clearContext) {
    
    
        	//调用doFilterInternal方法
            this.doFilterInternal(request, response, chain);
        } else {
    
    
            try {
    
    
                request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                this.doFilterInternal(request, response, chain);
            } catch (RequestRejectedException var9) {
    
    
                this.requestRejectedHandler.handle((HttpServletRequest)request, (HttpServletResponse)response, var9);
            } finally {
    
    
                SecurityContextHolder.clearContext();
                request.removeAttribute(FILTER_APPLIED);
            }

        }
    }

    private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    
    
        FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);
        HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);
        //注意:此处是一个集合,里面封装着Filter类型的对象,很可疑!!!
        //继续查看getFilters()方法
        List<Filter> filters = this.getFilters((HttpServletRequest)firewallRequest);
        if (filters != null && filters.size() != 0) {
    
    
            if (logger.isDebugEnabled()) {
    
    
                logger.debug(LogMessage.of(() -> {
    
    
                    return "Securing " + requestLine(firewallRequest);
                }));
            }

            FilterChainProxy.VirtualFilterChain virtualFilterChain = new FilterChainProxy.VirtualFilterChain(firewallRequest, chain, filters);
            virtualFilterChain.doFilter(firewallRequest, firewallResponse);
        } else {
    
    
            if (logger.isTraceEnabled()) {
    
    
                logger.trace(LogMessage.of(() -> {
    
    
                    return "No security for " + requestLine(firewallRequest);
                }));
            }

            firewallRequest.reset();
            chain.doFilter(firewallRequest, firewallResponse);
        }
    }
	
    private List<Filter> getFilters(HttpServletRequest request) {
    
    
        int count = 0;
        //发现Filter原来都封装在filterChains里面,刚好对应上图delegate对象当中的filterChain属性
        Iterator var3 = this.filterChains.iterator();

        SecurityFilterChain chain;
        do {
    
    
            if (!var3.hasNext()) {
    
    
                return null;
            }

            chain = (SecurityFilterChain)var3.next();
            if (logger.isTraceEnabled()) {
    
    
                ++count;
                logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, count, this.filterChains.size()));
            }
        } while(!chain.matches(request));

        return chain.getFilters();
    }
}    

进入SecurityFilterChain的内部:

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
    
    
    private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
    private final RequestMatcher requestMatcher;
    private final List<Filter> filters;//该集合当中封装着SpringSecurity的Filter

    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
    
    
        this(requestMatcher, Arrays.asList(filters));
    }

    public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
    
    
        logger.info(LogMessage.format("Will secure %s with %s", requestMatcher, filters));
        this.requestMatcher = requestMatcher;
        this.filters = new ArrayList(filters);
    }

    public RequestMatcher getRequestMatcher() {
    
    
        return this.requestMatcher;
    }

    public List<Filter> getFilters() {
    
    
        return this.filters;
    }

    public boolean matches(HttpServletRequest request) {
    
    
        return this.requestMatcher.matches(request);
    }

    public String toString() {
    
    
        return this.getClass().getSimpleName() + " [RequestMatcher=" + this.requestMatcher + ", Filters=" + this.filters + "]";
    }
}

    通过一步步的分析,我们最终发现SpringSecurity的Filter们都被封装在一个SecurityFilterChain类型的对象当中。

1.4SecurityFilterChain

    FilterChainProxy使用SecurityFilterChain对象去决定当前哪一个SpringSecurity的Filter应该被调用。
在这里插入图片描述
    注意:SecurityFilterChain当中的Security Filters一般都是注册到IOC当中的bean对象,但是其并不是通过SecurityFilterChain进行注册的,而是通过DelegatingFilterProxy(上图的FilterChainProxy类型的delegate对象在初始化时,其内部已经加载过Security Filters了)。

1.5Security Filters

    Security Filters通过 SecurityFilterChain API被注入到了 FilterChainProxy。通常你并不需要清楚SpringSecurity当中的filters的执行顺序,然而有时知道他们的执行顺序也许会对你有用。
    下面列出一些SpringSecurity当中的Filter的执行顺序。

  • ChannelProcessingFilter
  • WebAsyncManagerIntegrationFilter
  • SecurityContextPersistenceFilter
  • HeaderWriterFilter
  • CorsFilter
  • CsrfFilter
  • LogoutFilter
  • OAuth2AuthorizationRequestRedirectFilter
  • Saml2WebSsoAuthenticationRequestFilter
  • X509AuthenticationFilter
  • AbstractPreAuthenticatedProcessingFilter
  • CasAuthenticationFilter
  • OAuth2LoginAuthenticationFilter
  • Saml2WebSsoAuthenticationFilter
  • UsernamePasswordAuthenticationFilter
  • OpenIDAuthenticationFilter
  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
  • ConcurrentSessionFilter
  • DigestAuthenticationFilter
  • BearerTokenAuthenticationFilter
  • BasicAuthenticationFilter
  • RequestCacheAwareFilter
  • SecurityContextHolderAwareRequestFilter
  • JaasApiIntegrationFilter
  • RememberMeAuthenticationFilter
  • AnonymousAuthenticationFilter
  • OAuth2AuthorizationCodeGrantFilter
  • SessionManagementFilter
  • ExceptionTranslationFilter
  • FilterSecurityInterceptor
  • SwitchUserFilter

1.6Handling Security Exceptions

    ExceptionTranslationFilter被插入到 FilterChainProxy当中作为其中之一的Filter。该Filter能够将 AccessDeniedException 和 AuthenticationException类型的异常添加到http响应当中。
在这里插入图片描述

  1. 首先,当FilterChainProxy执行到SecurityFilterChain当中的ExceptionTranslationFilter时,ExceptionTranslationFilter对象会调用FilterChain.doFilter(request, response)方法去继续执行当前应用剩下的Filter。
  2. 如果当前用户在下一个过滤器FilterSecurityInterceptor当中进行授权时,发现其没有经过认证或者抛出AuthenticationException时,就会开启认证。
    ·首先 SecurityContextHolder会被清楚。
    ·HttpServletRequest会被保存在 RequestCache当中。当用户认证成功过后,可以从 RequestCache当中获取原先的request。
    · AuthenticationEntryPoint用于向客户端发送认证信息。例如:它可能发送一个重定向到登录页面的请求或者发送一个WWW-Authenticate请求头。
  3. 否则,如果返回的异常类型是AccessDeniedException,ExceptionTranslationFilter将会调用this.handleSpringSecurityException(request, response, chain, (RuntimeException)securityException)去处理响应的异常。
try {
    
    
    filterChain.doFilter(request, response); 
} catch (AccessDeniedException | AuthenticationException ex) {
    
    
    if (!authenticated || ex instanceof AuthenticationException) {
    
    
        startAuthentication(); 
    } else {
    
    
        accessDenied(); 
    }
}

    注意:如果 SecurityFilterChain当中前面的Filter抛出AuthenticationException 或者 AccessDeniedException类型的异常,该异常也会在这里被捕获并处理

二、认证流程分析

2.1登录流程

    让我们看一下在SpringSecurity中基于表单的工作流程是怎样的。首先,我们来了解一下用户未登录时如何重定向到登录表单界面。如下图所示:
在这里插入图片描述

 1.首先,一个用户向未授权的资源发送未经过认证的请求。	
 2.SpringSecurity的FilterSecurityInterceptor对象拦截到用户发送的请求,并开启认证流程。
 3.因为该请求没有经过认证,所以必然会抛出一个AccessDeniedException类型的异常(认证细节稍后稍后再讲)。
 4.ExceptionTranslationFilter这个过滤器类获取到该异常类之后,将会进行判断。如果发现是未认证类型的异常,将会使用配置过 AuthenticationEntryPoint对象向客户端发送一个重定向到/login页面的响应。
 5.浏览器将会向重定向页面再次发送请求
 6.通过loginController进行登录验证

    接下来,我们再来详细刨析一下使用表单进行登录的认证流程。


2.2UsernamePasswordAuthenticationFilter

    当用户名和密码通过表单进行提交之后,将由UsernamePasswordAuthenticationFilter进行认证用户的登录信息。这个UsernamePasswordAuthenticationFilter类型的过滤器继承自 AbstractAuthenticationProcessingFilter,因此UsernamePasswordAuthenticationFilter使用的是父类的doFilter()方法进行拦截验证。

AbstractAuthenticationProcessingFilter类源码分析:
在这里插入图片描述在这里插入图片描述

我们发现AbstractAuthenticationProcessingFilter类当中的attemptAuthentication()方法并没有立即实现,而是通过它的子类UsernamePasswordAuthenticationFilter进行实现。
在这里插入图片描述

2.3AuthenticationManager

    由上面源码得知,真正认证操作在AuthenticationManager里面!通过debug发现真正的认证的操作是在AuthenticationManager的实现类ProviderManager当中进行的:

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;

    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();
		
		//遍历认证策略,找到用于表单登录的认证策略
        while(var8.hasNext()) {
    
    
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            if (provider.supports(toTest)) {
    
    
                if (debug) {
    
    
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
    
    
	                //调用provider的认证方法
                    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;
                }
            }
        }        
    }
}    

2.4AbstractUserDetailsAuthenticationProvider

    咱们继续再找到AuthenticationProvider的实现类AbstractUserDetailsAuthenticationProvider:

public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
    
    
	
	//去除无关代码
	
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    
    
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {
    
    
            return 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 {
    
    
            	//获取指定的用户信息
            	//进入该方法,发现是由其子类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 {
    
    
            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);
        }

        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
    
    
            this.userCache.putUserInCache(user);
        }

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

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }
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);
        }
    }
}

2.5UserDetailsService

    UserDetailsService被DaoAuthenticationProvider通过用户登录时提交的用户名和密码用于获取自定义的用户名,密码,以及其他一下用于认证的属性。SpringSecurity提供了基于用于获取内存存储或者数据库存储自定义信息的UserDetailsService实现类。
    具体的自定义的过程将在下方介绍。

2.6AbstractUserDetailsAuthenticationProvider中authenticate返回值

    按理说到此已经知道自定义认证方法的怎么写了,但咱们把返回的流程也大概走一遍,上面不是说到返回了一个 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;
    }
} 

2.7UsernamePasswordAuthenticationToken

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

2.8AbstractAuthenticationToken

    再点进去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对象中一定要放置权限信息啊!

2.9AbstractAuthenticationProcessingFilter

    回到最初的起点,UsernamePasswordAuthenticationFilter使用的是父类的doFilter()方法进行的认证操作。点开AbstractAuthenticationProcessingFilter,删掉不必要的代码!

public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware {
    
    
    
    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 {
    
    
            	//此处使用的是子类UsernamePasswordAuthenticationFilter的方法
                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);
        }
    }
    
    //成功走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.1定义自己的UserService接口继承UserDetailsService

public interface UserService extends UserDetailsService {
    
    

}

public UserServiceImpl implements UserService{
    
    
	//编写loadUserByUsername业务 
	@Override 
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
      
		//调用持久层接口查询数据库中的用户信息
	    SysUser sysUser = userDao.findByName(username);    
	    if(sysUser==null){
    
            
	        //若用户名不对,直接返回null,表示认证失败。        
	        return null;    
	    } 
	    
	    List<SimpleGrantedAuthority> authorities = new ArrayList<>();    
	    //获取用户的权限
	    List<SysRole> roles = sysUser.getRoles();    
	    for (SysRole role : roles) {
    
            
	        authorities.add(new SimpleGrantedAuthority(role.getRoleName()));    
	    }    
    //最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证。    
    return new User(sysUser.getUsername(), "{noop}"+sysUser.getPassword(), authorities); 
	}
}

3.2在SpringSecurity主配置文件中指定认证使用的业务对象

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled =true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Bean
    public UserDetailsService userDetailsService() {
    
            
        return new UserServiceImpl();
    }
}    

四、加密认证

    SpringSecurity支持使用PasswordEncoder来安全的存储用户密码。可以通过定义一个PasswordEncoder类型的实现类并注入到容器当中,SpringSecurity将会获取该bean对象用于密码加密。
    前提条件:自定义的用户信息当中存储的用户密码必须是使用相应的PasswordEncoder进行加密后的密码

4.1在IOC容器中提供加密对象

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled =true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    

    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return new BCryptPasswordEncoder();
    }
}

源码分析:

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;

    public DaoAuthenticationProvider() {
    
    
        this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
    }

    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    
    
        if (authentication.getCredentials() == null) {
    
    
            this.logger.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
    
    
        	//此处,获取通过retrieveUser方法得到的自定义用户信息中的密码
            String presentedPassword = authentication.getCredentials().toString();
            //通过passwordEncoder判断用户登录时的密码与自定义用户信息中的密码是否一致
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
    
    
                this.logger.debug("Authentication failed: password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }
}

4.2修改认证方法

@Override 
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
        
    SysUser sysUser = userDao.findByName(username);    
    if(sysUser==null){
    
            
        //若用户名不对,直接返回null,表示认证失败。        
        return null;    
    }    
    List<SimpleGrantedAuthority> authorities = new ArrayList<>();    
    List<SysRole> roles = sysUser.getRoles();    
    for (SysRole role : roles) {
    
            
        authorities.add(new SimpleGrantedAuthority(role.getRoleName()));   
    }    
    //最终需要返回一个SpringSecurity的UserDetails对象,{noop}表示不加密认证。    
    return new User(sysUser.getUsername(), sysUser.getPassword(), authorities);
}

4.3修改添加用户的操作

@Service 
@Transactional 
public class UserServiceImpl implements UserService {
    
        
 
    @Autowired    
    private UserDao userDao;
 
    @Autowired    
    private BCryptPasswordEncoder passwordEncoder;    
    
    @Override    
    public void save(SysUser user) {
    
            
        //对密码进行加密,然后再入库
        user.setPassword(passwordEncoder.encode(user.getPassword()));        
        userDao.save(user);    
    }
    //…… 
}

总结

    Springecurity通过DelegatingFilterProxy使Security Filters最终与Servlet当中的Filters一同作用,用于完成用户的认证与授权操作。在这个过程中使用了代理模式和责任链模式等设计模式,在这里就不再做详细论述。本文旨在帮助初学者快速掌握SpringSecurity框架的主要对象和登录认证流程。文中不足之处,请多指点!!!!!!

猜你喜欢

转载自blog.csdn.net/zyk1111/article/details/112880018