天天看點

(一)Spring Security 簡單原理Spring Security 原理:

Spring Security 原理:

Spring Security架構中有很多攔截器,這裡就不一一贅述,後面文章會較長的描述,現在來看看最核心的幾個。

首先來看看Spring Security 架構工作流程圖

(一)Spring Security 簡單原理Spring Security 原理:

Spring Security 核心流程分為使用者認證和授權管理兩部分。

使用者認證

用戶端發出登入請求,被AuthenticationProcessingFilter攔截器攔截,先判斷通路的url是否需要權限驗證,若不需要,則放行,繼續調用下一個攔截器,若需要權限,則調用AuthenticationManager的實作

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

        // Authentication success
        if (continueChainBeforeSuccessfulAuthentication) {
            chain.doFilter(request, response);
        }

        successfulAuthentication(request, response, chain, authResult);
    }

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

AuthenticationManager調用ProviderManager來擷取使用者的認證資訊,ProviderManager存在多種不同的provider,因為使用者的認證資訊可以在xml配置檔案上,可以在LADP伺服器上,可以在資料庫上。

驗證通過後,将使用者的認證資訊封裝成UserDetails對象放在全局緩存SecuriyContextHolder中,以備後面通路資源時使用。

protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {

        if (logger.isDebugEnabled()) {
            logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                    + authResult);
        }

        SecurityContextHolder.getContext().setAuthentication(authResult);

        rememberMeServices.loginSuccess(request, response, authResult);

        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }

        successHandler.onAuthenticationSuccess(request, response, authResult);
    }
           

授權管理

通路url時,會通過AbstractSecurityInterceptor攔截器攔截,這時會調用FilterInvocationSecurityMetadataSource的方法來擷取該url所需要的全部的權限

Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
                .getAttributes(object);
           

再調用授權管理器AccessDecisionManager,并傳遞權限清單

this.accessDecisionManager.decide(authenticated, object, attributes);
           

AccessDecisionManager到全局緩存SecurityContextHolder中拿到使用者認證資訊

Authentication authentication = SecurityContextHolder.getContext()
                .getAuthentication();
           

再根據AccessDecisionManager的授權對策(一票決定、一票否定、少數服從多數)進行判斷,如果權限足夠,則傳回,否則抛出權限不足異常,調用權限不足頁面。

try {
                returnedObject = afterInvocationManager.decide(token.getSecurityContext()
                        .getAuthentication(), token.getSecureObject(), token
                        .getAttributes(), returnedObject);
            }
            catch (AccessDeniedException accessDeniedException) {
                AuthorizationFailureEvent event = new AuthorizationFailureEvent(
                        token.getSecureObject(), token.getAttributes(), token
                                .getSecurityContext().getAuthentication(),
                        accessDeniedException);
                publishEvent(event);

                throw accessDeniedException;
            }