春のセキュリティ解析(C) - カスタマイズ達成認証とリメンバー・ミー

春のセキュリティ解析(C) - カスタマイズ達成認証とリメンバー・ミー

  春の雲を学習すると、理論とデザインを学び、常に乏しい、それは最初の春のセキュリティ、春のセキュリティのOAuth2と認定コンテンツに関連する他の権利に決まったのOAuth関連コンテンツ認証サービスを、会って、もう一度それを整理します。記事のこのシリーズは、侵害は、ご連絡ください場合は、学習と理解の過程で記述されたような印象を強化することです。

プロジェクト環境:

  • JDK1.8
  • 春のブート2.xの
  • 春のセキュリティの5.x

パーソナライズされた証明書

(A)の構成ログイン

   承認プロセスおよび認証プロセスでは、我々は、デフォルトのログイン・ページ(/ログイン)のセキュリティを使用しているので、我々はそれを達成するためにどのようにログインページをカスタマイズしたい場合は?実は、私たちはFormAuthenticationConfigの設定クラスを非常に簡単な作成し、以下の設定を構成する(HttpSecurity HTTP)メソッドを実装します。

        http.formLogin()
                //可以设置自定义的登录页面 或者 (登录)接口
                // 注意1: 一般来说设置成(登录)接口后,该接口会配置成无权限即可访问,所以会走匿名filter, 也就意味着不会走认证过程了,所以我们一般不直接设置成接口地址
                // 注意2: 这里配置的 地址一定要配置成无权限访问,否则将出现 一直重定向问题(因为无权限后又会重定向到这里配置的登录页url)
                .loginPage(securityProperties.getLogin().getLoginPage())
                //.loginPage("/loginRequire")
                // 指定验证凭据的URL(默认为 /login) ,
                // 注意1:这里修改后的 url 会意味着  UsernamePasswordAuthenticationFilter 将 验证此处的 url
                // 注意2: 与 loginPage设置的接口地址是有 区别, 一但 loginPage 设置了的是访问接口url,那么此处配置将无任何意义
                // 注意3: 这里设置的 Url 是有默认无权限访问的
                .loginProcessingUrl(securityProperties.getLogin().getLoginUrl())
                //分别设置成功和失败的处理器
                .successHandler(customAuthenticationSuccessHandler)
                .failureHandler(customAuthenticationFailureHandler);

  最後には、configureのSpringSecurityConfig(HttpSecurity HTTP)メソッドにformAuthenticationConfig.configure(HTTP)を呼び出します。

   あなたは)私たちはloginPage(によって設定され、見ることができるように、ログインページまたはインタフェースを、インタフェースアドレスが一致するUsernamePasswordAuthenticationFilter()loginProcessingUrl(でポストでなければなりません)(承認プロセスを読んで、生徒が知っておくべきdefault / loginのことです) 。ここでは、以下の点に注意する必要があります。

  • ここで設定loginPage()のアドレス(URLインタフェースまたはログインページのどちらかが)そうでない場合はそこここには権限がないため、問題が(リダイレクトされたされた後、ログイン設定ページのURLにリダイレクトされます、権限なしでアクセスするように設定する必要があります
  • loginPage()インタフェースがアクセスする権限を設定しないように構成されますので、直接(もちろん、ログインページへのアクセスを設定する必要が設​​定されていない)(ログイン)インターフェイスに設定されていない一般的に、それは匿名フィルタを行く、また手段認証プロセスは、前方に行くことはありませんので、我々は、一般的にインターフェースに直接対応していません
  • ここで変更loginProcessingUrl()urlはここ検証URL UsernamePasswordAuthenticationFilter意味するだろう
  • デフォルトへのアクセスがないここで設定loginProcessingUrl()URL、およびインターフェイスアドレスのloginPageセットは異なりますが、インタフェースのセットは、URLがloginPageで、構成はここでは意味を持ちません
  • successHandler()とfailureHandlerは(これら二つのプロセッサがない印象ならば、それは承認審査プロセスを推奨します)、認証が成功したプロセッサおよびプロセッサ認証失敗で設定されています

(B)プロセッサ構成の成功と失敗を

   承認プロセスの間に、我々はこれら二つのプロセッサの簡単な言及を追加する必要がありました、セキュリティのデフォルトのプロセッサがSavedRequestAwareAuthenticationSuccessHandlerとSimpleUrlAuthenticationFailureHandler、我々はこれら二つのプロセッサをカスタマイズし、この時、それぞれCustomAuthenticationSuccessHandler(SavedRequestAwareAuthenticationSuccessHandlerを拡張)重量ですonAuthenticationSuccess()メソッドを記述します。

@Component("customAuthenticationSuccessHandler")
@Slf4j
public class CustomAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Autowired
    private SecurityProperties securityProperties;

    private RequestCache requestCache = new HttpSessionRequestCache();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功");
        // 如果设置了loginSuccessUrl,总是跳到设置的地址上
        // 如果没设置,则尝试跳转到登录之前访问的地址上,如果登录前访问地址为空,则跳到网站根路径上
        if (!StringUtils.isEmpty(securityProperties.getLogin().getLoginSuccessUrl())) {
            requestCache.removeRequest(request, response);
            setAlwaysUseDefaultTargetUrl(true);
            setDefaultTargetUrl(securityProperties.getLogin().getLoginSuccessUrl());
        }
        super.onAuthenticationSuccess(request, response, authentication);
    }

}

