Heim  >  Artikel  >  Java  >  Einführung in die Spring Security-Prinzipien (mit Code)

Einführung in die Spring Security-Prinzipien (mit Code)

不言
不言nach vorne
2019-03-22 16:49:413296Durchsuche

Dieser Artikel bietet Ihnen eine Einführung in die Prinzipien von Spring Security (mit Code). Freunde in Not können sich darauf beziehen.

Nur wenn Sie Ihren Feind kennen und sich selbst kennen, können Sie jeden Kampf gewinnen. Um Spring Security zu nutzen, um unsere Bedürfnisse zu erfüllen, ist es am besten, seine Prinzipien zu verstehen, damit Sie nach Belieben erweitern können Betriebsprozess von Spring Security.

Filter

Spring Security verwendet grundsätzlich Filter, um die konfigurierte Identitätsauthentifizierung, Berechtigungsauthentifizierung und Abmeldung abzuschließen.

Spring Security registriert einen Filter FilterChainProxy in der Filterkette des Servlets, der die Anfrage an mehrere von Spring Security selbst verwaltete Filterketten weiterleitet. Wenn eine Übereinstimmung vorliegt, stimmt der entsprechende Filter überein wird ausgeführt. Die Filterketten sind sequentiell und nur die erste passende Filterkette wird für eine Anfrage ausgeführt. Die Konfiguration von Spring Security besteht im Wesentlichen aus dem Hinzufügen, Löschen und Ändern von Filtern.

Einführung in die Spring Security-Prinzipien (mit Code)

Standardmäßig entsprechen die 15 vom System für uns eingefügten Filter unterschiedlichen Konfigurationsanforderungen. Als Nächstes konzentrieren wir uns auf die Analyse des Filters „UsernamePasswordAuthenticationFilter“. Dabei handelt es sich um einen Filter, der für die Anmeldeauthentifizierung mithilfe von Benutzername und Kennwort verwendet wird. In vielen Fällen handelt es sich bei unserem Anmeldenamen jedoch nicht nur um einen einfachen Benutzernamen und ein Kennwort, sondern möglicherweise auch um eine autorisierte Anmeldung eines Drittanbieters . Zu diesem Zeitpunkt müssen wir einen benutzerdefinierten Filter verwenden. Natürlich werde ich hier nicht näher darauf eingehen, wie der benutzerdefinierte Filter eingefügt wird.

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

Identitätsauthentifizierungsprozess

Wir müssen einige grundlegende Konzepte verstehen, bevor wir mit dem Identitätsauthentifizierungsprozess beginnen

  1. SecurityContextHolder

SecurityContextHolder speichert SecurityContext-Objekte. SecurityContextHolder ist ein Speicheragent mit drei Speichermodi:

  • MODE_THREADLOCAL: SecurityContext wird im Thread gespeichert.
  • MODE_INHERITABLETHREADLOCAL: SecurityContext wird im Thread gespeichert, aber der untergeordnete Thread kann den SecurityContext im übergeordneten Thread erhalten.
  • MODE_GLOBAL: SecurityContext ist in allen Threads gleich.

SecurityContextHolder verwendet standardmäßig den MODE_THREADLOCAL-Modus und SecurityContext wird im aktuellen Thread gespeichert. Beim Aufruf von SecurityContextHolder müssen keine expliziten Parameter übergeben werden. Das SecurityContextHolder-Objekt kann direkt im aktuellen Thread abgerufen werden.

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

2.Authentifizierung

Authentifizierung ist eine Überprüfung, die angibt, wer der aktuelle Benutzer ist. Was ist eine Verifizierung? Beispielsweise ist ein Satz aus Benutzername und Passwort eine Verifizierung. Natürlich ist auch ein falscher Benutzername und ein falsches Passwort eine Verifizierung, aber Spring Security schlägt bei der Verifizierung fehl.

Authentifizierungsschnittstelle

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

Tatsächlich ist AuthenticationManager hauptsächlich für den Abschluss des Identitätsauthentifizierungsprozesses zuständig Implementierung der AuthenticationManager-Schnittstelle. ProviderManager verfügt über einen Sammlungsattributanbieter, der das AuthenticationProvider-Objekt aufzeichnet. Es gibt zwei Methoden in der AuthenticationProvider-Schnittstellenklasse Schließen Sie die Identitätsauthentifizierung mit dem entsprechenden AuthenticationProvider ab.

4.UserDetailsService UserDetails


Es gibt nur eine einfache Methode in der UserDetailsService-Schnittstelle

public interface AuthenticationProvider {
    //实现具体的身份认证逻辑,认证失败抛出对应的异常
    Authentication authenticate(Authentication authentication)
            throws AuthenticationException;
    //该认证类是否支持该 Authentication 的认证
    boolean supports(Class> authentication);
}

5. Prozess

Was ist daran falsch? Konzept? Um es zu verstehen, werden wir es im nächsten Prozess langsam analysieren.

Beim Ausführen des UsernamePasswordAuthenticationFilter-Filters geben Sie zunächst die doFilter()-Methode seiner übergeordneten Klasse AbstractAuthenticationProcessingFilter ein.

public interface UserDetailsService {
    //根据用户名查到对应的 UserDetails 对象
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

5.1 Authentifizierungsfehlerbehandlung Logik

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

Der hier konfigurierte Standard-Fehlerbehandlungshandler ist SimpleUrlAuthenticationFailureHandler, der angepasst werden kann.

protected void unsuccessfulAuthentication(HttpServletRequest request,
                                          HttpServletResponse response, AuthenticationException failed)
        throws IOException, ServletException {
    SecurityContextHolder.clearContext();
    ...
    rememberMeServices.loginFail(request, response);
    //该 handler 处理失败界面跳转和响应逻辑
    failureHandler.onAuthenticationFailure(request, response, failed);
}

Hier ist eine kleine Erweiterung: Verwenden Sie den Fehlerbehandlungshandler des Systems, um die URL anzugeben, zu der gesprungen werden soll, wenn die Authentifizierung fehlschlägt. In der entsprechenden URL-Methode in MVC können Sie die Fehlerinformationen und das Feedback von der Anfrage abrufen Sitzung über den Schlüssel. Für das Frontend

5.2 Authentifizierungs-Erfolgsverarbeitungslogik

rrree

Der hier konfigurierte Standard-Erfolgsverarbeitungshandler ist SavedRequestAwareAuthenticationSuccessHandler Springt nach Erfolg zur angegebenen Authentifizierung. Die Schnittstelle ist anpassbar.

5.3 Identitätsauthentifizierungsdetails

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

Aus der Quellcode-Haltepunktverfolgung ist bekannt, dass die endgültige Auflösung durch die AuthenticationManager-Schnittstellenimplementierungsklasse ProviderManager

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

Inherited abgeschlossen wird von

Implementierung der DaoAuthenticationProvider-Schnittstelle AbstractUserDetailsAuthenticationProvider

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

本篇文章到这里就已经全部结束了,更多其他精彩内容可以关注PHP中文网的Java教程视频栏目!

Das obige ist der detaillierte Inhalt vonEinführung in die Spring Security-Prinzipien (mit Code). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:segmentfault.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen