新聞中心
路徑攔截策略
在Spring Security中當(dāng)然是按照不同的請(qǐng)求路徑規(guī)則定義專(zhuān)門(mén)的過(guò)濾器鏈,你可以通過(guò)三種方式來(lái)實(shí)現(xiàn)路徑攔截。然后按照策略定義過(guò)濾器鏈即可:

成都創(chuàng)新互聯(lián)主要業(yè)務(wù)有網(wǎng)站營(yíng)銷(xiāo)策劃、成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、微信公眾號(hào)開(kāi)發(fā)、小程序定制開(kāi)發(fā)、H5開(kāi)發(fā)、程序開(kāi)發(fā)等業(yè)務(wù)。一次合作終身朋友,是我們奉行的宗旨;我們不僅僅把客戶(hù)當(dāng)客戶(hù),還把客戶(hù)視為我們的合作伙伴,在開(kāi)展業(yè)務(wù)的過(guò)程中,公司還積累了豐富的行業(yè)經(jīng)驗(yàn)、全網(wǎng)整合營(yíng)銷(xiāo)推廣資源和合作伙伴關(guān)系資源,并逐漸建立起規(guī)范的客戶(hù)服務(wù)和保障體系。
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
SecurityFilterChain systemSecurityFilterChain(HttpSecurity http) throws Exception {
// 省略
}
這三種策略介紹如下。
按照正則過(guò)濾
你可以通過(guò)HttpSecurity提供的過(guò)濾器過(guò)濾URI,例如攔截請(qǐng)求中在query參數(shù)而且包含id的URI:
http.regexMatcher("/(\\\\?|\\\\&)\" + id + \"=([^\\\\&]+)/") 這種常用來(lái)匹配一些帶參數(shù)的URL。
按照Ant規(guī)則過(guò)濾
這種是我們常見(jiàn)的方式,例如攔截/system開(kāi)頭的所有路徑:
http.antMatcher("/system/**")關(guān)于這種方式這里不再贅述,詳細(xì)可以通過(guò)Ant規(guī)則詳解這一篇來(lái)了解。
按照RequestMatcher過(guò)濾
一些復(fù)雜的組合可以通過(guò)定義RequestMatcher接口來(lái)組合,例如這種復(fù)雜的規(guī)則:
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)
滿(mǎn)足三個(gè)路徑中的一個(gè)就行,這種組合方式能夠?qū)崿F(xiàn)最復(fù)雜的攔截策略。
配置隔離的一些要點(diǎn)
這里還要注意配置之間的隔離。
Session會(huì)話(huà)
默認(rèn)情況下的Session依賴(lài)于cookie中設(shè)定的jsessionid, 如果你使用會(huì)話(huà)模式,必須隔離多個(gè)過(guò)濾器鏈的會(huì)話(huà)存儲(chǔ),這樣能夠?qū)崿F(xiàn)一個(gè)多個(gè)過(guò)濾器在同一個(gè)會(huì)話(huà)下不同的登錄狀態(tài),否則它們共享配置就會(huì)發(fā)生錯(cuò)亂。
這是因?yàn)樵谝粋€(gè)會(huì)話(huà)下,默認(rèn)的屬性Key是SPRING_SECURITY_CONTEXT,當(dāng)在同一個(gè)會(huì)話(huà)下(同一個(gè)瀏覽器不同的tab頁(yè))獲取當(dāng)前上下文都是這樣的:
// 默認(rèn) SPRING_SECURITY_CONTEXT
Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey);
這樣登錄一個(gè),其它都認(rèn)為是登錄狀態(tài),這顯然不符合預(yù)期。你需要在不同的過(guò)濾器中定義不同的會(huì)話(huà)屬性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)
無(wú)狀態(tài)Token
無(wú)狀態(tài)Token相對(duì)簡(jiǎn)單一些,前端根據(jù)路徑分開(kāi)存儲(chǔ)即可,而且Token中應(yīng)該包含校驗(yàn)過(guò)濾器鏈的信息以方便后端校驗(yàn),避免Token混用。
UserDetailsService
如果你的不同端的用戶(hù)是獨(dú)立的,你需要實(shí)現(xiàn)不同的UserDetailsService,但是存在多個(gè)UserDetailsService的話(huà),
一定不要將它們直接注冊(cè)到Spring IoC中!
一定不要將它們直接注冊(cè)到Spring IoC中!
一定不要將它們直接注冊(cè)到Spring IoC中!
如果你一定要注冊(cè)到Spring IoC,你需要定義獨(dú)立的接口,就像這樣:
@FunctionalInterface
public interface OAuth2UserDetailsService {
UserDetails loadOAuth2UserByUsername(String username) throws UsernameNotFoundException;
}
然后實(shí)現(xiàn)該接口再注入Spring IoC,每個(gè)過(guò)濾器鏈配置的時(shí)候就可以這樣寫(xiě):
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE + 2)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http,
OAuth2UserDetailsService oAuth2UserDetailsService) throws Exception {
http.userDetailsService(oAuth2UserDetailsService::loadOAuth2UserByUsername)
}
但是Spring IoC中必須有一個(gè)UserDetailsService,你得這樣寫(xiě):
@Bean
UserDetailsService notFoundUserDetailsService() {
return username -> {
throw new UsernameNotFoundException("用戶(hù)未找到");
};
}
為啥不可用,因?yàn)樽⑷隨pring IoC的UserDetailsService是一個(gè)兜底的實(shí)現(xiàn),如果你只有一個(gè)實(shí)現(xiàn),放入Spring IoC無(wú)可厚非,如果你想讓多個(gè)各自走各自的就必須這樣寫(xiě)最安全,不然還有一個(gè)默認(rèn)的InMemoryUserDetailsManager也會(huì)生效成為兜底的。
其它
其它配置按照各自的配置就行了,目前我還沒(méi)有發(fā)現(xiàn)有沖突的地方。上面所講的東西,在Id Server授權(quán)服務(wù)器中就是這樣實(shí)現(xiàn)授權(quán)服務(wù)器過(guò)濾、后臺(tái)管理用戶(hù)和前臺(tái)授權(quán)用戶(hù)三者之間隔離的:
@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";
/**
* 授權(quán)服務(wù)器配置
*
* @author felord.cn
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
public static class AuthorizationServerConfiguration {
/**
* Authorization server 集成 優(yōu)先級(jí)要高一些
*
* @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 {
OAuth2AuthorizationServerConfigurerauthorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
// 把自定義的授權(quán)確認(rèn)URI加入配置
authorizationServerConfigurer.authorizationEndpoint(authorizationEndpointConfigurer ->
authorizationEndpointConfigurer.consentPage(CUSTOM_CONSENT_PAGE_URI));
RequestMatcher authorizationServerEndpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
// 攔截 授權(quán)服務(wù)器相關(guān)的請(qǐng)求端點(diǎn)
http.requestMatcher(authorizationServerEndpointsMatcher)
.authorizeRequests().anyRequest().authenticated()
.and()
// 忽略掉相關(guān)端點(diǎn)的csrf
.csrf(csrf -> csrf
.ignoringRequestMatchers(authorizationServerEndpointsMatcher))
.formLogin()
.and()
// 應(yīng)用 授權(quán)服務(wù)器的配置
.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 配置化 生產(chǎn)應(yīng)該使用域名
return ProviderSettings.builder().issuer("http://localhost:" + port).build();
}
}
/**
* 后臺(tái)安全配置.
*
* @author felord.cn
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
public static class SystemSecurityConfiguration {
/**
* 管理后臺(tái)以{@code /system}開(kāi)頭
*
* @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();
}
}
/**
* 普通用戶(hù)訪問(wèn)安全配置.
*
* @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();
}
}
}
你可以通過(guò)https://github.com/NotFound403/id-server下載源碼進(jìn)行改造學(xué)習(xí),歡迎Star。
本文名稱(chēng):一套系統(tǒng)多套用戶(hù)安全體系該怎么辦
當(dāng)前地址:http://fisionsoft.com.cn/article/djcggse.html


咨詢(xún)
建站咨詢(xún)