和CustomAuthenticationFailureHandler(SimpleUrlAuthenticationFailureHandlerを拡張)重写onAuthenticationFailure()方法:

@Component("customAuthenticationFailureHandler")
@Slf4j
public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private SecurityProperties securityProperties;

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException exception) throws IOException {

        logger.info("登录失败");
        if (StringUtils.isEmpty(securityProperties.getLogin().getLoginErrorUrl())){

            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(exception.getMessage()));

        } else {
            // 跳转设置的登陆失败页面
            redirectStrategy.sendRedirect(request,response,securityProperties.getLogin().getLoginErrorUrl());
        }

    }
}

(C)カスタムランディングページ

ここでは記載していない、直接コードに添付:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h2>登录页面</h2>
<form action="/loginUp" method="post">  
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan='2'><input name="remember-me" type="checkbox" value="true"/>记住我</td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">登录</button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

  要求されたアドレスがloginProcessingUrl()設定されたアドレスであることに注意してください

(D)検証試験

  ここに掲載結果を考慮している限り、我々はあなたができるこのようなライン上のプロセスの結果を理解し、ではありません:
localhostを:8080 - >私たちのカスタムログインページ/loginUp.htmlに行く---->セキュリティアクセス制御を確認するためにテストするためにクリックしてください、loginSuccessUrlが配置され、次いでloginSuccess.htmlにジャンプ---->ログインし、そうでなければ結果を返す/ GET_USER /テストインタフェースにジャンプ。完全に私たちのカスタム、カスタムログオンの成功/失敗プロセッサへのログインページ全体のプロセスに関与します。

二、リメンバー・ミー(私を忘れないでください)機能解析

(A)の構成のためのリメンバー・ミー機能を

首先我们一股脑的将rememberMe配置加上,然后看下现象:

1.ユーザーのトークンと関連付けられた情報を格納するためpersistent_loginsテーブルを作成します。

create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null);

2、設定情報を追加するリメンバー・ミー

    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        // 如果token表不存在,使用下面语句可以初始化 persistent_logins(ddl在db目录下) 表;若存在,请注释掉这条语句,否则会报错。
        //tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }
    
     @Override
    protected void configure(HttpSecurity http) throws Exception {

        formAuthenticationConfig.configure(http);
        http.   ....
                .and()
                // 开启 记住我功能,意味着 RememberMeAuthenticationFilter 将会 从Cookie 中获取token信息
                .rememberMe()
                // 设置 tokenRepository ,这里默认使用 jdbcTokenRepositoryImpl,意味着我们将从数据库中读取token所代表的用户信息
                .tokenRepository(persistentTokenRepository())
                // 设置  userDetailsService , 和 认证过程的一样,RememberMe 有专门的 RememberMeAuthenticationProvider ,也就意味着需要 使用UserDetailsService 加载 UserDetails 信息
                .userDetailsService(userDetailsService)
                // 设置 rememberMe 的有效时间,这里通过 配置来设置
                .tokenValiditySeconds(securityProperties.getLogin().getRememberMeSeconds())
                .and()
                .csrf().disable(); // 关闭csrf 跨站(域)攻击防控
    }

ここでの設定を説明します。

  • リメンバー・ミー()関数は、私を忘れないでくださいになっている、それはRememberMeAuthenticationFilterはクッキーからのトークン情報を取得することを意味します
  • tokenRepository()取得ポリシーのトークン構成は、ここでデータベースから読み出すように構成します
  • userDetailsS​​ervice()の設定UserDetaisService(あなたが主題に精通していない場合、認証プロセスの提案レビュー)
  • 効果的な時間tokenValiditySecondsは、()ここでは、リメンバー・ミーを提供構成することによって提供されます

