Spring Boot 애플리케이션에 OAuth 로그인을 추가하는 과정은 주로 Spring Security와 OAuth2 클라이언트 설정을 통해 이루어집니다. OAuth 제공자(예: Google, Facebook, Naver 등)와 통합하기 위한 일반적인 순서는 다음과 같습니다
1. 필요한 의존성 추가
Spring Boot 프로젝트에서 OAuth2 로그인을 지원하려면 Spring Security OAuth2 Client 의존성을 추가해야 합니다.
Maven:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. OAuth2 제공자 설정
OAuth2 로그인에 사용할 OAuth 제공자의 정보를 애플리케이션 설정 파일에 추가합니다. 예를 들어, Google을 사용하는 경우 아래와 같이 설정할 수 있습니다.
application.yml:
spring:
security:
oauth2:
client:
registration:
google:
client-id: {client-id}
client-secret: {client-secret}
scope: profile, email
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
authorization-grant-type: authorization_code
client-authentication-method: client_secret_basic
client-name: Google
provider:
google:
authorization-uri: https://accounts.google.com/o/oauth2/v2/auth
token-uri: https://oauth2.googleapis.com/token
user-info-uri: https://openidconnect.googleapis.com/v1/userinfo
user-name-attribute: sub
여기서, client-id, client-secret은 해당 OAuth 제공자의 개발자 콘솔에서 발급받아야 합니다.
spring.security.oauth2.client.registration.*.redirect-uri의 `baseUrl`과 `registrationId`는 Spring Security의 OAuth2 로그인 과정에서 자동으로 설정됩니다.
- baseUrl : 애플리케이션이 http://localhost:8080에서 실행 중이라면, baseUrl은 http://localhost:8080이 됩니다.
- registrationId : OAuth2 클라이언트 설정에서 각각의 OAuth 제공자를 식별하는 값입니다. (예: google, facebook, naver 등)
위 설정을 기반으로 ClientRegistration 객체를 생성하고, 이를 관리하는 ClientRegistrationRepository 빈을 자동으로 등록합니다.
- 자동 생성 Bean
- ClientRegistrationRepository: 여러 개의 클라이언트 등록 정보를 관리하는 리포지토리입니다. Spring Boot는 application.yml이나 application.properties에 설정된 내용을 바탕으로 이 빈을 자동으로 생성합니다.
- OAuth2AuthorizedClientService: OAuth2 클라이언트와 관련된 인가된 클라이언트를 관리하는 서비스입니다. 액세스 토큰, 리프레시 토큰 등을 관리합니다.
- OAuth2LoginAuthenticationFilter: OAuth2 로그인 필터로, 로그인 요청을 처리하는 필터입니다.
만약, application.yml이 아닌 java configuration으로 설정하고싶다면, 다음과 같은 방식으로 ClientRegistration과 ClientRegistrationRepository를 직접 등록할 수 있습니다.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
@Configuration
public class OAuth2ClientConfig {
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
}
private ClientRegistration googleClientRegistration() {
return ClientRegistration.withRegistrationId("google")
.clientId(clientId) // Google OAuth 클라이언트 ID
.clientSecret(clientSecret) // Google OAuth 클라이언트 Secret
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) // 인증 방식
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) // 권한 부여 방식
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}") // 리디렉션 URI
.scope("profile", "email") // 요청하는 scope (사용자 정보 범위)
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth") // 권한 승인 URI
.tokenUri("https://oauth2.googleapis.com/token") // 토큰 발급 URI
.userInfoUri("https://openidconnect.googleapis.com/v1/userinfo") // 사용자 정보 조회 URI
.userNameAttributeName(IdTokenClaimNames.SUB) // 사용자 ID로 사용할 속성
.clientName("Google") // 클라이언트 이름
.build();
}
}
3. Spring Security 설정
Spring Security 설정을 통해 OAuth2 로그인 기능을 활성화합니다.
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login").permitAll() // 로그인 페이지는 모두 접근 가능
.anyRequest().authenticated() // 그 외 모든 요청은 인증 필요
.and()
.oauth2Login() // OAuth2 로그인 활성화
.loginPage("/login") // 커스텀 로그인 페이지 설정
.defaultSuccessUrl("/home", true); // 로그인 성공 시 리다이렉트 경로 설정
}
}
- oauth2Login(): OAuth2 로그인을 활성화.
4. OAuth 로그인 버튼 추가 (프론트엔드)
로그인 페이지에 OAuth 제공자로 로그인을 위한 링크를 추가합니다. Spring Security는 기본적으로 /oauth2/authorization/{registrationId} 경로를 사용하여 해당 OAuth 제공자를 통해 로그인을 시작합니다.
HTML 예시:
<a href="/oauth2/authorization/google">Google 로그인</a>
이 링크를 통해 구글 로그인 페이지로 이동하여 인증을 시작합니다.
5. 로그인 후 사용자 정보 처리
로그인 후 사용자의 정보를 처리하기 위해, OAuth2UserService 또는 OidcUserService를 구현하여 사용자 정보를 커스터마이징할 수 있습니다.
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUserRequest;
import org.springframework.security.oauth2.core.oidc.user.OidcUserService;
import org.springframework.stereotype.Service;
/**
* OpenID Connect(OAuth2.0 기반) 로 인증 후처리 (예 - Google)
*/
@Service
public class CustomOidcUserService extends OidcUserService {
@Override
public OidcUser loadUser(OidcUserRequest userRequest) {
// ID Token 가져오기
final String idToken = userRequest.getIdToken();
// 사용자 ID 가져오기
final String userName = idToken.getSubject();
OidcUser oidcUser = super.loadUser(userRequest);
// 사용자 정보 가져오기
String email = oidcUser.getEmail();
// 사용자 정보 커스터마이징 로직 추가 가능
// ...
return oidcUser;
}
}
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
String platformUserId = this.getPlatformUserId(userRequest);
// 사용자 정보를 DB에 저장하거나 추가 로직을 처리할 수 있습니다.
return oAuth2User;
}
private String getPlatformUserId(OAuth2UserRequest userRequest, String registrationId) {
OAuth2User oAuth2User = super.loadUser(userRequest);
Map<String, Object> attributes = oAuth2User.getAttributes();
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
if (registrationId.equals(OAuth2Provider.NAVER.lowerName())) {
Map<String, Object> response = (Map<String, Object>) attributes.get(userNameAttributeName);
return (String) response.get("id");
}
return (String) attributes.get(userNameAttributeName);
}
}
6. 테스트 및 디버깅
- 애플리케이션을 실행한 후 브라우저에서 로그인 페이지로 이동하여, OAuth 제공자(Google, Naver 등)를 통해 로그인이 잘 되는지 테스트합니다.
- OAuth2 인증이 완료되면, Spring Security는 자동으로 해당 사용자의 세션을 생성하고 인증 상태를 유지합니다.
7. (선택) 리프레시 토큰 및 추가 기능 구현
필요하다면 OAuth2 리프레시 토큰을 사용하여 액세스 토큰을 갱신하거나, 추가적인 사용자 데이터 처리 로직을 구현할 수 있습니다.
구글 토큰 검증 예시:
public boolean verifySignIn(String idToken, String googleUserId) throws RuntimeException {
//Global instance of the JSON factory.
final String CLIENT_ID = clientRegistrationRepository.findByRegistrationId("google").getClientId();
final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
// Static HTTP transport factory used by the API to communicate with the Game Services API.
NetHttpTransport NET_HTTP_TRANSPORT = new NetHttpTransport();
//참고: https://developers.google.com/identity/sign-in/web/backend-auth
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(NET_HTTP_TRANSPORT, JSON_FACTORY)
// Specify the CLIENT_ID of the app that accesses the backend:
.setAudience(Collections.singletonList(CLIENT_ID))
// Or, if multiple clients access the backend: .setAudience(Arrays.asList(CLIENT_ID_1, CLIENT_ID_2, CLIENT_ID_3))
.build();
GoogleIdToken googleIdToken;
try { // 공통화 처리를 위해 check exception을 unchecked exception으로 핸들링.
googleIdToken = verifier.verify(idToken);
} catch (Exception e) {
return false;
}
if (googleIdToken == null) { //idToken검사결과 유효한 토큰이 아님
return false;
}
GoogleIdToken.Payload payload = googleIdToken.getPayload();
//동일한 userId의 토큰인지 검사
if (!StringUtils.equals(payload.getSubject(), googleUserId)) {
return false;
}
return true;
}
'spring > spring security' 카테고리의 다른 글
[SpringSecurity5] OAuth2.0로 카카오 로그인 구현 (0) | 2024.10.17 |
---|---|
[SpringSecurity5] OAuth2.0로 네이버 로그인 구현 (0) | 2024.10.17 |
Spring Security 5에서 Spring Security 6으로 변경 (URL-권한 인가) (0) | 2024.04.30 |
[Spring Security] DaoAuthenticationProvider (0) | 2023.05.09 |
[Spring Security] DelegatingPasswordEncoder와 BCryptPasswordEncoder strength에 따른 수행시간 (0) | 2022.03.20 |
댓글