Step
Step 1. OAuth2 개요 : https://radpro.tistory.com/316
Step 2. SpringSecurity에서 OAuth2 인증 : 현재글
Step 3. OAuth2 적용 : https://radpro.tistory.com/321
사전작업
: 구글 API 콘솔에서 OAuth2 설정
링크 : https://console.cloud.google.com/apis/dashboard?project=analog-daylight-363906
1. 프로젝트 생성 => 프로젝트 이름 설정( 자율 )
2. OAuth 동의 화면
1) 외부 선택 => 만들기
2) 앱 이름 / 사용자 지원 이메일 / 개발자 연락처 정보 입력 => 저장 후 계속
3) 테스트 사용자 화면에서 => 저장 후 계속
3. 사용자 인증 정보 생성
1) 사용자 인증 정보 만들기 => OAuth 클라이언트 ID
2) 애플리케이션 유형 ( 웹 애플리케이션 ) / 이름 ( 자율 ) / URI 추가 ( http://localhost:8080/login/oauth2/code/google )
3) 만들기
4. 클라이언트ID / 클라이언트 보안 비밀번호 생성
: 사용자 인증 정보 => OAuth2.0 클라이언트 ID의 해당 이름 들어가면, 우측 상단에서 조회가능
Build.gradle
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // (1)
implementation 'org.springframework.boot:spring-boot-starter-security' // (2)
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' // (3)
}
1. 타임리프 : SSR 방식에서 사용. 만약 CSR 방식을 사용할 것이라면 필요 없음
2. 시큐리티 : Spring Security기반 애플리케이션이므로 추가
3. OAuth2-client : 구글의 OAuth 2 클라이언트이므로 추가
코드 구성
1. SSR용 HTML 페이지 생성 : hello-oauth2.html ( 경로 : src/main/resources/templates )
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Welcome to Hello OAuth 2.0</title>
</head>
<body>
<div style="text-align: center"><h2>Welcome to Hello OAuth 2.0!!</h2></div>
</body>
</html>
2. HelloHomeController : html 화면에 대한 뷰를 리턴하는 컨트롤러
: String 타입으로 뷰의 이름을 리턴하는 SSR 방식의 핸들러 메서드가 있음
: GetMapping을 통해 웹 브라우저로 전송
package com.codestates.home;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class HelloHomeController {
@GetMapping("/hello-oauth2")
public String home() {
return "hello-oauth2";
}
}
3. SecurityConfiguration : OAuth2 로그인 설정 추가
: dependancies에 불러온 아래의 코드덕분에 자동으로 ClientRegistrationRepository를 생성 및 Bean등록 해진거임
: 수동은 아래의 5번(SecurityConfigurationVer2)참고
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
package com.codestates.hello_oauth2.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.oauth2Login(withDefaults());
return http.build();
}
}
4. application.yml : API로 받은 clientId / clientSecret 추가
: 보안상 실제 프로젝트에선 환경변수에다 설정하거나, 따로 관리해야함
spring:
...
security:
oauth2:
client:
registration:
google:
clientId: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
clientSecret: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
...
5. SecurityConfigurationVer2 :
package com.codestates.hello_oauth2.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
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.web.SecurityFilterChain;
@Configuration
public class SecurityConfiguration {
// --- 클라이언트 ID/PW 로드 (from yml) --------------------------------
@Value("${spring.security.oauth2.client.registration.google.clientId}")
private String clientId;
@Value("${spring.security.oauth2.client.registration.google.clientSecret}")
private String clientSecret;
...
// --- 클라이언트 등록 레파지토리를 Bean으로 등록 --------------------------
// 만약 SpringSecurity 자동 구성 기능 이용시, yml에 설정된 구글의 클라이언트 Id/Secret 정보 기반으로
// ClientRegistrationRepository Bean 자동 생성
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
// clientRegistration()을 호출해서 clientRegistration인스턴스 받음
var clientRegistration = clientRegistration();
// 인메모리~~ 가 clientRegistration을 저장
return new InMemoryClientRegistrationRepository(clientRegistration);
}
private ClientRegistration clientRegistration() {
return CommonOAuth2Provider // Spring Security에서는 CommonOAuth2Provider라는 enum을 제공
.GOOGLE // CommonOAuth2Provider 는 내부적으로 Builder 패턴을 이용해 ClientRegistration 인스턴스를 제공하는 역할
.getBuilder("google")
.clientId(clientId)
.clientSecret(clientSecret)
.build();
}
}
웹 브라우저에서 확인
로컬 링크 : localhost:8080/hello-oauth2
: 익히 알고 있는 Google 계정으로 로그인 화면이 나온다
*궁금한 점 : 로그아웃은??
Check 1 : 인증된 Authentication이 사용자 정보를 잘 포함하고 있는지 확인하는 방법
1. SecurityContext 이용방법 : HomeController의 home() 헨들러 메서드에 인증된 사용자 정보를 얻는 코드
1) SecurotyContext에서 인증된 Authentication 객체를 통해 Principal 객체를 얻음
2) OAuth2User 객체에 저장되어있는 사용자 정보중 원하는거 빼옴
* 웹브라우저에서 home()핸들러 메서드로 request전송시, OAuth2 인증 성공 전까지는 home()메서드가 호출되지 않음
=> 따라서, Context에서 인증된 Authentication을 얻어 출력하면, OAuth2가 성공했기 때문인것임
@Controller
public class HelloHomeControllerV2 {
@GetMapping("/hello-oauth2")
public String home() {
// ----- 이 부분 두 줄 -----
var oAuth2User = (OAuth2User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println(oAuth2User.getAttributes().get("email"));
// ----- 여기까지 ---------
return "hello-oauth2";
}
}
2. Authentication 객체를 핸들러 메서드 파라미터로 전달 받는 방법 : HomeController내 home() 메서드에 전달
@Controller
public class HelloHomeController {
@GetMapping("/hello-oauth2")
public String home(Authentication authentication) { <= 파라미터로 Authetication 전달
var oAuth2User = (OAuth2User)authentication.getPrincipal(); // <=파라미터로 받은것에서 바로 정보 추출
System.out.println(oAuth2User);
System.out.println("User's email in Google: " + oAuth2User.getAttributes().get("email"));
return "hello-oauth2";
}
}
3. OAuth2User를 파라미터로 전달 받는 방법 : @AuthenticationPrincipal 애너테이션을 이용해 OAuth2User 직접 전달
@Controller
public class HelloHomeControllerV4 {
@GetMapping("/hello-oauth2")
public String home (@AuthenticationPrincipal OAuth2User oAuth2User) {
System.out.println("User's email in Google: "+oAuth2User.getAttributes().get("email"));
return "hello-oauth2";
}
}
Check 2 : Authorization Server로부터 전달 받은 Access Token 확인
1. OAuth2 AuthorizedClientService를 DI 받아 Access Token 정보 보는 방법
: HelloHomeController 수정
1) AuthorizedClientService DI : private final 객체 선언 및 @RequireArgsConstructor 애너테이션 사용
2) 클라이언트 객체 선언
3) 클라이언트 객체로 AccessToken 발급
4) Access Token으로 토큰정보 로그 찍어보기 ( 로그인 성공시 자동 찍힘 )
5) 로그인은 우측 링크에서 =>=> localhost:8080/hello-oauth2
@RequiredArgsConstructor
@Controller
public class HelloHomeControllerV5 {
// OAuth2AuthorizedClientService 인터페이스를 DI 받음
private final OAuth2AuthorizedClientService authorizedClientService;
@GetMapping("/hello-oauth2")
public String home(Authentication authentication) {
// 인가받은 클라이언트 객체를 생성. DI받은 인터페이스를 통해 인증받은 아이디를 로드받음
var authorizedClient = authorizedClientService.loadAuthorizedClient("google", authentication.getName());
// 인증받은 아이디를 포함한 클라이언트 객체를 통해 Access Token을 받아옴
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
// 이제 Access 토큰에 대한 각종 정보들을 빼먹을 차례
System.out.println("Access Token Value: " + accessToken.getTokenValue());
System.out.println("Access Token Type: " + accessToken.getTokenType().getValue());
System.out.println("Access Token Scopes: " + accessToken.getScopes());
System.out.println("Access Token Issued At: " + accessToken.getIssuedAt());
System.out.println("Access Token Expires At: " + accessToken.getExpiresAt());
return "hello-oauth2";
}
}
6) 결과 로그 : 대충 요로콤 찍힘
2. OAuth2AuthorizedClient를 핸들러 메서드의 파라미터로 전달받는 방법
: 말 그대로 OAuth2AuthorizedClient를 메서드의 파라미터로 넣는다
: 이때, @RegisteredOAuth2AuthorizedClient("") 애너테이션을 같이 넣어준다.
: 애너테이션 괄호 안에는 clientRegistrationId가 들어간다 ex) google, github 등..
: 이 경우, 객체 선언 없이 파라미터로 전달받은 클라이언트에서 바로 토큰을 뽑아쓴다
: 결과 로그는 위의 DI 방식과 같다
@GetMapping("/hello-oauth2")
public String home (@RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient) {
// 파라미터로 객체를 바로 받아 오기 때문에, 전달받은 파라미터인 클라이언트에서 바로 Access Token을 뽑아 쓸 수 있다.
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
System.out.println("Access Token Value: " + accessToken.getTokenValue());
System.out.println("Access Token Type: " + accessToken.getTokenType().getValue());
System.out.println("Access Token Scopes: " + accessToken.getScopes());
System.out.println("Access Token Issued At: " + accessToken.getIssuedAt());
System.out.println("Access Token Expires At: " + accessToken.getExpiresAt());
return "hello-oauth2";
}
'Codestates [Back-end] > 데일리 로그 [TIL]' 카테고리의 다른 글
22.09.30 Cloud - 개념 (0) | 2022.09.30 |
---|---|
22.09.29 OAuth2 - Step 3. OAuth2 적용 (0) | 2022.09.29 |
22.09.28 OAuth2 - Step 1. 개요 (0) | 2022.09.28 |
22.09.27 메모 (0) | 2022.09.27 |
22.09.27 JWT - SpringSecurity 인증 (Step 3. 자격증명 및 검증 구현) [진행중] (0) | 2022.09.27 |