もう一つの重要な構成ログインページ、名前がなければならない=「覚えて-私を」、リメンバー・ミーremermberMe機能は、コンフィギュレーションを検証することで開くことです。

<input name="remember-me" type="checkbox" value="true"/>记住我</td>

  実用的な演算結果は次のようになります。ログインページを入力- >ログインチェックの後、私を忘れないでください- >成功したチェックpersistent_loginsは、データテーブルを有することが判明した後- >再起動プロジェクト- >再訪ページにアクセスするにはログインする必要があり、い伐採せずにアクセスで- >、persistent_loginsデータテーブルを削除する有効なトークン設定時間が経過するのを待っているランディングページを見つけるために、ページジャンプを更新した後、。

(B)RembemberMe実装ソースコード解析

   まず、ソース内部successfulAuthentication()メソッドのUsernamePasswordAuthenticationFiler(AbstractAuthenticationProcessingFilter)を参照してください。

protected void successfulAuthentication(HttpServletRequest request,
            HttpServletResponse response, FilterChain chain, Authentication authResult)
            throws IOException, ServletException {
        
        // 1 设置 认证成功的Authentication对象到SecurityContext中
        SecurityContextHolder.getContext().setAuthentication(authResult);
        
        // 2 调用 RememberMe 相关service处理
        rememberMeServices.loginSuccess(request, response, authResult);

        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }
        //3 调用成功处理器
        successHandler.onAuthenticationSuccess(request, response, authResult);
    }

これでは、コードの焦点のこの行で自分自身を見つける:rememberMeServices.loginSuccess(要求、応答、authResult)を、このメソッド内のソースコードを表示します。

@Override
    public final void loginSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication successfulAuthentication) {
        // 这里就在判断用户是否勾选了记住我
        if (!rememberMeRequested(request, parameter)) {
            logger.debug("Remember-me login not requested.");
            return;
        }

        onLoginSuccess(request, response, successfulAuthentication);
    }

rememberMeRequested()によって確認されたが、私を覚えているかどうかを決定します。
onLoginSuccess()メソッドは、最終的に呼び出しPersistentTokenBasedRememberMeServices onLoginSuccess()メソッド、次のようにソースを置く方法を:

protected void onLoginSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication successfulAuthentication) {
        // 1 获取账户名
        String username = successfulAuthentication.getName();
        
        // 2 创建  PersistentRememberMeToken 对象
        PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
                username, generateSeriesData(), generateTokenData(), new Date());
        try {
            // 3 通过 tokenRepository 存储 persistentRememberMeToken 信息
            tokenRepository.createNewToken(persistentToken);
            // 4 将 persistentRememberMeToken 信息添加到Cookie中
            addCookie(persistentToken, request, response);
        }
        catch (Exception e) {
            logger.error("Failed to save persistent token ", e);
        }
    }

ソース解析手順:

  • アカウント情報のユーザー名を取得
  • PersistentRememberMeTokenを作成した着信ユーザ名オブジェクト
  • persistentRememberMeToken保存tokenRepository情報により、
  • クッキーpersistentRememberMeTokenに情報を追加します。

  ここtokenRepositoryは、当社のリメンバー・ミー構成機能セットです。上記解像度の後、我々はrememberServicesトークン情報を作成し、参照し、データベースに格納されている(私たちは、データベース・ストレージJdbcTokenRepositoryImplを構成しているため)、及びトークンはCookieに情報を追加します。ここでは、リメンバー・ミーを達成する前に、我々は戻ってリメンバー・ミーを達成するためにどのように、ビジネスプロセスの数を参照してください、私たちは、おそらく心が終わる持っていると思います。我々はのプロセスに言及する必要はありません直接スローフィルタクラスの前に、ここで認定RememberMeAuthenticationFilterを、それはフィルタの前に責任があるの後に、それは、UsernamePasswordAuthenticationFilterとAnonymousAuthenticationFilterの間のフィルタの間にあるクッキーから情報を得るために、成功した認証トークンではありませんその後、tokenRepositoryによるログインユーザー名について、次にUserDetailsS​​ervcie UserDetails情報をロードした後、Authticaton(RememberMeAuthenticationToken)の情報を作成し、AuthenticationManager.authenticate()認証プロセスを呼び出します。

