Spring 授權伺服器是一個旨在實作 OAuth 2.1 和 OpenID Connect 1.0 規格以及其他相關標準的框架。它基於 Spring Security 構建,為創建符合 OpenID Connect 1.0 和 OAuth2 授權伺服器解決方案的身份提供者提供了安全、輕量級和可自訂的基礎。
功能列表
簡短回答
Spring Security 是一個功能強大且高度可自訂的身份驗證和存取控制框架。它是保護基於 Spring 的應用程式的事實標準。
本質上,Spring Security 本質上是 servlet 過濾器的集合,旨在透過強大的身份驗證和授權功能來增強您的 Web 應用程式。
Spring Security 也與 Spring Web MVC 或 Spring Boot 等框架配合,支援 OAuth2 和 SAML 等標準。它會自動產生登入和登出接口,並保護您的應用程式免受 CSRF 等常見安全漏洞的影響。
嗯,這不是很有幫助,不是嗎?
讓我們深入研究網路安全,以掌握其安全工作流程的要點。
要成為Spring Security專家,首先要掌握這三個核心概念:
注意 - 不要繞過此部分;它為所有 Spring Security 功能奠定了基礎。
您需要在線上存取您的銀行帳戶以查看餘額或進行交易。通常這是使用使用者名稱和密碼完成的
使用者:「我是 John Doe。我的使用者名稱是:johndoe1985。」
銀行系統:「請驗證您的身分。您的密碼是多少?」
使用者:「我的密碼是:secureB@nk2023。」
銀行系統:「歡迎,John Doe。這是您的帳戶概覽。」
對於基本應用程序,僅進行身份驗證就足夠了:用戶登入後,他們將被授予訪問應用程式所有區域的權限。
但是,在大多數應用程式中,都有權限或角色在發揮作用。
使用者:「讓我來玩一下該交易...」
銀行系統:「等一下,我需要先檢查您的權限…是的,John Doe 先生,您的權限等級正確。盡情享受吧。」
使用者:「我轉1M哈哈哈…開玩笑開玩笑」
現在,讓我們來探索 Servlet 濾鏡。它們與身分驗證和授權有何關係?
為什麼要使用 Servlet 濾鏡?
每個 Spring Web 應用程式都圍繞著一個 servlet 展開:值得信賴的 DispatcherServlet。它的主要作用是將傳入的 HTTP 請求(例如來自瀏覽器的請求)路由到適當的 @Controller 或 @RestController 進行處理。
事情是這樣的:DispatcherServlet 本身沒有任何內建的安全功能,您可能不想直接在 @Controller 中處理原始 HTTP Basic Auth 標頭。理想情況下,在請求到達您的 @Controllers
之前就應該處理身份驗證和授權幸運的是,在 Java Web 環境中,您可以透過在 servlet 之前放置過濾器來實現這一點。這意味著您可以考慮建立一個 SecurityFilter 並將其設定在 Tomcat(servlet 容器/應用程式伺服器)中,以在每個傳入的 HTTP 請求到達您的 servlet 之前攔截和處理它。
SecurityFilter 大約有 4 個任務
在實踐中,我們會將一個過濾器分解為多個過濾器,然後您可以將它們連結在一起。
以下是傳入 HTTP 請求的傳輸方式:
此設定稱為 FilterChain。
透過使用過濾器(或一系列過濾器),您可以有效地管理應用程式中的所有身份驗證和授權挑戰,而無需更改 @RestControllers 或 @Controllers 的核心實作。
假設您已經正確配置了 Spring Security 並啟動了您的 Web 應用程式。您會注意到一條日誌訊息,如下所示:
2020-02-25 10:24:27.875 INFO 11116 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@46320c9a, org.springframework.security.web.context.SecurityContextPersistenceFilter@4d98e41b, org.springframework.security.web.header.HeaderWriterFilter@52bd9a27, org.springframework.security.web.csrf.CsrfFilter@51c65a43, org.springframework.security.web.authentication.logout.LogoutFilter@124d26ba, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@61e86192, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@10980560, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@32256e68, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@52d0f583, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5696c927, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5f025000, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5e7abaf7, org.springframework.security.web.session.SessionManagementFilter@681c0ae6, org.springframework.security.web.access.ExceptionTranslationFilter@15639d09, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4f7be6c8]|
擴充這一行表示 Spring Security 不僅僅添加一個過濾器,它還設定了一個包含 15 個(!)不同過濾器的完整過濾器鏈。
當 HTTP 請求到達時,它會依次通過這 15 個過濾器中的每一個,然後最終到達您的 @RestControllers。這些過濾器的順序至關重要,因為請求是從鏈的頂部到底部處理的。
深入研究鏈中每個過濾器的細節會讓我們走得太遠,但這裡是一些關鍵過濾器的解釋。為了更深入地了解其他內容,您可以探索 Spring Security 的原始程式碼。
問題 - 為什麼 HTTP 請求會被 Spring Security 過濾器破壞?
回答 - 因為每次它試圖靠近時,過濾器都會說:「等一下!讓我先檢查一下你!」 ?是的,休息......哇,等等......這次的安全討論太多了!
開始使用 Spring 授權伺服器的最簡單方法是建立基於 Spring Boot 的應用程式。您可以使用start.spring.io產生一個基本專案。
唯一需要的依賴是實作(“org.springframework.boot:spring-boot-starter-oauth2-authorization-server”)
我們將增加另外兩個來執行更多操作
2020-02-25 10:24:27.875 INFO 11116 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@46320c9a, org.springframework.security.web.context.SecurityContextPersistenceFilter@4d98e41b, org.springframework.security.web.header.HeaderWriterFilter@52bd9a27, org.springframework.security.web.csrf.CsrfFilter@51c65a43, org.springframework.security.web.authentication.logout.LogoutFilter@124d26ba, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@61e86192, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@10980560, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@32256e68, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@52d0f583, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5696c927, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5f025000, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5e7abaf7, org.springframework.security.web.session.SessionManagementFilter@681c0ae6, org.springframework.security.web.access.ExceptionTranslationFilter@15639d09, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4f7be6c8]|
如何設定 Spring Security
使用最新的 Spring Security 和/或 Spring Boot 版本,設定 Spring Security 的方法是使用一個類別: 使用 @EnableWebSecurity 註解。
dependencies { implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server") implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-validation") }
(1):協定端點的 Spring Security 過濾器鏈。
(2) :用於身份驗證的 Spring Security 過濾器鏈。
(3) : com.nimbusds.jose.jwk.source.JWKSource 的實例,用於簽署存取權杖。
(4) : 用於解碼簽章存取權杖的 JwtDecoder 實例。
(5) : AuthorizationServerSettings 實例,用於設定 Spring Authorization Server。
讓我們設定 CORS 以允許某些 URL 存取我們的應用程式
2020-02-25 10:24:27.875 INFO 11116 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@46320c9a, org.springframework.security.web.context.SecurityContextPersistenceFilter@4d98e41b, org.springframework.security.web.header.HeaderWriterFilter@52bd9a27, org.springframework.security.web.csrf.CsrfFilter@51c65a43, org.springframework.security.web.authentication.logout.LogoutFilter@124d26ba, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@61e86192, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@10980560, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@32256e68, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@52d0f583, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5696c927, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5f025000, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5e7abaf7, org.springframework.security.web.session.SessionManagementFilter@681c0ae6, org.springframework.security.web.access.ExceptionTranslationFilter@15639d09, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4f7be6c8]|
Cors配置
該類別用於定義CORS規則。在這種情況下:
UrlBasedCorsConfigurationSource
哇,配置太多了!但這就是 Spring 框架的魔力——它處理幕後的所有繁重工作。
dependencies { implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server") implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-validation") }
上面我們做了一些事情
UserDetailsService 由 DaoAuthenticationProvider 用於擷取 使用者名稱、密碼以及其他用於使用使用者名稱和密碼進行驗證的屬性。 Spring Security 提供了 UserDetailsService 的記憶體、JDBC 和快取實作。
您可以透過將自訂 UserDetailsService 公開為 bean 來定義自訂驗證。
@Configuration @EnableWebSecurity public class SecurityConfig { private static final String[] ALLOW_LIST = {"/oauth2/token", "/userinfo"}; //This is primarily configured to handle OAuth2 and OpenID Connect specific endpoints. It sets up the security for the authorization server, handling token endpoints, client authentication, etc. @Bean (1) @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer.authorizationServer(); http .cors(Customizer.withDefaults()) .authorizeHttpRequests(authz -> authz .requestMatchers(ALLOW_LIST).permitAll() .requestMatchers("/**", "/oauth2/jwks/").hasAuthority("SCOPE_keys.write") .anyRequest() .authenticated()) .securityMatchers(matchers -> matchers.requestMatchers(antMatcher("/oauth2/**"), authorizationServerConfigurer.getEndpointsMatcher())) .with(authorizationServerConfigurer, (authorizationServer) -> authorizationServer .oidc(Customizer.withDefaults())) // Enable OpenID Connect 1.0 // Redirect to the login page when not authenticated from the // authorization endpoint .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) )) // Accept access tokens for User Info and/or Client Registration .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults())); return http.build(); } // This configuration is set up for general application security, handling standard web security features like form login for paths not specifically managed by the OAuth2 configuration. @Bean (2) @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/login", "/error", "/main.css") .permitAll() .anyRequest() .authenticated() ) // Form login handles the redirect to the login page from the // authorization server filter chain .formLogin((login) -> login.loginPage("/login")); return http.build(); } @Bean (3) public JWKSource<SecurityContext> jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet<>(jwkSet); } private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; } @Bean (4) public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean (5) public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings .builder() .build(); } }
一旦我們啟動應用程序,我們的 OIDC 和 OAuth2 設定與 Spring 授權伺服器應該可以正常運作。但是,您會注意到我們使用了 InMemoryUserDetailsManager,它非常適合演示或原型設計。但對於生產環境,這是不可取的,因為應用程式重新啟動後所有資料都會消失。
JdbcUserDetailsManager 是 Spring Security 中的一項功能,它使用 JDBC 透過連接到關聯式資料庫來處理使用者憑證和角色。當您的應用程式可以使用 Spring Security 期望的使用者表的標準架構時,這是理想的選擇。
可從 Spring security org/springframework/security/core/userdetails/jdbc/users.ddl
取得的架構
@Configuration public class CorsConfig { @Bean public UrlBasedCorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.addAllowedOrigin("http://localhost:3000/"); // Change to specific domains in production configuration.addAllowedMethod("*"); configuration.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
從 InMemoryUserDetailsManager 轉換到 JdbcUserDetailsManager 所需的唯一調整
2020-02-25 10:24:27.875 INFO 11116 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@46320c9a, org.springframework.security.web.context.SecurityContextPersistenceFilter@4d98e41b, org.springframework.security.web.header.HeaderWriterFilter@52bd9a27, org.springframework.security.web.csrf.CsrfFilter@51c65a43, org.springframework.security.web.authentication.logout.LogoutFilter@124d26ba, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@61e86192, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@10980560, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@32256e68, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@52d0f583, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5696c927, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@5f025000, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@5e7abaf7, org.springframework.security.web.session.SessionManagementFilter@681c0ae6, org.springframework.security.web.access.ExceptionTranslationFilter@15639d09, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@4f7be6c8]|
此配置對於堅持 Spring Security 標準表模式的應用程式有效。但是,如果您需要自訂(例如使用電子郵件而不是使用者名稱登入),則實作自訂 UserDetailsService 可提供必要的適應性。
讓我們在提供者中新增自訂 CustomUserDetailservice。在 AuthenticationProvider 中使用 setUserDetailsService
設定自訂服務
dependencies { implementation("org.springframework.boot:spring-boot-starter-oauth2-authorization-server") implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("org.springframework.boot:spring-boot-starter-validation") }
客製化服務
@Configuration @EnableWebSecurity public class SecurityConfig { private static final String[] ALLOW_LIST = {"/oauth2/token", "/userinfo"}; //This is primarily configured to handle OAuth2 and OpenID Connect specific endpoints. It sets up the security for the authorization server, handling token endpoints, client authentication, etc. @Bean (1) @Order(1) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer.authorizationServer(); http .cors(Customizer.withDefaults()) .authorizeHttpRequests(authz -> authz .requestMatchers(ALLOW_LIST).permitAll() .requestMatchers("/**", "/oauth2/jwks/").hasAuthority("SCOPE_keys.write") .anyRequest() .authenticated()) .securityMatchers(matchers -> matchers.requestMatchers(antMatcher("/oauth2/**"), authorizationServerConfigurer.getEndpointsMatcher())) .with(authorizationServerConfigurer, (authorizationServer) -> authorizationServer .oidc(Customizer.withDefaults())) // Enable OpenID Connect 1.0 // Redirect to the login page when not authenticated from the // authorization endpoint .exceptionHandling((exceptions) -> exceptions .defaultAuthenticationEntryPointFor( new LoginUrlAuthenticationEntryPoint("/login"), new MediaTypeRequestMatcher(MediaType.TEXT_HTML) )) // Accept access tokens for User Info and/or Client Registration .oauth2ResourceServer((oauth2) -> oauth2.jwt(Customizer.withDefaults())); return http.build(); } // This configuration is set up for general application security, handling standard web security features like form login for paths not specifically managed by the OAuth2 configuration. @Bean (2) @Order(2) public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests((authorize) -> authorize .requestMatchers("/login", "/error", "/main.css") .permitAll() .anyRequest() .authenticated() ) // Form login handles the redirect to the login page from the // authorization server filter chain .formLogin((login) -> login.loginPage("/login")); return http.build(); } @Bean (3) public JWKSource<SecurityContext> jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet<>(jwkSet); } private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; } @Bean (4) public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean (5) public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings .builder() .build(); } }
儲存庫
@Configuration public class CorsConfig { @Bean public UrlBasedCorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.addAllowedOrigin("http://localhost:3000/"); // Change to specific domains in production configuration.addAllowedMethod("*"); configuration.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } }
實體
@Configuration public class Clients { @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("stomble") .clientAuthenticationMethod(ClientAuthenticationMethod.NONE) .authorizationGrantTypes(types -> { types.add(AuthorizationGrantType.AUTHORIZATION_CODE); types.add(AuthorizationGrantType.REFRESH_TOKEN); }) .redirectUris(redirectUri -> { redirectUri.add("http://localhost:3000"); redirectUri.add("https://oauth.pstmn.io/v1/callback"); redirectUri.add("http://localhost:3000/signin-callback"); }) .postLogoutRedirectUri("http://localhost:3000") .scopes(score -> { score.add(OidcScopes.OPENID); score.add(OidcScopes.PROFILE); score.add(OidcScopes.EMAIL); }) .clientSettings(ClientSettings.builder() .requireAuthorizationConsent(false) .requireProofKey(true) .build()) .build(); return new InMemoryRegisteredClientRepository(oidcClient); } }
在安全過濾器中,我們必須告訴 spring security 使用此服務
.clientAuthentication(clientAuth -> clientAuth.authenticationProvider(authenticationProvider))
@Configuration public class UserConfig { @Bean public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) { UserDetails userDetailFirst = User.builder() .username("user1") .password(passwordEncoder.encode("password")) .roles("USER") .build(); UserDetails userDetailSecond = User.builder() .username("user2") .password(passwordEncoder.encode("password")) .roles("USER") .build(); return new InMemoryUserDetailsManager(List.of(userDetailFirst, userDetailSecond)); } } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
在這裡,您有兩個強大的選擇來處理身份驗證:
無論您選擇 JdbcUserDetailsManager 或決定實作自訂 UserDetailsService,兩者都將為您的應用程式配備可擴充的、資料庫支援的驗證系統。
以上是Spring 授權伺服器 spring security 具有自訂使用者詳細資訊服務,用於靈活的資料驅動身份驗證的詳細內容。更多資訊請關注PHP中文網其他相關文章!