Spring Security源码探究

一,源码解读

先来两张官方的类图,熟悉下类之间关系


1,身份验证大流程

AbstractAuthenticationProcessingFilter. java

/**
处理基于浏览器交互的HTTP验证请求
也就是说AbstractAuthenticationProcessingFilter处理所有的HTTP Request和Response对象,并将其封装成 AuthenticationManager可以处理的Authentication(Token),
并且在身份验证成功或失败之后,返回response
默认是有 UsernamePasswordAuthenticationFilter实现的
*/
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;

        //判断拦截到的url是否需要进行权限鉴定,默认是除了/login外(一般设置/login什么权限的人都可以,具体在子类UsernamePasswordAuthenticationFilter中传入参数的)
        //不是/login就执行if中语句
        if (!requiresAuthentication(request, response)) {
            //进行下一个Filter(一般情况下一个就是FilterSecurityInterceptor,即权限鉴定)
            chain.doFilter(request, response);

            return;
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Request is to process authentication");
        }

        Authentication authResult;

        try {
            //UsernamePasswordAuthenticationFilter 的 attemptAuthentication 方法
            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. java

/**
登录的表单必须有username和password两个parameter,如常量定义的,但是也可以改变usernameParameter和passwordParameter的值
来改变传入的parameter字段的命名,但是没必要修改
另外过滤器只匹配/login以及POST请求为登录页为入口页,
*/
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 其中 authenticated 属性初始化默认为false(也就还没通过身份验证)
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);

        //获取session如果过存在session则否返回当前session,如果不存在则返回null,然后把这些信息set到Token的details属性值中
        setDetails(request, authRequest);

        //获取到ProviderManager并调用它身份验证方法
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

ProviderManager. java

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {

    //身份验证
    //Authentication 就是各种Token
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();

        for (AuthenticationProvider provider : getProviders()) {
            //通过各种provider,看有没有哪个provider支持验证这个Token
            if (!provider.supports(toTest)) {
                continue;
            }

            if (debug) {
                logger.debug("Authentication attempt using "
                        + provider.getClass().getName());
            }

            try {
                //最后是由 AbstractUserDetailsAuthenticationProvider 的 authenticate 方法执行的
                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还是为null那么继续让parent进行执行
                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;
    }
        
}

AbstractUserDetailsAuthenticationProvider. java

/**
看名字就知道这是提供对 UserDetails 进行解析然后身份验证的一个类
*/
public abstract class AbstractUserDetailsAuthenticationProvider implements
        AuthenticationProvider, InitializingBean, MessageSourceAware {
    
    public 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信息的
        UserDetails user = this.userCache.getUserFromCache(username);

        if (user == null) {
            cacheWasUsed = false;

            try {
                //缓存中拿不到就从数据库中获取UserDetails信息 默认实现是 DaoAuthenticationProvider 的 retrieveUser方法
                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 {
            //检查User的各种状态, 用户过期, 密码过期等
            preAuthenticationChecks.check(user);
            //密码匹配校验, 调用加密类 PasswordEncoder (可以自己定义)
            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) {
            //将UserDetails放入缓存
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;

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

        //将用户所有的所有合法角色放入Token 中的authorities中 并且 authenticated 设置为true 表示验证通过了
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
}

DaoAuthenticationProvider. java

/**
很简单就是提供 利用数据库进行身份验证
*/
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    
    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            
            //重要方法 调用自定义 UserDetailsService 的 loadUserByUsername 方法
            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);
        }
    }
    
}

UserService. java

@Service
public class UserService implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUserName(username);
        if (user == null) {
            throw new UsernameNotFoundException("账户不存在");
        }
        List<Role> roles = userMapper.getUserRolesByUid(user.getId());
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        user.setAuthorities(authorities);
        return user;
    }
}

LoginUrlAuthenticationEntryPoint. java

/**
如果全权限不足,会抛出异常,然后被ExceptionTranslationFilter中catch住
try {
            chain.doFilter(request, response);

            logger.debug("Chain processed normally");
        }
        像这样,会catch住整个chain上抛出的异常,然后如果是AccessDeniedException(我们定义的鉴定方法就是抛出这个异常)的话,
        就会调用如下方法进行请求重定向到/login
*/
public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint,
        InitializingBean {
        
    /**
     * Performs the redirect (or forward) to the login form URL.
     */
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {

        String redirectUrl = null;

        //默认为false所以不是转发
        if (useForward) {

            if (forceHttps && "http".equals(request.getScheme())) {
                // First redirect the current request to HTTPS.
                // When that request is received, the forward to the login page will be
                // used.
                redirectUrl = buildHttpsRedirectUrlForRequest(request);
            }

            if (redirectUrl == null) {
                String loginForm = determineUrlToUseForThisRequest(request, response,
                        authException);

                if (logger.isDebugEnabled()) {
                    logger.debug("Server side forward to: " + loginForm);
                }

                RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);

                dispatcher.forward(request, response);

                return;
            }
        }
        //重定向
        else {
            // 构建一个重定向链接到login页

            redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);

        }

        //最终还是使用 response.sendRedirect(redirectUrl);
        redirectStrategy.sendRedirect(request, response, redirectUrl);
    }   
}

