image.png
有十几个过滤器

挑选三个讲解:

FilterSecurityInterceptor

image.png
源码:

  1. /**
  2. * Performs security handling of HTTP resources via a filter implementation.
  3. * <p>
  4. * The <code>SecurityMetadataSource</code> required by this security interceptor is of
  5. * type {@link FilterInvocationSecurityMetadataSource}.
  6. * <p>
  7. * Refer to {@link AbstractSecurityInterceptor} for details on the workflow.
  8. * </p>
  9. *
  10. * @author Ben Alex
  11. * @author Rob Winch
  12. */
  13. public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
  14. Filter {
  15. // ~ Static fields/initializers
  16. // =====================================================================================
  17. private static final String FILTER_APPLIED = "__spring_security_filterSecurityInterceptor_filterApplied";
  18. // ~ Instance fields
  19. // ================================================================================================
  20. private FilterInvocationSecurityMetadataSource securityMetadataSource;
  21. private boolean observeOncePerRequest = true;
  22. // ~ Methods
  23. // ========================================================================================================
  24. /**
  25. * Not used (we rely on IoC container lifecycle services instead)
  26. *
  27. * @param arg0 ignored
  28. *
  29. */
  30. public void init(FilterConfig arg0) {
  31. }
  32. /**
  33. * Not used (we rely on IoC container lifecycle services instead)
  34. */
  35. public void destroy() {
  36. }
  37. /**
  38. * Method that is actually called by the filter chain. Simply delegates to the
  39. * {@link #invoke(FilterInvocation)} method.
  40. *
  41. * @param request the servlet request
  42. * @param response the servlet response
  43. * @param chain the filter chain
  44. *
  45. * @throws IOException if the filter chain fails
  46. * @throws ServletException if the filter chain fails
  47. */
  48. public void doFilter(ServletRequest request, ServletResponse response,
  49. FilterChain chain) throws IOException, ServletException {
  50. FilterInvocation fi = new FilterInvocation(request, response, chain);
  51. invoke(fi);
  52. }
  53. public FilterInvocationSecurityMetadataSource getSecurityMetadataSource() {
  54. return this.securityMetadataSource;
  55. }
  56. public SecurityMetadataSource obtainSecurityMetadataSource() {
  57. return this.securityMetadataSource;
  58. }
  59. public void setSecurityMetadataSource(FilterInvocationSecurityMetadataSource newSource) {
  60. this.securityMetadataSource = newSource;
  61. }
  62. public Class<?> getSecureObjectClass() {
  63. return FilterInvocation.class;
  64. }
  65. public void invoke(FilterInvocation fi) throws IOException, ServletException {
  66. if ((fi.getRequest() != null)
  67. && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
  68. && observeOncePerRequest) {
  69. // filter already applied to this request and user wants us to observe
  70. // once-per-request handling, so don't re-do security checking
  71. fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  72. }
  73. else {
  74. // first time this request being called, so perform security checking
  75. if (fi.getRequest() != null && observeOncePerRequest) {
  76. fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
  77. }
  78. InterceptorStatusToken token = super.beforeInvocation(fi);
  79. try {
  80. fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
  81. }
  82. finally {
  83. super.finallyInvocation(token);
  84. }
  85. super.afterInvocation(token, null);
  86. }
  87. }
  88. /**
  89. * Indicates whether once-per-request handling will be observed. By default this is
  90. * <code>true</code>, meaning the <code>FilterSecurityInterceptor</code> will only
  91. * execute once-per-request. Sometimes users may wish it to execute more than once per
  92. * request, such as when JSP forwards are being used and filter security is desired on
  93. * each included fragment of the HTTP request.
  94. *
  95. * @return <code>true</code> (the default) if once-per-request is honoured, otherwise
  96. * <code>false</code> if <code>FilterSecurityInterceptor</code> will enforce
  97. * authorizations for each and every fragment of the HTTP request.
  98. */
  99. public boolean isObserveOncePerRequest() {
  100. return observeOncePerRequest;
  101. }
  102. public void setObserveOncePerRequest(boolean observeOncePerRequest) {
  103. this.observeOncePerRequest = observeOncePerRequest;
  104. }
  105. }

image.png

