<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>

          Spring-Gateway 與 Spring-Security在前后端分離項目中的實踐

          共 8429字,需瀏覽 17分鐘

           ·

          2022-05-28 17:18


          前言

          網(wǎng)上貌似webflux這一套的SpringSecurity操作資料貌似很少。

          自己研究了一波,記錄下來做一點備忘,如果能幫到也在迷惑的人一點點,就更好了。

          新項目是前后端分離的項目,前臺vue,后端SpringCloud2.0,采用oauth2.0機制來獲得用戶,權(quán)限框架用的gateway。

          一,前臺登錄

          大概思路前臺主要是配合項目中配置的clientId,clientSecret去第三方服務(wù)器拿授權(quán)碼code,然后拿這個code去后端交互,后端根據(jù)code去第三方拿用戶信息,由于第三方只保存用戶信息,不管具體的業(yè)務(wù)權(quán)限,所以我會在本地保存一份用戶副本,用來做權(quán)限關(guān)聯(lián)。用戶登錄成功后,會把一些用戶基本信息(脫敏)生成jwt返回給前端放到head中當(dāng)Authorization,同時后端把一些相關(guān)聯(lián)的菜單,權(quán)限等數(shù)據(jù)放到redis里做關(guān)聯(lián),為后面的權(quán)限控制做準(zhǔn)備。

          二,SpringSecurity的webflux應(yīng)用

          如果用過SpringSecurity,HttpSecurity應(yīng)該是比較熟悉的,基于Web允許為特定的http請求配置安全性。

          WebFlux中ServerHttpSecurity與HttpSecurity提供的相似的類似,但僅適用于WebFlux。默認(rèn)情況下,它將應(yīng)用于所有請求,但可以使用securityMatcher(ServerWebExchangeMatcher)或其他類似方法進行限制。

          項目比較特殊,就不能全展示了,大概寫一寫,開啟Security如下:

          @EnableWebFluxSecuritypublic class MyExplicitSecurityConfiguration {  @Bean  SecurityWebFilterChain webFluxSecurityFilterChain(ServerHttpSecurity http) throws Exception {http.securityContextRepository(new NoOpServerSecurityContextAutoRepository(tokenProvider)).httpBasic().disable().formLogin().disable().csrf().disable().logout().disable();http.addFilterAt(corsFilter(), SecurityWebFiltersOrder.CORS).authorizeExchange().matchers(EndpointRequest.to("health", "info")).permitAll().and().authorizeExchange().pathMatchers(HttpMethod.OPTIONS).permitAll().and().authorizeExchange().pathMatchers(HttpMethod.PUT).denyAll().and().authorizeExchange().pathMatchers(HttpMethod.DELETE).denyAll().and().authorizeExchange().pathMatchers(HttpMethod.HEAD).denyAll().and().authorizeExchange().pathMatchers(HttpMethod.PATCH).denyAll().and().authorizeExchange().pathMatchers(HttpMethod.TRACE).denyAll().and().authorizeExchange().pathMatchers(excludedAuthPages).permitAll().and().authorizeExchange().pathMatchers(authenticatedPages).authenticated().and().exceptionHandling().accessDeniedHandler(new AccessDeniedEntryPointd()).and().authorizeExchange().and().addFilterAt(webFilter(), SecurityWebFiltersOrder.AUTHORIZATION).authorizeExchange().pathMatchers("/**").access(new JwtAuthorizationManager(tokenProvider))      .anyExchange().authenticated();    return http.build();  }}

          因為是前后端分離項目,所以沒有常規(guī)的后端的登錄操作,把這些disable掉。

          securityContextRepository是個用于在請求之間保留SecurityContext策略接口,實現(xiàn)類是WebSessionServerSecurityContextRepository(session存儲),還有就是NoOpServerSecurityContextRepository(用于無狀態(tài)應(yīng)用),像我們JWT這種就用后者,不能用前者,應(yīng)該我們是無狀態(tài)的應(yīng)用,沒有主動clear的操作,會導(dǎo)致內(nèi)存溢出等問題。

          build()方法中會有一個初始化操作。

          初始化操作就設(shè)置成了WebSessionServerSecurityContextRepository,我們就自己在SecurityWebFilterChain中設(shè)置成NoOpServerSecurityContextRepository。

          接下來我們?yōu)榱藵M足自定義認(rèn)證需求,我們自己配置一個AuthenticationWebFilter。

          public AuthenticationWebFilter webFilter() {        AuthenticationWebFilter authenticationWebFilter = new AuthenticationWebFilter(new JWTReactiveAuthenticationManager(userCache, tokenProvider, coreUserApi));        authenticationWebFilter.setServerAuthenticationConverter(new TokenAuthenticationConverter(guestList, tokenProvider));        authenticationWebFilter.setRequiresAuthenticationMatcher(new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.pathMatchers(excludedAuthPages)));        authenticationWebFilter.setSecurityContextRepository(new NoOpServerSecurityContextAutoRepository(tokenProvider));return authenticationWebFilter;    }

          幾個特殊的類,稍微解釋下。

          • AuthenticationWebFilter

          一個執(zhí)行特定請求身份驗證的WebFilter,包含了一整套驗證的流程操作,具體上源碼看一眼基本能了解個大概。

          @Overridepublic Mono filter(ServerWebExchange exchange, WebFilterChain chain) {return this.requiresAuthenticationMatcher.matches(exchange)      .filter( matchResult -> matchResult.isMatch())      .flatMap( matchResult -> this.authenticationConverter.convert(exchange))      .switchIfEmpty(chain.filter(exchange).then(Mono.empty()))      .flatMap( token -> authenticate(exchange, chain, token))      .onErrorResume(AuthenticationException.class, e -> this.authenticationFailureHandler          .onAuthenticationFailure(new WebFilterExchange(exchange, chain), e));  }
          private Mono authenticate(ServerWebExchange exchange, WebFilterChain chain, Authentication token) {return this.authenticationManagerResolver.resolve(exchange) .flatMap(authenticationManager -> authenticationManager.authenticate(token)) .switchIfEmpty(Mono.defer(() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass())))) .flatMap(authentication -> onAuthenticationSuccess(authentication, new WebFilterExchange(exchange, chain))); }
          protected Mono onAuthenticationSuccess(Authentication authentication, WebFilterExchange webFilterExchange) { ServerWebExchange exchange = webFilterExchange.getExchange(); SecurityContextImpl securityContext = new SecurityContextImpl(); securityContext.setAuthentication(authentication);return this.securityContextRepository.save(exchange, securityContext) .then(this.authenticationSuccessHandler .onAuthenticationSuccess(webFilterExchange, authentication)) .subscriberContext(ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext))); }

          • ServerWebExchangeMatcher

            一個用來匹配URL用來驗證的接口,我代碼中用的是他的實現(xiàn)類NegatedServerWebExchangeMatcher,這個類就是指一些我設(shè)置的白名單的url就不要驗證了,他還有許多實現(xiàn)類,具體可以參見源碼,我這就不累述了。

          • ServerAuthenticationConverter

            一個用于從ServerWebExchange轉(zhuǎn)換為用于通過提供的org.springframework.security.authentication.ReactiveAuthenticationManager進行身份驗證的Authentication的策略。如果結(jié)果為Mono.empty() ,則表明不進行任何身份驗證嘗試。我這邊自己實現(xiàn)了一個TokenAuthenticationConverter,主要功能就是通過JWT轉(zhuǎn)換成Authentication(UsernamePasswordAuthenticationToken)。

          • ReactiveAuthenticationManager

            對提供的Authentication進行身份驗證,基本上核心的驗證操作就在它提供的唯一方法authenticate里進行操作,根據(jù)conver那邊轉(zhuǎn)換過來的Authentication當(dāng)參數(shù)進行具體的驗證操作,簡述如下:

          @Overridepublic Mono authenticate(final Authentication authentication) {if (authentication.isAuthenticated()) {return Mono.just(authentication);        }return Mono.just(authentication)                .switchIfEmpty(Mono.defer(this::raiseBadCredentials))                .cast(UsernamePasswordAuthenticationToken.class)                .flatMap(this::authenticateToken)                .publishOn(Schedulers.parallel())                .onErrorResume(e -> raiseBadCredentials())                .switchIfEmpty(Mono.defer(this::raiseBadCredentials))                .map(u -> {                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), authentication.getName(), Collections.EMPTY_LIST);                    usernamePasswordAuthenticationToken.setDetails(u);return usernamePasswordAuthenticationToken;                });    }
          • ServerSecurityContextRepository

            用于在請求之間保留SecurityContext,因為在登錄成功后我們是需要保存一個登錄的數(shù)據(jù),用來后面的請求進行相關(guān)的操作。因為我們是無狀態(tài)的,所以其實NoOpServerSecurityContextRepository是能
            滿足我們的需求,我們不需要進行實際的save,但是load我們稍微要改造下,所以我實現(xiàn)了ServerSecurityContextRepository,仿照NoOpServerSecurityContextRepository,實現(xiàn)了一個自定義的Repository,為什么load我們要改造,就是因為雖然我們是無狀態(tài)的,但是實際上每次請求,我們依然要區(qū)分到底是誰,為了后面的權(quán)限驗證做準(zhǔn)備,所以我們根據(jù)jwt可以生成一個SecurityContext放入ReactiveSecurityContextHolder。

          public class NoOpServerSecurityContextAutoRepositoryimplements ServerSecurityContextRepository {
          private TokenProvider tokenProvider;
          public NoOpServerSecurityContextAutoRepository(TokenProvider tokenProvider) {this.tokenProvider = tokenProvider; }
          public Mono save(ServerWebExchange exchange, SecurityContext context) {return Mono.empty();
          }
          public Mono load(ServerWebExchange exchange) { String token = exchange.getRequest().getHeaders().getFirst("Authorization");if (StrUtil.isNotBlank(token)) { SecurityContext securityContext = new SecurityContextImpl(); securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("password", token, Collections.EMPTY_LIST));return Mono.justOrEmpty(securityContext); } else {return Mono.empty(); } }}

          權(quán)限驗證

          權(quán)限驗證是在圖上配置的。大概的流程,可以看下面的截圖。

          • AuthorizationWebFilter

          跟到里面,我們發(fā)現(xiàn)了最主要的就是這個AuthorizationWebFilter,用來做權(quán)限驗證的,然后我們在filter方法里面就看得很清楚了,他第一步就是拿的ReactiveSecurityContextHolder.getContext(),然后我們之前在ReactorContextWebFilter里的load操作就是從我們NoOpServerSecurityContextAutoRepository里塞到ReactiveSecurityContextHolder里,因為本質(zhì) 來說SpringSecurity就是個filter集合,我們從ReactorContextWebFilter里load,然后在AuthorizationWebFilter取,這樣就能拿到Authentication來做權(quán)限驗證了。

          • ReactiveAuthorizationManager

          響應(yīng)式授權(quán)管理器接口,可以確定Authentication是否有權(quán)訪問特定對象。其實看源碼就很清楚了,就是根據(jù)Authentication來做具體的權(quán)限驗證。

          代碼很清楚,就不細講了,我們主要是寫check方法。所以我這邊自已經(jīng)實現(xiàn)了一個JwtAuthorizationManager類用來做具體的check,內(nèi)容我就不貼了,簡單來說就是拿Authentication里的內(nèi)容去redis里查對應(yīng)的菜單權(quán)限。

          結(jié)語

          上面就我實際項目中的一些點滴記錄,Spring-Security雖是一個博大精深的框架,細研究代碼,其實也能大致明白整體的思路,雖然webflux讓這一層代碼更加了一層迷霧,但是只要努力鉆研,總會有茅塞頓開的時候。

          附上相關(guān)代碼,由于是生產(chǎn)項目,只能截取部分代碼,僅供參考。

          https://files.cnblogs.com/files/zhou-yuan/java.rar
          來源:?//www.cnblogs.com/zhou-yuan/p/14472889.html


          記得點「」和「在看」↓

          愛你們


          瀏覽 149
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  亚洲秘 无码一区二区三区胖子 | 成黄网站一蜜芽二 | Safari帮我打开日韩av三级片 | 青青草在线观看免费视频 | 一本无码一区二区三区 |