一套系統多套用戶安全體系該怎么辦

      網友投稿 809 2022-05-30

      在業務系統中很可能遇到兩個或者用戶體系,比如后臺管理用戶和前臺APP用戶。很多時候這兩種用戶走的還是兩種不同的體系,比如后臺用戶用有狀態的Session,而前臺用戶用流行的無狀態JWT,總之它們是兩種完全不同的隔離體系。這種需求該怎么實現呢?其中有哪些坑要踩呢?本文將告訴你怎么做。

      路徑攔截策略

      在Spring Security中當然是按照不同的請求路徑規則定義專門的過濾器鏈,你可以通過三種方式來實現路徑攔截。然后按照策略定義過濾器鏈即可:

      @Bean

      @Order(Ordered.HIGHEST_PRECEDENCE?+?1)

      SecurityFilterChain?systemSecurityFilterChain(HttpSecurity?http)?throws?Exception?{

      //?省略

      }

      這三種策略介紹如下。

      按照正則過濾

      你可以通過HttpSecurity提供的過濾器過濾URI,例如攔截請求中在query參數而且包含id的URI:

      http.regexMatcher("/(\\?|\\&)\"?+?id?+?\"=([^\\&]+)/")

      這種常用來匹配一些帶參數的URL。

      按照Ant規則過濾

      這種是我們常見的方式,例如攔截/system開頭的所有路徑:

      http.antMatcher("/system/**")

      關于這種方式這里不再贅述,詳細可以通過Ant規則詳解這一篇來了解。

      按照RequestMatcher過濾

      一些復雜的組合可以通過定義RequestMatcher接口來組合,例如這種復雜的規則:

      RequestMatcher?requestMatcher?=?new?OrRequestMatcher(

      new?AntPathRequestMatcher(

      providerSettings.getTokenEndpoint(),

      HttpMethod.POST.name()),

      new?AntPathRequestMatcher(

      providerSettings.getTokenIntrospectionEndpoint(),

      HttpMethod.POST.name()),

      new?AntPathRequestMatcher(

      providerSettings.getTokenRevocationEndpoint(),

      HttpMethod.POST.name()));

      http.requestMatcher(requestMatcher)

      滿足三個路徑中的一個就行,這種組合方式能夠實現最復雜的攔截策略。

      配置隔離的一些要點

      這里還要注意配置之間的隔離。

      Session會話

      默認情況下的Session依賴于cookie中設定的jsessionid, 如果你使用會話模式,必須隔離多個過濾器鏈的會話存儲,這樣能夠實現一個多個過濾器在同一個會話下不同的登錄狀態,否則它們共享配置就會發生錯亂。

      這是因為在一個會話下,默認的屬性Key是SPRING_SECURITY_CONTEXT,當在同一個會話下(同一個瀏覽器不同的tab頁)獲取當前上下文都是這樣的:

      //?默認?SPRING_SECURITY_CONTEXT

      Object?contextFromSession?=?httpSession.getAttribute(this.springSecurityContextKey);

      這樣登錄一個,其它都認為是登錄狀態,這顯然不符合預期。你需要在不同的過濾器中定義不同的會話屬性Key。

      final?String?ID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY?="SOME_UNIQUE_KEY"

      HttpSessionSecurityContextRepository?hs?=?new?HttpSessionSecurityContextRepository();

      hs.setSpringSecurityContextKey(ID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY);

      http.securityContext().securityContextRepository(hs)

      無狀態Token

      無狀態Token相對簡單一些,前端根據路徑分開存儲即可,而且Token中應該包含校驗過濾器鏈的信息以方便后端校驗,避免Token混用。

      UserDetailsService

      如果你的不同端的用戶是獨立的,你需要實現不同的UserDetailsService,但是存在多個UserDetailsService的話,

      一定不要將它們直接注冊到Spring IoC中!

      一定不要將它們直接注冊到Spring IoC中!

      一定不要將它們直接注冊到Spring IoC中!

      如果你一定要注冊到Spring IoC,你需要定義獨立的接口,就像這樣:

      @FunctionalInterface

      public?interface?OAuth2UserDetailsService?{

      UserDetails?loadOAuth2UserByUsername(String?username)?throws?UsernameNotFoundException;

      }

      然后實現該接口再注入Spring IoC,每個過濾器鏈配置的時候就可以這樣寫:

      @Bean

      @Order(Ordered.HIGHEST_PRECEDENCE?+?2)

      SecurityFilterChain?defaultSecurityFilterChain(HttpSecurity?http,

      OAuth2UserDetailsService?oAuth2UserDetailsService)?throws?Exception?{

      http.userDetailsService(oAuth2UserDetailsService::loadOAuth2UserByUsername)

      }

      但是Spring IoC中必須有一個UserDetailsService,你得這樣寫:

      @Bean

      UserDetailsService notFoundUserDetailsService() {

      return username -> {

      throw new UsernameNotFoundException("用戶未找到");

      };

      }

      為啥不可用,因為注入Spring IoC的UserDetailsService是一個兜底的實現,如果你只有一個實現,放入Spring IoC無可厚非,如果你想讓多個各自走各自的就必須這樣寫最安全,不然還有一個默認的InMemoryUserDetailsManager也會生效成為兜底的。

      其它

      其它配置按照各自的配置就行了,目前我還沒有發現有沖突的地方。上面所講的東西,在Id Server授權服務器中就是這樣實現授權服務器過濾、后臺管理用戶和前臺授權用戶三者之間隔離的:

      @EnableWebSecurity

      @EnableGlobalMethodSecurity(prePostEnabled?=?true)

      public?class?IdServerSecurityConfiguration?{

      private?static?final?String?CUSTOM_CONSENT_PAGE_URI?=?"/oauth2/consent";

      private?static?final?String?SYSTEM_ANT_PATH?=?"/system/**";

      /**

      *?The?constant?ID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY.

      */

      public?static?final?String?ID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY?=?"ID_SERVER_SYSTEM_SECURITY_CONTEXT";

      /**

      *?授權服務器配置

      *

      *?@author?felord.cn

      *?@since?1.0.0

      */

      @Configuration(proxyBeanMethods?=?false)

      public?static?class?AuthorizationServerConfiguration?{

      /**

      *?Authorization?server?集成?優先級要高一些

      *

      *?@param?http?the?http

      *?@return?the?security?filter?chain

      *?@throws?Exception?the?exception

      *?@since?1.0.0

      */

      @Bean("authorizationServerSecurityFilterChain")

      @Order(Ordered.HIGHEST_PRECEDENCE)

      SecurityFilterChain?authorizationServerSecurityFilterChain(HttpSecurity?http)?throws?Exception?{

      OAuth2AuthorizationServerConfigurer?authorizationServerConfigurer?=

      new?OAuth2AuthorizationServerConfigurer<>();

      //??把自定義的授權確認URI加入配置

      authorizationServerConfigurer.authorizationEndpoint(authorizationEndpointConfigurer?->

      authorizationEndpointConfigurer.consentPage(CUSTOM_CONSENT_PAGE_URI));

      RequestMatcher?authorizationServerEndpointsMatcher?=?authorizationServerConfigurer.getEndpointsMatcher();

      //?攔截?授權服務器相關的請求端點

      http.requestMatcher(authorizationServerEndpointsMatcher)

      .authorizeRequests().anyRequest().authenticated()

      .and()

      //?忽略掉相關端點的csrf

      .csrf(csrf?->?csrf

      .ignoringRequestMatchers(authorizationServerEndpointsMatcher))

      .formLogin()

      .and()

      //?應用?授權服務器的配置

      .apply(authorizationServerConfigurer);

      return?http.build();

      }

      /**

      *?配置?OAuth2.0?provider元信息

      *

      *?@param?port?the?port

      *?@return?the?provider?settings

      *?@since?1.0.0

      */

      @Bean

      public?ProviderSettings?providerSettings(@Value("${server.port}")?Integer?port)?{

      //TODO?配置化?生產應該使用域名

      return?ProviderSettings.builder().issuer("http://localhost:"?+?port).build();

      }

      }

      /**

      *?后臺安全配置.

      *

      *?@author?felord.cn

      *?@since?1.0.0

      */

      @Configuration(proxyBeanMethods?=?false)

      public?static?class?SystemSecurityConfiguration?{

      /**

      *?管理后臺以{@code?/system}開頭

      *

      *?@param?http?the?http

      *?@return?the?security?filter?chain

      *?@throws?Exception?the?exception

      *?@see?AuthorizationServerConfiguration

      */

      @Bean

      @Order(Ordered.HIGHEST_PRECEDENCE?+?1)

      SecurityFilterChain?systemSecurityFilterChain(HttpSecurity?http,?UserInfoService?userInfoService)?throws?Exception?{

      SimpleAuthenticationEntryPoint?authenticationEntryPoint?=?new?SimpleAuthenticationEntryPoint();

      AuthenticationEntryPointFailureHandler?authenticationFailureHandler?=?new?AuthenticationEntryPointFailureHandler(authenticationEntryPoint);

      HttpSessionSecurityContextRepository?securityContextRepository?=?new?HttpSessionSecurityContextRepository();

      securityContextRepository.setSpringSecurityContextKey(ID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY);

      http.antMatcher(SYSTEM_ANT_PATH).csrf().disable()

      .headers().frameOptions().sameOrigin()

      .and()

      .securityContext().securityContextRepository(securityContextRepository)

      .and()

      .authorizeRequests().anyRequest().authenticated()

      /*??.and()

      .exceptionHandling()

      .authenticationEntryPoint(authenticationEntryPoint)*/

      .and()

      .userDetailsService(userInfoService::findByUsername)

      .formLogin().loginPage("/system/login").loginProcessingUrl("/system/login")

      .successHandler(new?RedirectLoginAuthenticationSuccessHandler("/system"))

      .failureHandler(authenticationFailureHandler).permitAll();

      return?http.build();

      }

      }

      /**

      *?普通用戶訪問安全配置.

      一套系統多套用戶安全體系該怎么辦

      *

      *?@author?felord.cn

      *?@since?1.0.0

      */

      @Configuration(proxyBeanMethods?=?false)

      public?static?class?OAuth2SecurityConfiguration?{

      /**

      *?Default?security?filter?chain?security?filter?chain.

      *

      *?@param?http?????????????????????the?http

      *?@param?oAuth2UserDetailsService?the?oauth2?user?details?service

      *?@param?securityFilterChain??????the?security?filter?chain

      *?@return?the?security?filter?chain

      *?@throws?Exception?the?exception

      */

      @Bean

      @Order(Ordered.HIGHEST_PRECEDENCE?+?2)

      SecurityFilterChain?defaultSecurityFilterChain(HttpSecurity?http,

      OAuth2UserDetailsService?oAuth2UserDetailsService,

      @Qualifier("authorizationServerSecurityFilterChain")?SecurityFilterChain?securityFilterChain)?throws?Exception?{

      DefaultSecurityFilterChain?authorizationServerFilterChain?=?(DefaultSecurityFilterChain)?securityFilterChain;

      SimpleAuthenticationEntryPoint?authenticationEntryPoint?=?new?SimpleAuthenticationEntryPoint();

      AuthenticationEntryPointFailureHandler?authenticationFailureHandler?=?new?AuthenticationEntryPointFailureHandler(authenticationEntryPoint);

      http.requestMatcher(new?AndRequestMatcher(

      new?NegatedRequestMatcher(new?AntPathRequestMatcher(SYSTEM_ANT_PATH)),

      new?NegatedRequestMatcher(authorizationServerFilterChain.getRequestMatcher())

      )).authorizeRequests(authorizeRequests?->

      authorizeRequests

      .anyRequest().authenticated()

      ).csrf().disable()

      .userDetailsService(oAuth2UserDetailsService::loadOAuth2UserByUsername)

      .formLogin().loginPage("/login")

      .successHandler(new?RedirectLoginAuthenticationSuccessHandler())

      .failureHandler(authenticationFailureHandler).permitAll()

      .and()

      .oauth2ResourceServer().jwt();

      return?http.build();

      }

      }

      }

      你可以通過https://github.com/NotFound403/id-server下載源碼進行改造學習,歡迎Star。

      OAuth2教程可通過https://blog.csdn.net/qq_35067322/category_11691173.html訂閱。

      Spring 網絡

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:初識GaussDB A(初識GaussDB(for Influx))
      下一篇:三維重建技術簡介(三維重建技術 各種方法簡介)
      相關文章
      亚洲AV无码国产精品永久一区| 亚洲av无码成人精品区一本二本| 亚洲成av人片在线观看天堂无码| 亚洲午夜理论片在线观看| 亚洲伊人久久大香线蕉影院| 亚洲尹人九九大色香蕉网站| 亚洲精品福利视频| 无码久久精品国产亚洲Av影片| 亚洲av无码乱码国产精品 | 亚洲国产精品无码久久一区二区| 综合亚洲伊人午夜网| 中文字幕亚洲无线码| 国产中文在线亚洲精品官网| 亚洲综合区小说区激情区| 久久亚洲欧洲国产综合| 中文字幕亚洲图片| 国产亚洲人成网站在线观看不卡| 亚洲国产精品无码一线岛国 | 亚洲日韩中文字幕一区| 亚洲精品无码久久久久YW| 人人狠狠综合久久亚洲| 青青青国产色视频在线观看国产亚洲欧洲国产综合 | 日日噜噜噜噜夜夜爽亚洲精品| 久久久久国产成人精品亚洲午夜| 精品亚洲一区二区三区在线播放| 伊人久久大香线蕉亚洲五月天| 亚洲精品无码高潮喷水在线| 久久伊人久久亚洲综合| 久久亚洲中文字幕精品有坂深雪| 亚洲黄网站wwwwww| 亚洲a视频在线观看| 亚洲人成人网站18禁| 国产精品亚洲综合| 国产成人精品日本亚洲专区| 国产亚洲精品xxx| 亚洲高清不卡视频| 亚洲色偷偷综合亚洲AV伊人蜜桃 | 亚洲图片一区二区| 亚洲av乱码一区二区三区| 亚洲乱码国产乱码精华| 亚洲 另类 无码 在线|