ExceptionTranslationFilter

image.png
源码:

public class ExceptionTranslationFilter extends GenericFilterBean {

    // ~ Instance fields
    // ================================================================================================

    private AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();
    private AuthenticationEntryPoint authenticationEntryPoint;
    private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl();
    private ThrowableAnalyzer throwableAnalyzer = new DefaultThrowableAnalyzer();

    private RequestCache requestCache = new HttpSessionRequestCache();

    private final MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint) {
        this(authenticationEntryPoint, new HttpSessionRequestCache());
    }

    public ExceptionTranslationFilter(AuthenticationEntryPoint authenticationEntryPoint,
            RequestCache requestCache) {
        Assert.notNull(authenticationEntryPoint,
                "authenticationEntryPoint cannot be null");
        Assert.notNull(requestCache, "requestCache cannot be null");
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.requestCache = requestCache;
    }

    // ~ Methods
    // ========================================================================================================

    @Override
    public void afterPropertiesSet() {
        Assert.notNull(authenticationEntryPoint,
                "authenticationEntryPoint must be specified");
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            chain.doFilter(request, response);

            logger.debug("Chain processed normally");
        }
        catch (IOException ex) {
            throw ex;
        }
        catch (Exception ex) {
            // Try to extract a SpringSecurityException from the stacktrace
            Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
            RuntimeException ase = (AuthenticationException) throwableAnalyzer
                    .getFirstThrowableOfType(AuthenticationException.class, causeChain);

            if (ase == null) {
                ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
                        AccessDeniedException.class, causeChain);
            }

            if (ase != null) {
                if (response.isCommitted()) {
                    throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
                }
                handleSpringSecurityException(request, response, chain, ase);
            }
            else {
                // Rethrow ServletExceptions and RuntimeExceptions as-is
                if (ex instanceof ServletException) {
                    throw (ServletException) ex;
                }
                else if (ex instanceof RuntimeException) {
                    throw (RuntimeException) ex;
                }

                // Wrap other Exceptions. This shouldn't actually happen
                // as we've already covered all the possibilities for doFilter
                throw new RuntimeException(ex);
            }
        }
    }

    public AuthenticationEntryPoint getAuthenticationEntryPoint() {
        return authenticationEntryPoint;
    }

    protected AuthenticationTrustResolver getAuthenticationTrustResolver() {
        return authenticationTrustResolver;
    }

    private void handleSpringSecurityException(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, RuntimeException exception)
            throws IOException, ServletException {
        if (exception instanceof AuthenticationException) {
            logger.debug(
                    "Authentication exception occurred; redirecting to authentication entry point",
                    exception);

            sendStartAuthentication(request, response, chain,
                    (AuthenticationException) exception);
        }
        else if (exception instanceof AccessDeniedException) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
                logger.debug(
                        "Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
                        exception);

                sendStartAuthentication(
                        request,
                        response,
                        chain,
                        new InsufficientAuthenticationException(
                            messages.getMessage(
                                "ExceptionTranslationFilter.insufficientAuthentication",
                                "Full authentication is required to access this resource")));
            }
            else {
                logger.debug(
                        "Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
                        exception);

                accessDeniedHandler.handle(request, response,
                        (AccessDeniedException) exception);
            }
        }
    }

    protected void sendStartAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain,
            AuthenticationException reason) throws ServletException, IOException {
        // SEC-112: Clear the SecurityContextHolder's Authentication, as the
        // existing Authentication is no longer considered valid
        SecurityContextHolder.getContext().setAuthentication(null);
        requestCache.saveRequest(request, response);
        logger.debug("Calling Authentication entry point.");
        authenticationEntryPoint.commence(request, response, reason);
    }

    public void setAccessDeniedHandler(AccessDeniedHandler accessDeniedHandler) {
        Assert.notNull(accessDeniedHandler, "AccessDeniedHandler required");
        this.accessDeniedHandler = accessDeniedHandler;
    }

    public void setAuthenticationTrustResolver(
            AuthenticationTrustResolver authenticationTrustResolver) {
        Assert.notNull(authenticationTrustResolver,
                "authenticationTrustResolver must not be null");
        this.authenticationTrustResolver = authenticationTrustResolver;
    }

    public void setThrowableAnalyzer(ThrowableAnalyzer throwableAnalyzer) {
        Assert.notNull(throwableAnalyzer, "throwableAnalyzer must not be null");
        this.throwableAnalyzer = throwableAnalyzer;
    }

    /**
     * Default implementation of <code>ThrowableAnalyzer</code> which is capable of also
     * unwrapping <code>ServletException</code>s.
     */
    private static final class DefaultThrowableAnalyzer extends ThrowableAnalyzer {
        /**
         * @see org.springframework.security.web.util.ThrowableAnalyzer#initExtractorMap()
         */
        protected void initExtractorMap() {
            super.initExtractorMap();

            registerExtractor(ServletException.class, throwable -> {
                ThrowableAnalyzer.verifyThrowableHierarchy(throwable,
                        ServletException.class);
                return ((ServletException) throwable).getRootCause();
            });
        }

    }

}

