org.springframework.boot:spring-boot-starter-oauth2-client 를 통해 스프링 시큐리티에서 기본적으로 제공하는 OAuth2.0 프로토콜을 사용한다.
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
보안 구성 및 인증/인가 설정을 구성한다. 본 코드는 구글을 기준으로 작성되었다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Value("${spring.security.oauth2.client.registration.google.client-id}")
private String clientId;
@Value("${spring.security.oauth2.client.registration.google.client-secret}")
private String clientSecret;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/oauth/**").permitAll()
.anyRequest().permitAll()
)
.oauth2Login(withDefaults());
return http.build();
}
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(this.googleClientRegistration());
}
@Bean
public ClientRegistration googleClientRegistration() {
Map<String, Object> configMap = new HashMap<>();
configMap.put("access_type", "offline");
return ClientRegistration
.withRegistrationId("google")
.clientId(clientId)
.clientSecret(clientSecret)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("http://localhost:8080/oauth/redirect/google")
.scope("profile", "email")
.authorizationUri("https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent")
.tokenUri("https://www.googleapis.com/oauth2/v4/token")
.userInfoUri("https://www.googleapis.com/oauth2/v3/userinfo")
.userNameAttributeName(IdTokenClaimNames.SUB)
.jwkSetUri("https://www.googleapis.com/oauth2/v3/certs")
.clientName("Google")
.build();
}
}
- Registration ID: 클라이언트 등록을 식별하는 고유한 식별자
- Client ID: 클라이언트 애플리케이션의 id
- Client Secret: 클라이언트 애플리케이션의 secret key
- Client Authentication Method: 클라이언트 인증 방법
- Authorization Grant Type: 클라이언트가 액세스 토큰을 요청할 때 사용할 인가 유형
- Redirect URI: 사용자 인증 후에 리디렉션되는 URI
- Scope: 클라이언트가 요청할 수 있는 권한 범위
- Authorization URI: 인가 코드를 받기 위한 엔드포인트 URI
- Token URI: 액세스 토큰을 요청하기 위한 엔드포인트 URI
- User Info URI: 사용자 정보 엔드포인트 URI
- User Name Attribute Name: 사용자의 고유 식별자 속성 이름
- JWK Set URI: Json Web Key Set 엔드포인트 URI
- Client Name: 클라이언트의 이름 또는 제목
엔드포인트의 실제 동작과 관련된 로직을 Service에 구현하기 위해 매핑해준다.
@RestController
@RequestMapping("/oauth")
@RequiredArgsConstructor
public class OAuthController {
private final OAuthService oauthService;
@GetMapping("/redirect/{registrationId}")
public void loadUser(@RequestParam String code, @PathVariable String registrationId) {
oauthService.socialLogin(code, registrationId);
}
}
로그인이 정상적으로 이루어졌다면 code에는 인증 코드가, registrationId에는 로그인을 지원해준 서비스가 넘어오게 된다. 해당 코드에서는 google을 받아오게 된다.
실질적인 동작 부분을 구현한다.
@Service
@RequiredArgsConstructor
public class OAuthService {
private final RestTemplate restTemplate = new RestTemplate();
private final ClientRegistration googleClientRegistration;
public void socialLogin(String code, String registrationId) {
JsonNode tokens = getToken(code, registrationId);
String accessToken = tokens.get("access_token").asText();
String refreshToken = tokens.get("refresh_token").asText();
JsonNode userInfo = getUserResource(accessToken);
}
private JsonNode getToken(String code, String registrationId) {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("prompt", "consent");
params.add("client_id", googleClientRegistration.getClientId());
params.add("client_secret", googleClientRegistration.getClientSecret());
params.add("redirect_uri", googleClientRegistration.getRedirectUri());
params.add("code", code);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(params, headers);
String tokenUri = googleClientRegistration.getProviderDetails().getTokenUri();
ResponseEntity<JsonNode> response = restTemplate.exchange(
tokenUri,
HttpMethod.POST,
entity,
JsonNode.class);
return response.getBody();
}
private JsonNode getUserResource(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);
HttpEntity entity = new HttpEntity(headers);
String userInfoUri = googleClientRegistration.getProviderDetails().getUserInfoEndpoint().getUri();
return restTemplate.exchange(userInfoUri,
HttpMethod.GET,
entity,
JsonNode.class).getBody();
}
}
- getToken: access token과 refresh token을 Json객체로 받아온다.
- getUserResource: 유저 정보를 Json 객체로 받아온다.