티스토리 뷰
전체적인 과정은 공식문서를 참고해서 만들어 보았다
(https://docs.spring.io/spring-authorization-server/reference/1.3/getting-started.html)
1. 의존성을 추가한다
- 전체 application.yaml 파일이 아닌 필수적인 부분만 작성
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation "org.springframework.boot:spring-boot-starter-oauth2-authorization-server"
}
2. OAuth2 엔드포인트 설정용 SecurityFilterChain 빈을 정의
@Bean
@Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http)
throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
http
.exceptionHandling((exceptions) -> exceptions
.defaultAuthenticationEntryPointFor(
new LoginUrlAuthenticationEntryPoint("/login"),
new MediaTypeRequestMatcher(MediaType.TEXT_HTML)
)
);
return http.build();
}
- Spring Security 권한서버는 몇 가지 엔드포인트를 자동으로 제공해 주는데(/oauth2/authorize, /oauth2/token 등) 이러한 기본 설정에 대한 security 기본 설정을 사용하기 위해 아래 코드를 작성한다. OIDC 기능 또한 기본 기능을 사용한다
- OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
.oidc(Customizer.withDefaults());
- 아래 applyDefaultSecurity 코드를 보면 인증설정(autenticated()), csrf 설정 등을 하는 걸 볼 수 있다
- OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
public static void applyDefaultSecurity(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
http.securityMatcher(endpointsMatcher).authorizeHttpRequests((authorize) -> ((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)authorize.anyRequest()).authenticated()).csrf((csrf) -> csrf.ignoringRequestMatchers(new RequestMatcher[]{endpointsMatcher})).apply(authorizationServerConfigurer);
}
- HTML 요청일 경우에 /login 페이지로(/login 페이지도 Spring Security가 제공한다)로 보내기 위해 exceptionHandling을 추가한다
3. 인증에 대한 처리를 정의하기 위해 SecurityFilterChain을 정의한다
- 위의 SecurityFilterChain 빈보다 나중에 호출되기 위해 @Order를 적용했다
@Bean
@Order(2)
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http)
throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
- 모든 요청에 대해 인증이 필요하도록 설정
- .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
- Spring Security의 FormLogin을 사용하도록 설정
- .formLogin(Customizer.withDefaults());
- csrf와 Basic 인증은 사용하지 않기 위해 비활성화
- .csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)
- .csrf(AbstractHttpConfigurer::disable)
4. User 정보를 조회하기 위해 UserDetailsService와 PasswordEncoder 빈 정의
@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
final String usersQuery = "select email, password, true from user where email = ?";
final String authsQuery = "select email, null from user where email = ?";
var userDetailsManager = new JdbcUserDetailsManager(dataSource);
userDetailsManager.setUsersByUsernameQuery(usersQuery);
userDetailsManager.setAuthoritiesByUsernameQuery(authsQuery);
return userDetailsManager;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
5. 인증서버를 이용할 클라이언트 서버 정보를 등록한다
@Bean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("user-service")
.clientSecret("secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
.redirectUri("http://localhost:8081/users/login")
.scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
.build();
return new InMemoryRegisteredClientRepository(oidcClient);
}
- 편의를 위해 메모리에 클라이언트 정보를 등록
- InMemoryRegisteredClientRepository(oidcClient);
- 메모리에 클라이언트를 등록하므로 id는 UUID 사용
- .withId(UUID.randomUUID().toString())
- 클라이언트 id, secret을 등록
- .clientId("user-service")
.clientSecret(passwordEncoder().encode("secret"))
- .clientId("user-service")
- 권한서버에 클라이언트를 인증할 때 기본인증(basic)을 사용
- .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
- 토큰을 얻는 방식으로 승인코드(AUTHORIZATION_CODE)와 갱신토큰(REFRESH_TOKEN) 유형을 사용
- .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
- .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
- 인증 성공 후 리다이렉트 될 클라이언트 uri를 등록
- .redirectUri("http://localhost:8081/users/login")
- 허용 스코프를 지정
- .scope(OidcScopes.OPENID)
.scope(OidcScopes.PROFILE)
- .scope(OidcScopes.OPENID)
- 공식문서에서 생략한 부분
- .postLogoutRedirectUri("http://localhost:8080/")
- 클라이언트 로그아웃을 권한서버까지 연동하지 않을 것이므로 생략했다
- .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build())
- 권한서버로부터 권한접근 허용여부를 확인하는 단계를 생략했다
- .postLogoutRedirectUri("http://localhost:8080/")
6. JWT 인증에 사용할 빈 정의
@Bean
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
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
- JWT 인증에 사용할 키를 생성하고 해당 키 정보로 Decoder 빈도 정의
7. Security의 기본 권한 서버 endpoint 설정을 그대로 사용하기 위해 AuthorizationServerSettings 빈 정의
- 따로 커스텀하는 내용 X
@Bean
public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder().build();
}
+ 권한서버의 /.well-known/openid-configuration 경로로 접속해 보면 권한 서버의 인증 관련 정보를 볼 수 있다
'Spring > Security' 카테고리의 다른 글
| [Security] hasIpAddress 사용하기 (0) | 2024.03.09 |
|---|
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크

