91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Spring Security原理是什么

發布時間:2020-10-29 11:06:57 來源:億速云 閱讀:230 作者:小新 欄目:編程語言

這篇文章主要介紹Spring Security原理是什么,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

知彼知己方能百戰百勝,用 Spring Security 來滿足我們的需求最好了解其原理,這樣才能隨意拓展,本篇文章主要記錄 Spring Security 的基本運行流程。

過濾器

Spring Security 基本都是通過過濾器來完成配置的身份認證、權限認證以及登出。

Spring Security 在 Servlet 的過濾鏈(filter chain)中注冊了一個過濾器 FilterChainProxy,它會把請求代理到 Spring Security 自己維護的多個過濾鏈,每個過濾鏈會匹配一些 URL,如果匹配則執行對應的過濾器。過濾鏈是有順序的,一個請求只會執行第一條匹配的過濾鏈。Spring Security 的配置本質上就是新增、刪除、修改過濾器。

Spring Security原理是什么

默認情況下系統幫我們注入的這 15 個過濾器,分別對應配置不同的需求。接下來我們重點是分析下 UsernamePasswordAuthenticationFilter 這個過濾器,他是用來使用用戶名和密碼登錄認證的過濾器,但是很多情況下我們的登錄不止是簡單的用戶名和密碼,又可能是用到第三方授權登錄,這個時候我們就需要使用自定義過濾器,當然這里不做詳細說明,只是說下自定義過濾器怎么注入。

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    http.addFilterAfter(...);
    ...
 }

身份認證流程

在開始身份認證流程之前我們需要了解下幾個基本概念

  1. SecurityContextHolder

SecurityContextHolder 存儲 SecurityContext 對象。SecurityContextHolder 是一個存儲代理,有三種存儲模式分別是:

  • MODE_THREADLOCAL:SecurityContext 存儲在線程中。
  • MODE_INHERITABLETHREADLOCAL:SecurityContext 存儲在線程中,但子線程可以獲取到父線程中的 SecurityContext。
  • MODE_GLOBAL:SecurityContext 在所有線程中都相同。

SecurityContextHolder 默認使用 MODE_THREADLOCAL 模式,SecurityContext 存儲在當前線程中。調用 SecurityContextHolder 時不需要顯示的參數傳遞,在當前線程中可以直接獲取到 SecurityContextHolder 對象。

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

2.Authentication

Authentication 即驗證,表明當前用戶是誰。什么是驗證,比如一組用戶名和密碼就是驗證,當然錯誤的用戶名和密碼也是驗證,只不過 Spring Security 會校驗失敗。

Authentication 接口

public interface Authentication extends Principal, Serializable {
       //獲取用戶權限,一般情況下獲取到的是用戶的角色信息
       Collection<? extends GrantedAuthority> getAuthorities();
       //獲取證明用戶認證的信息,通常情況下獲取到的是密碼等信息,不過登錄成功就會被移除
       Object getCredentials();
       //獲取用戶的額外信息,比如 IP 地址、經緯度等
       Object getDetails();
       //獲取用戶身份信息,在未認證的情況下獲取到的是用戶名,在已認證的情況下獲取到的是 UserDetails (暫時理解為,當前應用用戶對象的擴展)
       Object getPrincipal();
       //獲取當前 Authentication 是否已認證
       boolean isAuthenticated();
       //設置當前 Authentication 是否已認證
       void setAuthenticated(boolean isAuthenticated);
}

3.AuthenticationManager ProviderManager AuthenticationProvider

其實這三者很好區分,AuthenticationManager 主要就是為了完成身份認證流程,ProviderManager 是 AuthenticationManager 接口的具體實現類,ProviderManager 里面有個記錄 AuthenticationProvider 對象的集合屬性 providers,AuthenticationProvider 接口類里有兩個方法

public interface AuthenticationProvider {
    //實現具體的身份認證邏輯,認證失敗拋出對應的異常
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
    //該認證類是否支持該 Authentication 的認證
    boolean supports(Class<?> authentication);
}

接下來就是遍歷 ProviderManager 里面的 providers 集合,找到和合適的 AuthenticationProvider 完成身份認證。

4.UserDetailsService UserDetails

在 UserDetailsService 接口中只有一個簡單的方法

