<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一個接口是如何在Keycloak和Spring Security之間執(zhí)行的

          共 8642字,需瀏覽 18分鐘

           ·

          2021-08-24 19:27

          上一篇我們對Keycloak的常用配置進行了熟悉,今天我們來對Keycloak適配Spring Security的執(zhí)行流程做一個分析,簡單了解一下其定制的一些Spring Security過濾器。

          /admin/foo的執(zhí)行流程

          在適配了Keycloak和Spring Security的Spring Boot應(yīng)用中,我編寫了一個/admin/foo的接口并對這個接口進行了權(quán)限配置:

              @Override
              protected void configure(HttpSecurity http) throws Exception {
                  super.configure(http);
                  http
                          .authorizeRequests()
                          .antMatchers("/customers*").hasRole("USER")
                          .antMatchers("/admin/**").hasRole("base_user")
                          .anyRequest().permitAll();
              }

          這是典型的Spring Security配置,擁有base_user角色的用戶有權(quán)限訪問/admin/**。這里需要大家明白的是所謂的用戶base_user角色目前都由Keycloak平臺管理,而我們的應(yīng)用目前只能控制資源的訪問策略。為了探明執(zhí)行的流程我開啟了所有的日志打印,當(dāng)我訪問/admin/foo時經(jīng)過了以下過濾器:

          Security filter chain: [
            WebAsyncManagerIntegrationFilter
            SecurityContextPersistenceFilter
            HeaderWriterFilter
            CsrfFilter
            KeycloakPreAuthActionsFilter
            KeycloakAuthenticationProcessingFilter
            LogoutFilter
            RequestCacheAwareFilter
            SecurityContextHolderAwareRequestFilter
            KeycloakSecurityContextRequestFilter
            KeycloakAuthenticatedActionsFilter
            AnonymousAuthenticationFilter
            SessionManagementFilter
            ExceptionTranslationFilter
            FilterSecurityInterceptor
          ]

          這里除了Spring Security常規(guī)的內(nèi)置過濾器外還加入了Keycloak適配器的幾個過濾器,結(jié)合執(zhí)行流程來認(rèn)識一下它們。

          KeycloakPreAuthActionsFilter

          這個過濾器的作用是暴露一個Keycloak適配器對象PreAuthActionsHandlerSpring Security。而這個適配器的作用就是攔截處理一個Keycloak的職能請求處理接口,這些內(nèi)置接口都有特定的后綴:

          // 退出端點
          public static final String K_LOGOUT = "k_logout";
          // 重置什么公鑰的?
          public static final String K_PUSH_NOT_BEFORE = "k_push_not_before";
          // 測試用的
          public static final String K_TEST_AVAILABLE = "k_test_available";
          // 獲取 jwk 相關(guān)的
          public static final String K_JWKS = "k_jwks";

          ?

          一般不深入底層可以不管這個過濾器。

          KeycloakAuthenticationEntryPoint

          ?

          KeycloakAuthenticationEntryPointAuthenticationEntryPoint的實現(xiàn),配置于KeycloakWebSecurityConfigurerAdapter。

          當(dāng)請求被過濾器FilterSecurityInterceptor時發(fā)現(xiàn)當(dāng)前的用戶是個匿名用戶,不符合/admin/foo的訪問控制要求而拋出了AccessDeniedException。會通過ExceptionTranslationFilter傳遞給KeycloakAuthenticationEntryPoint處理401異常。

              @Override
              public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                  HttpFacade facade = new SimpleHttpFacade(request, response);
                  if (apiRequestMatcher.matches(request) || adapterDeploymentContext.resolveDeployment(facade).isBearerOnly()) {
                      commenceUnauthorizedResponse(request, response);
                  } else {
                      commenceLoginRedirect(request, response);
                  }
              }

          它執(zhí)行了兩種策略:

          • 當(dāng)請求時登錄請求/sso/login或者是BearerOnly(這些屬性上一篇可介紹了一部分哦)就直接返回標(biāo)頭含WWW-Authenticate 的401響應(yīng)。
          • 其它情況就跳一個OIDC認(rèn)證授權(quán)請求。
          protected void commenceLoginRedirect(HttpServletRequest request, HttpServletResponse response) throws IOException {
              if (request.getSession(false) == null && KeycloakCookieBasedRedirect.getRedirectUrlFromCookie(request) == null) {
                  // If no session exists yet at this point, then apparently the redirect URL is not
                  // stored in a session. We'll store it in a cookie instead.
                  response.addCookie(KeycloakCookieBasedRedirect.createCookieFromRedirectUrl(request.getRequestURI()));
              }

              String queryParameters = "";
              if (!StringUtils.isEmpty(request.getQueryString())) {
                  queryParameters = "?" + request.getQueryString();
              }

              String contextAwareLoginUri = request.getContextPath() + loginUri + queryParameters;
              log.debug("Redirecting to login URI {}", contextAwareLoginUri);
              response.sendRedirect(contextAwareLoginUri);
          }

          我們的接口明顯走的上面的方法,很明顯要跳登錄頁了。這時需要看看/admin/foo有沒有緩存起來,因為登錄完還要去執(zhí)行/admin/foo的邏輯。如果Spring Security沒有存Session或者Cookie中也沒有就會把/admin/foo緩存到Cookie中,然后重定向到Keycloak授權(quán)頁:

          http://localhost:8011/auth/realms/felord.cn/protocol/openid-connect/auth?response_type=code&client_id=CLIENT&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fsso%2Flogin&state=STATE&login=true&scope=openid

          KeycloakAuthenticationProcessingFilter

          上面是一個典型的 Authorization Code Flow模式。當(dāng)輸入帳號密碼同意授權(quán)時,授權(quán)服務(wù)器會請求一個攜帶codestate的回調(diào)鏈接(這里是/sso/login)。負(fù)責(zé)攔截處理/sso/login的是KeycloakAuthenticationProcessingFilter。這個接口不單單處理登錄,只要攜帶了授權(quán)頭Authorization、access_token、Keycloak Cookie三種之一的它都會攔截處理。

          在這個過濾器和我們熟悉的UsernamePasswordAuthenticationFilter一樣都繼承了AbstractAuthenticationProcessingFilter其實大致流程也很相似,只不過走的是Keycloak認(rèn)證授權(quán)的API。認(rèn)證授權(quán)成功就從Session中重新獲取/admin/foo接口并跳轉(zhuǎn)。整個簡單的Keycloak認(rèn)證授權(quán)過程就完成了。

          KeycloakSecurityContextRequestFilter

          這個過濾器功能比較單一,它是用來判斷是不是RefreshableKeycloakSecurityContext,可刷新的安全上下文,如果是就在ServletRequest對象中放個RefreshableKeycloakSecurityContext,后續(xù)其它過濾器會根據(jù)這個標(biāo)記做一些事情。

          KeycloakAuthenticatedActionsFilter

          這個過濾器就是用來捕捉KeycloakSecurityContextRequestFilter放在請求對象ServletRequest中的RefreshableKeycloakSecurityContext的。核心就這些:

              public boolean handledRequest() {
                  log.debugv("AuthenticatedActionsValve.invoke {0}", facade.getRequest().getURI());
                  if (corsRequest()) return true;
                  String requestUri = facade.getRequest().getURI();
                  if (requestUri.endsWith(AdapterConstants.K_QUERY_BEARER_TOKEN)) {
                      queryBearerToken();
                      return true;
                  }
                  if (!isAuthorized()) {
                      return true;
                  }
                  return false;
              }

          這里返回true就阻斷不往下走了。主要是根據(jù)Keycloak提供的策略來判斷是否已經(jīng)授權(quán),看上去邏輯還挺復(fù)雜的。

          ?

          基于篇幅的原理,我們后續(xù)再詳細(xì)介紹Keycloak的過濾器,今天我們先知道大致它們都干什么用的。

          補充

          其實要想搞清楚任何一個框架的運行流程,最好的辦法就是從日志打印中提煉一些關(guān)鍵點。Keycloak Spring Security Adapter的運行流程如果你想搞清楚,最好是自己先試一試。我把開啟Keycloak適配器的注解拆解開以打開Spring Security的日志:

          @Configuration
          @ComponentScan(
                  basePackageClasses = {KeycloakSecurityComponents.class}
          )
          @EnableWebSecurity(debug 
          true)
          public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
          //ignore
          }

          為了看到更多日志,把Spring Boot的org相關(guān)包的日志也調(diào)整為debug

          logging:
            level:
              org : debug

          然后代碼運行的流程會在控制臺Console非常清晰,極大方便了我弄清楚Keycloak的運行流程。Keycloak的流程簡單了解一下就好,感覺非常平淡無奇,大部分也沒有定制化的需要,個人覺得重心其實不在這里,如何根據(jù)業(yè)務(wù)定制Keycloak的用戶管理、角色管理等一系列管理API才是使用好它的關(guān)鍵。不要走開,后續(xù)會結(jié)合一些場景來魔改keycloak。

          往期推薦

          Spring Security實戰(zhàn)干貨:集成微信公眾號OAuth2.0授權(quán)

          Spring官宣新家族成員:Spring Authorization Server!

          我們自嘲的“碼農(nóng)”身份被官方實錘了!

          YYDS 的 IDEA插件,沒裝上的安排起來!

          混合辦公時代來了?攜程試點每周兩天居家辦公,76%的員工主動報名!


          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美成人一区二区三区四区 | 99欧美视频免费在线观看 | 午夜在线观看视频18 | 日韩草比| 69国产精品无码免费 |