RememberMeAuthenticationFilter

  私たちは、dofiler方法RememberMeAuthenticationFilterソースを見てみましょう。

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

        if (SecurityContextHolder.getContext().getAuthentication() == null) {
            //  1 调用 rememberMeServices.autoLogin() 获取Authtication 信息
            Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
                    response);

            if (rememberMeAuth != null) {
                // Attempt authenticaton via AuthenticationManager
                try {
                    // 2 调用 authenticationManager.authenticate() 认证
                    rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
                    
                    ......
                    }

                }
                catch (AuthenticationException authenticationException) {
                .....
            }

            chain.doFilter(request, response);
        }

私達の主な関心事のrememberMeServices.autoLogin(リクエスト、レスポンス)メソッドの実装では、ソースコードを表示します。

@Override
    public final Authentication autoLogin(HttpServletRequest request,
            HttpServletResponse response) {
        // 1 从Cookie 中获取 token 信息
        String rememberMeCookie = extractRememberMeCookie(request);

        if (rememberMeCookie == null) {
            return null;
        }
        
        if (rememberMeCookie.length() == 0) {
            cancelCookie(request, response);
            return null;
        }

        UserDetails user = null;

        try {
            // 2 解析 token信息
            String[] cookieTokens = decodeCookie(rememberMeCookie);
            // 3 通过 token 信息 生成 Uerdetails 信息
            user = processAutoLoginCookie(cookieTokens, request, response);
            userDetailsChecker.check(user);

            logger.debug("Remember-me cookie accepted");
            // 4 通过 UserDetails 信息创建 Authentication 
            return createSuccessfulAuthentication(request, user);
        } 
        .....
    }

内部実装手順:

  • トークンから情報を取得し、クッキーを解析
  • (達成processAutoLoginCookie()メソッド)UserDetailsを解析してトークンを生成します
  • 認証は(RememberMeAuthenticationTokenを作成するcreateSuccessfulAuthentication())UserDetailsによって生成されます

最も重要な1の一つはprocessAutoLoginCookie()メソッドであるUserDetailsオブジェクトを生成する方法で、我々はこの方法を達成するために、ソースコードを参照してください。

protected UserDetails processAutoLoginCookie(String[] cookieTokens,
            HttpServletRequest request, HttpServletResponse response) {
        final String presentedSeries = cookieTokens[0];
        final String presentedToken = cookieTokens[1];
        // 1 通过 tokenRepository 加载数据库token信息
        PersistentRememberMeToken token = tokenRepository
                .getTokenForSeries(presentedSeries);

        PersistentRememberMeToken newToken = new PersistentRememberMeToken(
                token.getUsername(), token.getSeries(), generateTokenData(), new Date());
        // 2 判断 用户传入token和数据中的token是否一致,不一致可能存在安全问题
        if (!presentedToken.equals(token.getTokenValue())) {
            tokenRepository.removeUserTokens(token.getUsername());
            throw new CookieTheftException(
                    messages.getMessage(
                            "PersistentTokenBasedRememberMeServices.cookieStolen",
                            "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
        }
        try {
            // 3 更新 token 并添加到Cookie中
            tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
                    newToken.getDate());
            addCookie(newToken, request, response);
        }
        catch (Exception e) {
            throw new RememberMeAuthenticationException(
                    "Autologin failed due to data access problem");
        }
        // 4 通过 UserDetailsService().loadUserByUsername() 方法加载UserDetails 信息并返回
        return getUserDetailsService().loadUserByUsername(token.getUsername());
    }

私たちは、その内部の手順を見てみましょう。

  • データベーストークン情報をロードtokenRepository
  • 矛盾、着信トークンとデータのユーザが一貫トークンかどうかを決定する、セキュリティ上の問題があるかもしれません
  • トークン更新し、クッキーに追加
  • そして、バック負荷情報UserDetails UserDetailsS​​ervice(スルー)。LoadUserByUsername()メソッド

   ここで私は、なぜtokenRepositoryとUserDetailsS​​erviceを設定するには、リメンバー・ミー機能を有効にしたとき、私たちは次のように理解してご覧ください信じています。

ここで私は、フローチャートを達成するためのプロセス全体を発揮し、古いルールはありません。

https://img2018.cnblogs.com/blog/1772687/201908/1772687-20190828221251139-652651366.jpg

   この記事では、コードリポジトリのセキュリティモジュール、githubのアドレス・プロジェクトにアクセスすることができ、認証およびパーソナライズリメンバー・ミーコードについて説明します。https://github.com/BUG9/spring-security

         あなたはこれらのことに興味がある場合は、スターを歓迎し、サポートを転送し、ブックマークに従ってください!

おすすめ

転載: www.cnblogs.com/bug9/p/11426879.html