2,权限鉴定大流程

FilterSecurityInterceptor. java

/**
本文使用的FilterSecurityInterceptor类型是针对某个请求的层级进行拦截和安全检查,是比较常用的
还有支持方法层级的,AspectJ层级的(更细的方法层级)
继承自AbstractSecurityInterceptor详细见父类
*/
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {
    
    //权限鉴定入口,由filter链进行调用
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(request, response, chain);
        //调用开始
        invoke(fi);
    }
    
    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        
        //fi.getRequest()一定不为null,observeOncePerRequest默认为true
        //getAttribute(FILTER_APPLIED)第一次进来没有值
        if ((fi.getRequest() != null)
                && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
                && observeOncePerRequest) {
            //进来这里表示已经处理过一次请求了,不需要重新做安全检查
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        }
        else {
            //进到这里表示第一次请求,需要进行安全检查
            if (fi.getRequest() != null && observeOncePerRequest) {
                //将FILTER_APPLIED标识放入request中
                fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
            }

            InterceptorStatusToken token = super.beforeInvocation(fi);

            try {
                fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
            }
            finally {
                super.finallyInvocation(token);
            }

            super.afterInvocation(token, null);
        }
    }
}

FilterInvocation. java

/**
保存与HTTP过滤器关联的对象
保证请求对象和响应对象是HttpServletRequest的实例和HttpServletResponse的实例,并且没有null对象
以上就是为了security框架内的类方便访问这些对象而不用每次都判断一下,转化一下
*/
public class FilterInvocation {
    
    
    public FilterInvocation(ServletRequest request, ServletResponse response,
            FilterChain chain) {
        //保证获取到非null的request和response
        if ((request == null) || (response == null) || (chain == null)) {
            throw new IllegalArgumentException("Cannot pass null values to constructor");
        }

        this.request = (HttpServletRequest) request;
        this.response = (HttpServletResponse) response;
        this.chain = chain;
    }
}

AbstractSecurityInterceptor. java

/**
抽象安全拦截器,确保安全拦截器的正确启动配置
它还将实现安全对象调用的正确处理,即:SecurityContextHolder中保存的Authentication对象
确定该请求是否与SecurityMetadataSource中配置的参数有关
如果这个请求是符合安全的那么,这里有一个ConfigAttribute列表给安全对象进行匹配:
如果org.springframework.security.core.Authentication#isAuthenticated()返回false,或者
#alwaysReauthenticate是true,根据配置的AuthenticationManager对请求进行身份验证
然后还会调用AccessDecisionManager进行权限鉴定
*/
public abstract class AbstractSecurityInterceptor implements InitializingBean,ApplicationEventPublisherAware, MessageSourceAware {

//object为FilterInvocation
    protected InterceptorStatusToken beforeInvocation(Object object) {
        /*省略次要代码*/

        //在此处获取ConfigAttribute集合,是通过调用SecurityMetadataSource的getAttributes方法获取的,
        //本文使用的是自定义的FilterInvocationSecurityMetadataSource
        Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);

        //第一次进来会会获取到AnonymousAuthenticationToken,他是在AnonymousAuthenticationFilter中初始化的,也就是匿名请求
        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            credentialsNotFound(messages.getMessage(
                    "AbstractSecurityInterceptor.authenticationNotFound",
                    "An Authentication object was not found in the SecurityContext"),
                    object, attributes);
        }

        //判断是否检查当前身份,验证令牌,并返回Authentication对象
        //第一次进去不符合条件直接返回匿名Token对象
        Authentication authenticated = authenticateIfRequired();

        //尝试进行授权
        try {
            //真正进行鉴定权限的地方通过的方法他是在AccessDecisionManager中的,本文使用的是它的自定义类
            //第一次进来是匿名Token对象,角色也是"ROLE_anonymous"没有一定会抛异常
            this.accessDecisionManager.decide(authenticated, object, attributes);
        }
        catch (AccessDeniedException accessDeniedException) {
            publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
                    accessDeniedException));

            throw accessDeniedException;
        }

        if (debug) {
            logger.debug("Authorization successful");
        }

        if (publishAuthorizationSuccess) {
            publishEvent(new AuthorizedEvent(object, attributes, authenticated));
        }

        // Attempt to run as a different user
        Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
                attributes);

        if (runAs == null) {
            if (debug) {
                logger.debug("RunAsManager did not change Authentication object");
            }

            // no further work post-invocation
            return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
                    attributes, object);
        }
        else {
            if (debug) {
                logger.debug("Switching to RunAs Authentication: " + runAs);
            }

            SecurityContext origCtx = SecurityContextHolder.getContext();
            SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
            SecurityContextHolder.getContext().setAuthentication(runAs);

            // need to revert to token.Authenticated post-invocation
            return new InterceptorStatusToken(origCtx, true, attributes, object);
        }
    }

