Spring 인증 서버는 기타 관련 표준과 함께 OAuth 2.1 및 OpenID Connect 1.0 사양을 구현하도록 설계된 프레임워크입니다. Spring Security를 기반으로 구축되어 OpenID Connect 1.0 및 OAuth2 인증 서버 솔루션을 준수하는 ID 공급자를 생성하기 위한 안전하고 가벼우며 사용자 정의 가능한 기반을 제공합니다.
기능 목록
단답형
Spring Security는 강력하고 사용자 정의가 가능한 인증 및 액세스 제어 프레임워크입니다. 이는 Spring 기반 애플리케이션 보안을 위한 사실상의 표준입니다.
Spring Security의 핵심은 강력한 인증 및 권한 부여 기능으로 웹 애플리케이션을 향상시키도록 설계된 서블릿 필터 모음입니다.
Spring Security는 또한 Spring Web MVC 또는 Spring Boot와 같은 프레임워크와 잘 어울리며 OAuth2 및 SAML과 같은 표준을 지원합니다. 자동으로 로그인 및 로그아웃 인터페이스를 생성하고 CSRF와 같은 일반적인 보안 취약성으로부터 애플리케이션을 보호합니다.
글쎄 별로 도움이 안 되는군요.
웹 보안에 대해 자세히 알아보고 보안 워크플로우의 핵심을 파악해 보겠습니다.
Spring Security 전문가가 되려면 먼저 다음 세 가지 핵심 개념을 이해해야 합니다.
참고 - 이 섹션을 건너뛰지 마세요. 이는 모든 Spring Security 기능의 기반을 마련합니다.
잔고를 확인하거나 거래하려면 온라인으로 은행 계좌에 접속해야 합니다. 일반적으로 이는 사용자 이름과 비밀번호를 사용하여 수행됩니다
사용자: "저는 John Doe입니다. 제 사용자 이름은 johndoe1985입니다."
은행 시스템: "신원을 확인하세요. 비밀번호는 무엇입니까?"
사용자: "내 비밀번호는 secureB@nk2023입니다."
은행 시스템: "환영합니다, John Doe. 계좌 개요는 다음과 같습니다."
기본 애플리케이션의 경우 인증만으로 충분할 수 있습니다. 사용자가 로그인하면 애플리케이션의 모든 영역에 대한 액세스 권한이 부여됩니다.
그러나 대부분의 애플리케이션에는 권한이나 역할이 있습니다.
사용자: "그 거래를 플레이해 보겠습니다…."
은행 시스템: "잠깐만요, 먼저 귀하의 권한을 확인해야 합니다…
사용자: "100만 이체하겠습니다 하하하… 농담 농담"
서블릿 필터를 사용하는 이유는 무엇입니까?
모든 Spring 웹 애플리케이션은 단일 서블릿, 즉 신뢰할 수 있는 DispatcherServlet을 중심으로 회전합니다. 주요 역할은 들어오는 HTTP 요청(예: 브라우저의 요청)을 적절한 @Controller 또는 @RestController로 라우팅하여 처리하는 것입니다.
여기에 거래가 있습니다. DispatcherServlet 자체에는 보안 기능이 내장되어 있지 않으며 @Controller에서 직접 원시 HTTP 기본 인증 헤더를 처리하고 싶지 않을 것입니다. 이상적으로는 요청이 @Controller에 도달하기 전에 인증 및 승인이 처리되어야 합니다
다행히도 Java 웹 환경에서는 서블릿 앞에 필터를 배치하여 이를 달성할 수 있습니다. 즉, SecurityFilter를 생성하고 이를 Tomcat(서블릿 컨테이너/애플리케이션 서버)에 설정하여 들어오는 모든 HTTP 요청이 서블릿에 도달하기 전에 가로채서 처리할 수 있습니다.
SecurityFilter에는 대략 4가지 작업이 있습니다.
실제로는 단일 필터를 여러 개로 나눈 다음 서로 연결합니다.
수신 HTTP 요청이 이동하는 방식은 다음과 같습니다.
이 설정을 FilterChain이라고 합니다.
필터(또는 필터 체인)를 사용하면 @RestController 또는 @Controller의 핵심 구현을 변경하지 않고도 애플리케이션의 모든 인증 및 권한 부여 문제를 효과적으로 관리할 수 있습니다.
Spring Security를 올바르게 구성하고 웹 애플리케이션을 시작했다고 상상해 보세요. 다음과 같은 로그 메시지가 표시됩니다.
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 요청이 도착하면 @RestController에 도달하기 전에 순서대로 15개의 필터를 각각 통과합니다. 요청이 체인의 위에서 아래로 처리되므로 이러한 필터의 순서는 매우 중요합니다.
체인의 모든 필터에 대해 자세히 알아보려면 너무 많은 시간이 걸리겠지만 다음은 몇 가지 주요 필터에 대한 설명입니다. 다른 것에 대한 더 깊은 이해를 위해 Spring Security의 소스 코드를 탐색할 수 있습니다.
질문 - HTTP 요청이 왜 Spring Security 필터와 충돌하나요?
Spring 인증 서버 설정
답변 - 가까워지려고 할 때마다 필터가 "잠깐! 먼저 확인해볼게!"라고 했기 때문이죠. ?
필요한 유일한 종속성은 구현("org.springframework.boot:spring-boot-starter-oauth2-authorization-server")입니다
더 많은 액션을 위해 2개를 더 추가할 예정입니다
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 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") }
(3) : 액세스 토큰 서명을 위한 com.nimbusds.jose.jwk.source.JWKSource의 인스턴스.
(4) : 서명된 액세스 토큰을 디코딩하기 위한 JwtDecoder의 인스턴스입니다.
(5) : Spring Authorization Server를 구성하기 위한 AuthorizationServerSettings의 인스턴스입니다.
애플리케이션에 특정 URL을 허용하도록 CORS를 구성할 수 있습니다
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 Framework의 마법입니다. 이는 뒤에서 모든 무거운 작업을 처리합니다.
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(); } }
애플리케이션을 실행하면 Spring Authorization Server를 사용한 OIDC 및 OAuth2 설정이 올바르게 작동해야 합니다. 그러나 데모나 프로토타입 제작에 적합한 InMemoryUserDetailsManager를 사용했다는 것을 알 수 있습니다. 하지만 프로덕션 환경에서는 애플리케이션을 다시 시작하면 모든 데이터가 사라지므로 권장되지 않습니다.
JdbcUserDetailsManager는 JDBC를 사용하여 관계형 데이터베이스에 연결하여 사용자 자격 증명과 역할을 처리하는 Spring Security의 기능입니다. 귀하의 애플리케이션이 Spring Security가 기대하는 사용자 테이블에 대한 표준 스키마와 함께 작동할 수 있을 때 이상적입니다.
Spring 보안 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를 구현하면 필요한 적응성이 제공됩니다.
공급자에 맞춤 CustomUserDetailsService를 추가해 보겠습니다. 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 인증 서버 스프링 보안의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!