亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

Spring security:如何實現“兩步”身份驗證(oauth 然后基于表單)?

Spring security:如何實現“兩步”身份驗證(oauth 然后基于表單)?

慕婉清6462132 2022-03-10 15:43:06
在我的應用程序中,我有兩個不同的身份驗證網關,它們分別工作得很好:OAuth 1.0 ( service-to-service ) 將 ROLE_OAUTH 提供給用戶;在這里,我對用戶一無所知,只有一些關于它在 Principal 對象中使用的服務的上下文信息;為用戶提供 ROLE_USER 的標準基于表單的身份驗證;在這里,我有關于我的用戶的完整信息,但沒有關于它在 Principal 對象中使用的服務的上下文信息;現在我想實現兩步身份驗證:1)OAuth 然后基于表單。復雜性在于,我不想在步驟 1 (OAuth) 之后丟失存儲在 Principal 中的特定于上下文的信息;我只想在完成基于表單的身份驗證后將一些新的用戶特定信息添加到安全上下文以及一個新角色 ROLE_USER,所有這些都在同一個身份驗證會話中。能否順利實施?如何在第二步(基于表單的身份驗證)中提取現有的 Principal 信息并將其添加到新的 Principal 中?有沒有不重新發明輪子的“模板解決方案”?我目前的直接解決方案是:我已經通過角色 ROLE_OAUTH 驗證了用戶并打開了身份驗證會話;為二維步驟創建一個單獨的路徑,例如/oauth/login;用戶輸入他的憑據后,我在控制器中的安全鏈之外處理它們,手動檢查憑據;如果成功,手動更新安全上下文而不丟失身份驗證會話,然后將用戶重定向到請求的受 ROLE_USER 保護的資源;但我不喜歡它,這似乎很蹩腳,因為我必須手動處理第二個安全請求..如何以 Spring-ish 的方式正確實現這一點?謝謝你。PS由于遺留原因,我必須使用 Oauth 1.0,無法將其升級到 v.2 或任何其他解決方案。
查看完整描述

1 回答

?
千巷貓影

TA貢獻1829條經驗 獲得超7個贊

好的,這就是我設法完成這項任務的方法。

  1. 我有一個經過身份驗證的用戶(實際上是服務),角色為 ROLE_OAUTH 并打開了身份驗證會話,以及有關會話中保留的上下文的一些關鍵信息,這些信息硬連接到 OAuth 請求中;

  2. 現在,當嘗試訪問需要另一個角色(例如 ROLE_USER)的受保護資源時,Spring 給了我AccessDeniedException并發送 403 禁止響應(請參閱AccessDeniedHandlerImpl),如果需要,請在自定義 AccessDeniedHandler 中覆蓋默認行為。這是代碼示例:


   public class OAuthAwareAccessDeniedHandler implements AccessDeniedHandler {

   private static final Log LOG = LogFactory.getLog(OAuthAwareAccessDeniedHandler.class);


    @Override

    public void handle(HttpServletRequest request, HttpServletResponse response,

            AccessDeniedException accessDeniedException) throws IOException, ServletException {

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

        if (oauthSecurityUtils.isUserWithOnlyOAuthRole(auth)) {

            LOG.debug("Prohibited to authorize OAuth user trying to access protected resource.., redirected to /login");

            // Remember the request pathway

            RequestCache requestCache = new HttpSessionRequestCache();

            requestCache.saveRequest(request, response);

            response.sendRedirect(request.getContextPath() + "/login");

            return;

        }

        LOG.debug("Ordinary redirection to /accessDenied URL..");

        response.sendRedirect(request.getContextPath() + "/accessDenied");

    }

}

現在我們需要將這個新的處理程序添加到配置中:

@Override

protected void configure(HttpSecurity http) throws Exception {

    http

        // all the config

        .and()

            .exceptionHandling().accessDeniedHandler(oauthAwareAccessDeniedHandler());

}

在此步驟之后,默認UsernamePasswordAuthenticationFilter將通過使用輸入的憑據創建另一個 Authentication 對象來處理輸入,默認行為只是丟失連接到先前 OAuth Authentication 對象的現有信息。所以我們需要通過擴展這個類來覆蓋這個默認行為,就像這樣,在標準的 UsernamePasswordAuthenticationFilter 之前添加這個過濾器。

public class OAuthAwareUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {


private static final Log LOG = LogFactory.getLog(LTIAwareUsernamePasswordAuthenticationFilter.class);



@Override

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

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

    // Check for OAuth authentication in place

    if (oauthSecurityUtils.isUserWithOnlyOAuthRole(previousAuth)) {

        LOG.debug("OAuth authentication exists, try to authenticate with UsernamePasswordAuthenticationFilter in the usual way");

        SecurityContextHolder.clearContext();

        Authentication authentication = null;

        try {// Attempt to authenticate with standard UsernamePasswordAuthenticationFilter

            authentication = super.attemptAuthentication(request, response);

        } catch (AuthenticationException e) {

            // If fails by throwing an exception, catch it in unsuccessfulAuthentication() method

            LOG.debug("Failed to upgrade authentication with UsernamePasswordAuthenticationFilter");

            SecurityContextHolder.getContext().setAuthentication(previousAuth);

            throw e;

        }

        LOG.debug("Obtained a valid authentication with UsernamePasswordAuthenticationFilter");

        Principal newPrincipal = authentication.getPrincipal();

        // Here extract all needed information about roles and domain-specific info

        Principal rememberedPrincipal = previousAuth.getPrincipal();

       // Then enrich this remembered principal with the new information and return it

        LOG.debug("Created an updated authentication for user");

        return newAuth;

    }

    LOG.debug("No OAuth authentication exists, try to authenticate with UsernamePasswordAuthenticationFilter in the usual way");

    return super.attemptAuthentication(request, response);

}


@Override

protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)

        throws IOException, ServletException {

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

    if (oauthSecurityUtils.isUserWithOnlyOAuthRole(previousAuth)) {

        LOG.debug("unsuccessfulAuthentication upgrade for OAuth user, previous authentication :: "+ previousAuth);

        super.unsuccessfulAuthentication(request, response, failed);

        LOG.debug("fallback to previous authentication");

        SecurityContextHolder.getContext().setAuthentication(previousAuth);

    } else {

        LOG.debug("unsuccessfulAuthentication for a non-OAuth user with UsernamePasswordAuthenticationFilter");

        super.unsuccessfulAuthentication(request, response, failed);

    }

}

}


唯一剩下的就是在 UsernamePasswordAuthenticationFilter 之前添加這個過濾器,并將其僅應用于給定的端點:


@Override

protected void configure(HttpSecurity http) throws Exception {

    http

        .csrf().disable()

        .addFilterBefore(oauthAwareUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

        // here come ant rules

        .and()

        .formLogin()

        .and()

            .exceptionHandling().accessDeniedHandler(oauthAwareAccessDeniedHandler());

}

而已。這個例子被測試是可行的。以后可能會發現一些副作用,不確定。此外,我確信它可以以更精致的方式完成,但我現在將使用此代碼。


查看完整回答
反對 回復 2022-03-10
  • 1 回答
  • 0 關注
  • 336 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號