public interface UserDetailsService {
    //根據用戶名查到對應的 UserDetails 對象
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

5.流程

對于上面概念有什么不明白的地方,在們在接下來的流程中慢慢分析

在運行到 UsernamePasswordAuthenticationFilter 過濾器的時候首先是進入其父類AbstractAuthenticationProcessingFilter的 doFilter() 方法中

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
        throws IOException, ServletException {
    ...
    //首先配對是不是配置的身份認證的URI,是則執行下面的認證,不是則跳過
    if (!requiresAuthentication(request, response)) {
        chain.doFilter(request, response);

        return;
    }
    ...
    Authentication authResult;

    try {
        //關鍵方法, 實現認證邏輯并返回 Authentication, 由其子類 UsernamePasswordAuthenticationFilter 實現, 由下面 5.3 詳解
        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) {
        //認證失敗調用...由下面 5.1 詳解
        unsuccessfulAuthentication(request, response, failed);

        return;
    }
    catch (AuthenticationException failed) {
        //認證失敗調用...由下面 5.1 詳解
        unsuccessfulAuthentication(request, response, failed);

        return;
    }

    // Authentication success
    if (continueChainBeforeSuccessfulAuthentication) {
        chain.doFilter(request, response);
    }
    //認證成功調用...由下面 5.2 詳解
    successfulAuthentication(request, response, chain, authResult);
}
5.1 認證失敗處理邏輯
protected void unsuccessfulAuthentication(HttpServletRequest request,
                                          HttpServletResponse response, AuthenticationException failed)
        throws IOException, ServletException {
    SecurityContextHolder.clearContext();
    ...
    rememberMeServices.loginFail(request, response);
    //該 handler 處理失敗界面跳轉和響應邏輯
    failureHandler.onAuthenticationFailure(request, response, failed);
}

這里默認配置的失敗處理 handler 是 SimpleUrlAuthenticationFailureHandler,可自定義。

public class SimpleUrlAuthenticationFailureHandler implements
        AuthenticationFailureHandler {
    ...

    public void onAuthenticationFailure(HttpServletRequest request,
            HttpServletResponse response, AuthenticationException exception)
            throws IOException, ServletException {
        //沒有配置失敗跳轉的URL則直接響應錯誤
        if (defaultFailureUrl == null) {
            logger.debug("No failure URL set, sending 401 Unauthorized error");

            response.sendError(HttpStatus.UNAUTHORIZED.value(),
                HttpStatus.UNAUTHORIZED.getReasonPhrase());
        }
        else {
            //否則
            //緩存異常
            saveException(request, exception);
            //根據配置的異常頁面是重定向還是轉發進行不同方式跳轉
            if (forwardToDestination) {
                logger.debug("Forwarding to " + defaultFailureUrl);

                request.getRequestDispatcher(defaultFailureUrl)
                        .forward(request, response);
            }
            else {
                logger.debug("Redirecting to " + defaultFailureUrl);
                redirectStrategy.sendRedirect(request, response, defaultFailureUrl);
            }
        }
    }
    //緩存異常,轉發則保存在request里面,重定向則保存在session里面
    protected final void saveException(HttpServletRequest request,
            AuthenticationException exception) {
        if (forwardToDestination) {
            request.setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);
        }
        else {
            HttpSession session = request.getSession(false);

            if (session != null || allowSessionCreation) {
                request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION,
                        exception);
            }
        }
    }
}

這里做下小拓展:用系統的錯誤處理handler,指定認證失敗跳轉的URL,在MVC里面對應的URL方法里面可以通過key從request或session里面拿到錯誤信息,反饋給前端

5.2 認證成功處理邏輯
protected void successfulAuthentication(HttpServletRequest request,
                                        HttpServletResponse response, FilterChain chain, Authentication authResult)
        throws IOException, ServletException {
    ...
    //這里要注意很重要,將認證完成返回的 Authentication 保存到線程對應的 `SecurityContext` 中
    SecurityContextHolder.getContext().setAuthentication(authResult);

    rememberMeServices.loginSuccess(request, response, authResult);

    // Fire event
    if (this.eventPublisher != null) {
        eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                authResult, this.getClass()));
    }
    //該 handler 就是為了完成頁面跳轉
    successHandler.onAuthenticationSuccess(request, response, authResult);
}

這里默認配置的成功處理 handler 是 SavedRequestAwareAuthenticationSuccessHandler,里面的代碼就不做具體展開了,反正是跳轉到指定的認證成功之后的界面,可自定義。

