在业务系统中,很可能会遇到两个或用户系统,比如后台管理用户和前端APP用户。在很多情况下,这两类用户仍然使用两种不同的系统。例如,后台用户使用有状态的Session,而前端用户使用流行的无状态JWT。简而言之,它们是两个完全不同的隔离系统。如何实现这个需求?踩到哪些陷阱?本文将告诉您如何操作。SpringSecurity中的路径拦截策略当然是根据不同的请求路径规则定义专门的过滤链。您可以通过三种方式实现路径拦截。然后根据策略定义过滤器链:@Bean@Order(Ordered.HIGHEST_PRECEDENCE+1)SecurityFilterChainsystemSecurityFilterChain(HttpSecurityhttp)throwsException{//省略}这三种策略介绍如下。根据正则过滤,可以通过HttpSecurity提供的过滤器过滤URI,比如拦截请求中包含query参数和ids的URI:http.regexMatcher("/(\\\\?|\\\\&)\"+id+\"=([^\\\\&]+)/")这个常用来匹配一些带参数的url。按照Ant规则进行过滤是我们常用的方式,比如拦截所有以/system开头的路径:http.antMatcher("/system/**")这个方法这里就不细说了,大家可以在详细通过Ant规则文章来了解。根据RequestMatcher过滤一些复杂的组合可以通过定义RequestMatcher接口来组合,比如这个复杂规则:RequestMatcherrequestMatcher=newOrRequestMatcher(newAntPathRequestMatcher(providerSettings.getTokenEndpoint(),HttpMethod.POST.name()),newAntPathRequestMatcher(providerSettings.getTokenIntrospectionEndpoint(),HttpMethod.POST.name()),newAntPathRequestMatcher(providerSettings.getTokenRevocationEndpoint(),HttpMethod.POST.name()));http.requestMatcher(requestMatcher)满足三个路径之一。组合可以实现最复杂的拦截策略。配置隔离的一些要点这里还要注意配置之间的隔离。SessionSession默认情况下,Session依赖于cookie中设置的jsessionid。如果使用session模式,必须隔离多个filter链的session存储,这样多个filter在同一个session中可以有不同的登录状态,否则他们的共享配置将被搞砸。这是因为在一个session中,默认的属性Key是SPRING_SECURITY_CONTEXT,在同一个session中获取当前context时(同一个浏览器的不同tab页):这样一来,一个登录了,其他的就认为登录了,显然不符合预期。您需要在不同的过滤器中定义不同的会话属性键。finalStringID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY="SOME_UNIQUE_KEY"HttpSessionSecurityContextRepositoryhs=newHttpSessionSecurityContextRepository();hs.setSpringSecurityContextKey(ID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY);http.securityContext().securityContextRepository(hs)无状态Token无状态Token相对简单一些,前端根据路径分开存储即可,Moreover,theTokenshouldcontaininformationaboutverifyingthefilterchaintofacilitateback-endverificationandavoidmixeduseofTokens.UserDetailsS??ervice如果你的不同端的用户是独立的,你需要实现不同的UserDetailsS??ervices,但是如果有多个UserDetailsS??ervices,不要直接在SpringIoC中注册!不要直接在SpringIoC中注册它们!不要使用它们直接注册到SpringIoC!如果一定要注册到SpringIoC,需要定义一个单独的接口,像这样:@FunctionalInterfacepublicinterfaceOAuth2UserDetailsS??ervice{UserDetailsloadOAuth2UserByUsername(Stringusername)throwsUsernameNotFoundException;}然后实现接口然后注入SpringIoC,在配置每个过滤器链的时候,你可以这样写:@Bean@Order(Ordered.HIGHEST_PRECEDENCE+2)SecurityFilterChaindefaultSecurityFilterChain(HttpSecurityhttp,OAuth2UserDetailsS??erviceoAuth2UserDetailsS??ervice)throwsSpringException{http.userDetailsS??ervice(oAuth2UserDetailsS??erviceoAuth2UserDetailsS??ervice)但SpringException{http.userDetailsS??ervice(oAuth2UserDetails)2UserAuth2UserDetailsIoC中必须有一个UserDetailsS??ervice,你必须这样写:@BeanUserDetailsS??ervicenotFoundUserDetailsS??ervice(){returnusername->{thrownewUsernameNotFoundException("Usernotfound");};}为什么不可用,因为注入到SpringIoC中的UserDetailsS??ervice是一种自下而上的实现,如果你只有一种实现,放在SpringIoC中也没什么问题。如果你想让多个各走各的路,你必须写最安全的方式,否则有一个默认的IoCnMemoryUserDetailsManager也会生效,其他成为包底的配置可以根据各自的配置进行配置。我还没有发现任何冲突。上面提到的东西在IdServer授权服务器中是这样实现的,实现授权服务器过滤、后台管理用户和前端授权用户的隔离:@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled=true)publicclassIdServerSecurityConfiguration{privatestaticfinal字符串CUSTOM_CONSENT_PAGE_URI="/oauth2/consent";privatestaticfinalStringSYSTEM_ANT_PATH="/system/**";/***常量ID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY。*/publicstaticfinalStringID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY="ID_SERVER_SYSTEM_SECURITY_CONTEXT";/***授权服务器配置**@authorfelord.cn*@since1.0.0*/@Configuration(proxyBeanMethods=false)publicstaticclassAuthorizationServerConfiguration{/***授权服务器集成优先级更高**@paramhttpthehttp*@return安全过滤器链*@throwsException异常*@since1.0.0*/@Bean("authorizationServerSecurityFilterChain")@Order(Ordered.HIGHEST_PRECEDENCE)SecurityFilterChainauthorizationServerSecurityFilterChain(HttpSecurityhttp)抛出异常//把自定义的授权确认URI加入配置authorizationServerConfigurer.authorizationEndpoint(authorizationEndpointConfigurer->authorizationEndpointConfigurer.consentPage(CUSTOM_CONSENT_PAGE_URI));RequestMatcherauthorizationServerEndpointsMatcher=authorizationServerConfigurer.getEndpointsMatcher();//拦截授权服务器相关的请求端点http.requestMatcher(authorizationServerEndpointsMatcher).authorizeRequests().anyRequest().authenticated().and()//忽略相关端点的csrf.csrf(csrf->csrf.忽略请求匹配器(作者izationServerEndpointsMatcher)).formLogin().and()//应用授权服??务器配置.apply(authorizationServerConfigurer);返回http.build();}/***配置OAuth2.0提供者元信息**@paramport端口*@return提供者设置*@since1.0.0*/@BeanpublicProviderSettingsproviderSettings(@Value("${server.port}")Integerport){//TODO配置制作要使用域名returnProviderSettings.builder().issuer("http://localhost:"+port).build();}}/***后台安全配置。**@authorfelord.cn*@since1.0.0*/@Configuration(proxyBeanMethods=false)publicstaticclassSystemSecurityConfiguration{/***管理后台以{@code/system}开头**@paramhttphttp*@返回安全过滤器链*@throwsException除了ion*@seeAuthorizationServerConfiguration*/@Bean@Order(Ordered.HIGHEST_PRECEDENCE+1)SecurityFilterChainsystemSecurityFilterChain(HttpSecurityhttp,UserInfoServiceuserInfoService)抛出异常{SimpleAuthenticationEntryPointauthenticationEntryPoint=newSimpleAuthenticationEntryPoint();AuthenticationEntryPointFailureHandlerauthenticationFailureHandler=newAuthenticationEntryPointFailureHandler(authenticationEntryPoint);HttpSessionSecurityContextRepositorysecurityContextRepository=newHttpSessionSecurityContextRepository();securityContextRepository.setSpringSecurityContextKey(ID_SERVER_SYSTEM_SECURITY_CONTEXT_KEY);http.antMatcher(SYSTEM_ANT_PATH).csrf().disable().headers().frameOptions().sameOrigin().and().securityContext().securityContextRepository(securityContextReposit)ory).and().authorizeRequests().anyRequest().authenticated()/*.and().exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)*/.and().userDetailsS??ervice(userInfoService::findByUsername).formLogin().loginPage("/system/login").loginProcessingUrl("/system/login").successHandler(newRedirectLoginAuthenticationSuccessHandler("/system")).failureHandler(authenticationFailureHandler).permitAll();返回http.build();}}/***普通用户访问安全配置。**@authorfelord.cn*@since1.0.0*/@Configuration(proxyBeanMethods=false)publicstaticclassOAuth2SecurityConfiguration{/***Defaultsecurityfilterchain安全过滤器链。**@参数httphttp*@paramoAuth2UserDetailsS??erviceoauth2用户详情服务*@paramsecurityFilterChain安全过滤器链*@return安全过滤器链*@throwsException异常*/@Bean@Order(Ordered.HIGHEST_PRECEDENCE+2)SecurityFilterChaindefaultSecurityFilterChain(HttpSecurityhttp,OAuth2UserDetailsS??erviceoAuth2UserDetailsS??ervice,@Qualifier("authorizationServerSecurityFilterChain")SecurityFilterChainsecurityFilterChain)抛出异常{DefaultSecurityFilterChainauthorizationServerFilterChain=(DefaultSecurityFilterChain)securityFilterChain;SimpleAuthenticationEntryPointauthenticationEntryPoint=newSimpleAuthenticationEntryPoint();AuthenticationEntryPointFailureHandlerauthenticationFailureHandler=新的AuthenticationEntryPointFailureHandler(authenticationEntryPoint);http.requestMatcher(newAndRequestMatcher(newNegatedRequestMatcher(newAntPathRequestMatcher(SYSTEM_ANT_PATH)),newNegatedRequestMatcher(authorizationServerFilterChain.getRequestMatcher()))).authorizeRequests(authorizeRequests->authorizeRequests.anyRequest().authenticated()).csrf().disable().userDetailsS??ervice(oAuth2UserDetailsS??ervice::loadOAuth2UserByUsername).formLogin().loginPage("/login").successHandler(newRedirectLoginAuthenticationSuccessHandler()).failureHandler(authenticationFailureHandler).permitAll().and().oauth2ResourceServer().jwt();返回http.build();}}}可以通过https://github.com/NotFound403/id-server下载源码进行改造学习,欢迎Star