/**
    主要就是身份验证,如果权限都不够那么也就不用身份认证了,直接返回
    如果org.springframework.security.core.Authentication#isAuthenticated()方法返回false,
    或者alwaysReauthenticate已经被设置为true,则检查当前的身份验证令牌并将其传递给AuthenticationManager
    */
    private Authentication authenticateIfRequired() {
        Authentication authentication = SecurityContextHolder.getContext()
                .getAuthentication();

        if (authentication.isAuthenticated() && !alwaysReauthenticate) {
            if (logger.isDebugEnabled()) {
                logger.debug("Previously Authenticated: " + authentication);
            }

            return authentication;
        }

        authentication = authenticationManager.authenticate(authentication);

        // We don't authenticated.setAuthentication(true), because each provider should do
        // that
        if (logger.isDebugEnabled()) {
            logger.debug("Successfully Authenticated: " + authentication);
        }

        SecurityContextHolder.getContext().setAuthentication(authentication);

        return authentication;
    }
}

CustomFilterInvocationSecurityMetadataSource. java

/**
 * 自定义认证数据源
 * 
 *
 * @author: baifan
 * @date: 2020/9/23
 */
@Service
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    //ant风格的URL匹配
    AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Autowired
    MenuMapper menuMapper;

    /**
     * @param object  一个FilterInvocation
     * @return  Collection<ConfigAttribute> 当前请求URL所需的角色
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //从FilterInvocation中获取当前请求的URL
        String requestUrl = ((FilterInvocation) object).getRequestUrl();
        //从数据库中获取所有的资源(角色和menu都查询)信息,可以缓存
        List<Menu> allMenus = menuMapper.getAllMenus();
        //遍历获取当前请求的URL所需要的角色信息
        for (Menu menu : allMenus) {
            if (antPathMatcher.match(menu.getPattern(), requestUrl)) {
                List<Role> roles = menu.getRoles();
                String[] roleArr = new String[roles.size()];
                for (int i = 0; i < roleArr.length; i++) {
                    roleArr[i] = roles.get(i).getName();
                }
                return SecurityConfig.createList(roleArr);
            }
        }
        //假设不存在URL对应的角色,则登录后即可访问
        return SecurityConfig.createList("ROLE_LOGIN");
    }

    //获取所有定义好的权限资源
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    //返回类对象是否支持校验
    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

ConfigAttribute. java

/**
存储与安全系统有关的配置属性
当一个AbstractSecurityInterceptor建立后,这里就有一个安全对象的样式(本文设定这个样式是角色名)的list
这个list对于RunAsManager和AccessDecisionManager以及AccessDecisionManager的继承类有特殊的意义,
对于AccessDecisionManager可以用这个list进行决定访问的对象是否符合安全样式
本文就是自定义了AccessDecisionManager

ConfigAttribute为接口, 本文具体实现使用的SecurityConfig
*/
public interface ConfigAttribute extends Serializable {
    String getAttribute();
}

SecurityConfig. java

/**
保存的都是String数据类型的ConfigAttribute
*/
public class SecurityConfig implements ConfigAttribute {
    private final String attrib;

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

    public SecurityConfig(String config) {
        Assert.hasText(config, "You must provide a configuration attribute");
        this.attrib = config;
    }
}

CustomAccessDecisionManager. java

/**
 * 当一个请求走完FilterInvocationSecurityMetadataSource中的getAttributes方法后
 * 就会到AccessDecisionManager中进行角色信息的对比
 *
 * @author: baifan
 * @date: 2020/9/23
 */
@Service
public class CustomAccessDecisionManager implements AccessDecisionManager {