UsernamePasswordAuthenticationFilter

image.png
源码:

public class UsernamePasswordAuthenticationFilter extends
        AbstractAuthenticationProcessingFilter {
    // ~ Static fields/initializers
    // =====================================================================================

    public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
    public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

    private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
    private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
    private boolean postOnly = true;

    // ~ Constructors
    // ===================================================================================================

    public UsernamePasswordAuthenticationFilter() {
        super(new AntPathRequestMatcher("/login", "POST"));
    }

    // ~ Methods
    // ========================================================================================================

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

    /**
     * Enables subclasses to override the composition of the password, such as by
     * including additional values and a separator.
     * <p>
     * This might be used for example if a postcode/zipcode was required in addition to
     * the password. A delimiter such as a pipe (|) should be used to separate the
     * password and extended value(s). The <code>AuthenticationDao</code> will need to
     * generate the expected password in a corresponding manner.
     * </p>
     *
     * @param request so that request attributes can be retrieved
     *
     * @return the password that will be presented in the <code>Authentication</code>
     * request token to the <code>AuthenticationManager</code>
     */
    @Nullable
    protected String obtainPassword(HttpServletRequest request) {
        return request.getParameter(passwordParameter);
    }

    /**
     * Enables subclasses to override the composition of the username, such as by
     * including additional values and a separator.
     *
     * @param request so that request attributes can be retrieved
     *
     * @return the username that will be presented in the <code>Authentication</code>
     * request token to the <code>AuthenticationManager</code>
     */
    @Nullable
    protected String obtainUsername(HttpServletRequest request) {
        return request.getParameter(usernameParameter);
    }

    /**
     * Provided so that subclasses may configure what is put into the authentication
     * request's details property.
     *
     * @param request that an authentication request is being created for
     * @param authRequest the authentication request object that should have its details
     * set
     */
    protected void setDetails(HttpServletRequest request,
            UsernamePasswordAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    /**
     * Sets the parameter name which will be used to obtain the username from the login
     * request.
     *
     * @param usernameParameter the parameter name. Defaults to "username".
     */
    public void setUsernameParameter(String usernameParameter) {
        Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
        this.usernameParameter = usernameParameter;
    }

    /**
     * Sets the parameter name which will be used to obtain the password from the login
     * request..
     *
     * @param passwordParameter the parameter name. Defaults to "password".
     */
    public void setPasswordParameter(String passwordParameter) {
        Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
        this.passwordParameter = passwordParameter;
    }

    /**
     * Defines whether only HTTP POST requests will be allowed by this filter. If set to
     * true, and an authentication request is received which is not a POST request, an
     * exception will be raised immediately and authentication will not be attempted. The
     * <tt>unsuccessfulAuthentication()</tt> method will be called as if handling a failed
     * authentication.
     * <p>
     * Defaults to <tt>true</tt> but may be overridden by subclasses.
     */
    public void setPostOnly(boolean postOnly) {
        this.postOnly = postOnly;
    }

    public final String getUsernameParameter() {
        return usernameParameter;
    }

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

过滤器是如何加载的

image.png

DelegatingFilterProxy

image.png
image.png
image.png

WebSecurityConfiguration

image.png

FilterChainProxy

image.png
image.png
image.png
WebSecurityConfiguration下的
image.png

记住我流程

image.png
image.png