5.3 身份認證詳情
public class UsernamePasswordAuthenticationFilter extends
        AbstractAuthenticationProcessingFilter {
    ...
    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;

    ...
    //開始身份認證邏輯
    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();
        //先用前端提交過來的 username 和 password 封裝一個簡易的 AuthenticationToken
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
                username, password);

        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        //具體的認證邏輯還是交給 AuthenticationManager 對象的 authenticate(..) 方法完成,接著往下看
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

由源碼斷點跟蹤得知,最終解析是由 AuthenticationManager 接口實現類 ProviderManager 來完成

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
        InitializingBean {
    ...
    private List<AuthenticationProvider> providers = Collections.emptyList();
    ...
    
    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        ....
        //遍歷所有的 AuthenticationProvider, 找到合適的完成身份驗證
        for (AuthenticationProvider provider : getProviders()) {
            if (!provider.supports(toTest)) {
                continue;
            }
            ...
            try {
                //進行具體的身份驗證邏輯, 這里使用到的是 DaoAuthenticationProvider, 具體邏輯記著往下看
                result = provider.authenticate(authentication);

                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            catch 
            ...
        }
        ...
        throw lastException;
    }
}

DaoAuthenticationProvider 繼承自 AbstractUserDetailsAuthenticationProvider 實現了 AuthenticationProvider 接口

public abstract class AbstractUserDetailsAuthenticationProvider implements
        AuthenticationProvider, InitializingBean, MessageSourceAware {
    ...
    private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
    private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
    ...

    public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        ...
        // 獲得提交過來的用戶名
        String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
                : authentication.getName();
        //根據用戶名從緩存中查找 UserDetails
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);

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

            try {
                //緩存中沒有則通過 retrieveUser(..) 方法查找 (看下面 DaoAuthenticationProvider 的實現)
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch 
            ...
        }

        try {
            //比對前的檢查,例如賬戶以一些狀態信息(是否鎖定, 過期...)
            preAuthenticationChecks.check(user);
            //子類實現比對規則 (看下面 DaoAuthenticationProvider 的實現)
            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();
        }
        //根據最終user的一些信息重新生成具體詳細的 Authentication 對象并返回 
        return createSuccessAuthentication(principalToReturn, authentication, user);
    }
    //具體生成還是看子類實現
    protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
        // Ensure we return the original credentials the user supplied,
        // so subsequent attempts are successful even with encoded passwords.
        // Also ensure we return the original getDetails(), so that future
        // authentication events after cache expiry contain the details
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
                principal, authentication.getCredentials(),
                authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());

        return result;
    }
}

接下來我們來看下 DaoAuthenticationProvider 里面的三個重要的方法,比對方式、獲取需要比對的 UserDetails 對象以及生產最終返回 Authentication 的方法。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    ...
    //密碼比對
    @SuppressWarnings("deprecation")
    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();
        //通過 PasswordEncoder 進行密碼比對, 注: 可自定義
        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"));
        }
    }

    //通過 UserDetailsService 獲取 UserDetails
    protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            //通過 UserDetailsService 獲取 UserDetails
            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);
        }
    }

    //生成身份認證通過后最終返回的 Authentication, 記錄認證的身份信息
    @Override
    protected Authentication createSuccessAuthentication(Object principal,
            Authentication authentication, UserDetails user) {
        boolean upgradeEncoding = this.userDetailsPasswordService != null
                && this.passwordEncoder.upgradeEncoding(user.getPassword());
        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsPasswordService.updatePassword(user, newPassword);
        }
        return super.createSuccessAuthentication(principal, authentication, user);
    }
}

以上是Spring Security原理是什么的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

康保县| 科尔| 武宣县| 琼结县| 清水县| 闽清县| 饶阳县| 土默特右旗| 乌拉特前旗| 育儿| 南丰县| 吴江市| 齐河县| 离岛区| 兰州市| 青阳县| 漾濞| 昂仁县| 太仓市| 凉城县| 许昌市| 峡江县| 登封市| 怀柔区| 来安县| 保康县| 兴义市| 沅陵县| 林州市| 金门县| 辉南县| 洞头县| 黑河市| 满洲里市| 石林| 宁乡县| 榆树市| 马龙县| 林甸县| 凤冈县| 平山县|