    /**
     * 在该方法中判断当前登录的用户是否具备当前请求URL所需要的角色信息
     * 如果不具备,就抛出AccessDeniedException异常
     * @param authentication  包含当前登录用户的信息
     * @param object    FilterInvocation
     * @param configAttributes    FilterInvocationSecurityMetadataSource中的getAttributes方法返回值
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        for (ConfigAttribute configAttribute : configAttributes) {
            if ("ROLE_LOGIN".equals(configAttribute.getAttribute()) && authentication instanceof UsernamePasswordAuthenticationToken/*说明已登录*/) {
                return;
            }
            for (GrantedAuthority authority : authorities) {
                if (configAttribute.getAttribute().equals(authority.getAuthority())) {
                    return;
                }
            }
        }
        throw new AccessDeniedException("权限不足");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

二,执行流程

第一种情况:未登录,第一次访问/login

1,请求被AbstractAuthenticationProcessingFilter拦截执行doFilter方法

2,doFilter方法中requiresAuthentication方法判断请求不符合POST/login请求

3,进入FilterChain中下一个FilterDefaultLoginPageGeneratingFilter

4,执行DefaultLoginPageGeneratingFilter的doFilter方法,它会将来自/login/login?error/login?logout这些跳转链接统一重定向到自己生成的登录页并且执行了return即不在执行后续的Filter了

所以默认情况下看到的登录页,全都是代码硬编码写死的,不好用也不可取,只要在WebSecurityConfig中配置了loginPage就可以自定义登录页面了(使用前后部分里),很鸡肋的登录页啊!

第二种情况:未登录, 直接访问其它链接如 /db

由于在自定义的 WebSecurityConfig中没有设置 /db可以允许任何人访问

1,请求被 AbstractAuthenticationProcessingFilter 拦截 执行 doFilter 方法

2,doFilter 方法中 requiresAuthentication 方法判断请求不符合 POST /login 请求

3,进入FilterChain中下一个Filter DefaultLoginPageGeneratingFilter

4,执行DefaultLoginPageGeneratingFilter的 doFilter 方法由于 不符合 /login /login?error /login?logout 路径所以不会立刻反回登录页

5,进入FilterChain中下一个Filter RequestCacheAwareFilter

6,RequestCacheAwareFilter 中获取session中有没有SPRING_SECURITY_SAVED_REQUEST缓存 并且将获取到的缓存传入下一个Filter SecurityContextHolderAwareRequestFilter

7,SecurityContextHolderAwareRequestFilter 其中定义了 private String rolePrefix = "ROLE_"; 并且将请求转化为 HttpServletRequest 继续下一个Filter AnonymousAuthenticationFilter

8,AnonymousAuthenticationFilter 生成了一个AnonymousToken(与UserNameToken对比)包含匿名的验证对象 继续下一个Filter AbstractSecurityInterceptor

9,不管生成怎样的Token都要 在AbstractSecurityInterceptor 和它的子类 FilterSecurityInterceptor 中进行权限鉴定 主要做了如下事情
(1)调用 FilterInvocationSecurityMetadataSource (本文为自定义子类) 中的 getAttributes方法 根据 url获取匹配的角色列表,并且转换为 ConfigAttribute 列表
(2)调用 AccessDecisionManager (本文为自定义子类) 中的 decide 方法判断是否通过权限鉴定 很显然本文没有任何匿名角色即ROLE_ANONYMOUS的配置,所有没有通过 抛出了AccessDeniedException

10,经过或许Filter处理,回到登录页,至此结束

第三种情况比较复杂用流程图表示

附录

spring-security中的过滤器

1,OncePerRequestFilter(通用): Spring中默认所有Filter最前面的,作用是兼容各种请求,保证每次执行一个Filter

2,WebAsyncManagerIntegrationFilter(通用): Web异步管理集成过滤器,作用是集成SecurityContext到Spring异步执行机制中的WebAsyncManager

3,SecurityContextPersistenceFilter: 先从session查找有没有securityContext,没有的创建一个并且放入到SecurityContextHolder中

4,HeaderWriterFilter: 在请求前后向往前请求头或者响应头写入一些信息

5,LogoutFilter: 登出过滤器

6,AbstractAuthenticationProcessingFilter(起重要作用): 处理所有基于HTTP请求form登录的过滤器, 身份认证获取Token

7,DefaultLoginPageGeneratingFilter: 是否跳转默认登录页Filter

8,RequestCacheAwareFilter

9,SecurityContextHolderAwareRequestFilter

10,AnonymousAuthenticationFilter: 处理匿名用户访问
11,SessionManagementFilter

12,ExceptionTranslationFilter: security系统异常处理

13,AbstractSecurityInterceptor(FilterSecurityInterceptor起重要作用): 根据Token进行权限鉴定

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351