📍구글 로그인 준비
이 링크로 들어와서
Google 클라우드 플랫폼
로그인 Google 클라우드 플랫폼으로 이동
accounts.google.com
1. 프로젝트 생성
spring-oauth-google이라는 프로젝트를 하나 만들었다.
2. OAuth 동의 화면 만들기
동의화면 만들어줌 외부로!
3. 사용자 인증정보 만들기
사용자 인증정보에 들어가서 사용자 인증정보 만들기 클릭
OAuth 클라이언트 ID선택
승인된 리디렉션 url : http://localhost:8080/login/oauth2/code/google
- 구글 로그인을 완료하고 나면 서버쪽에서 우리쪽으로 code를 넘겨주기 때문에 이 코드를 통해서 액세스 토큰을 받게 되고 정보 접근 권한을 가지게 된다 .
- 이름 : spring-oauth
- 어플리케이션 유형 : 웹 애플리케이션
+) OAuth 참고자료
OAuth
나는 한명인데 인터넷에서 내 정보는 가입한 사이트의 개수만큼 존재한다. 이 말은 내 개인정보가 여러 군데 노출되어있다!→ 한군데서 관리하는게 더 효과적이지 않을까? naver, 카카오 등 대
88dldl.tistory.com
authclient라는 라이브러리를 사용하기 때문에 이 주소는 고정이다!
→ 카카오는 이 라이브러리로 구현불가기때문에 직접 하나씩 다 구현해줘야해서 저 주소가 크게 의미가 없다.
구글이나 페이스북에서 http://localhost:8080/login/oauth2/code이까지는 고정이다.
⇒ 이 말은 컨트롤러가 필요없다는 말!!
생성하고 나면
이렇게 클라이언트의 아이디와 비밀번호가 뜬다.
📍의존성 추가
pom.xml파일에서 의존성을 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
application.yml파일에 설정추가
spring:
security:
oauth2:
client:
registration:
google:
client-id: {id}
client-secret: {secret}
scope: email,profile
Scope는 리소스 오너(즉 고객)이, 클라이언트(스프링서버)에게 구글이 들고 있는 자원들중에 어떤 자원을 위임해줄 수 있는지를 설정하는 범위이다.
로그인폼.html 추가
<a href="/oauth2/authorization/google">구글로그인</a>
저 주소도 라이브러리에서 제공하는 고정값이다.
로그인버튼을 누르면 저렇게 이동한다.
.oauth2Login((oauth2Login)->oauth2Login.loginPage("/loginForm"));
securityconfig 에 추가한 파일은 다음과 같다.
근데 후처리를 더 해줘야한다!
📍후처리
- 코드 받기(인증)
- 엑세스 토큰(권한)
- 사용자 프로필 정보 가지고오기
- 회원가입 자동진행 or 추가회원가입
- 구글은 코드를 받는게 아니라 엑세스토큰+사용자프로필정보를 한번에 받는다!!!
일단 config에 oauth 패키지를 만들고 PrincipalOauth2UserService 클래스를 만든다.
package com.cos.security1.config.oauth;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService{
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
return super.loadUser(userRequest);
}
}
loadUser함수에 의해서 후처리(구글로부터 받은 userRequest데이터에 대한 정보) 가 진행된다 !
userRequest 정보
👉 구글 로그인 버튼 클릭 → 구글 로그인창 → 로그인을 완료→ code를 리턴(OAuth-Client라이브러리) → AccessToken 요청
loadUser함수
👉 회원프로필 받아오기
userRequest.getClientRegistation() //서버에 대한 기본정보
- registrationId 로 어떤 OAuth로 로그인했는지 확인 가능
userRequest.getAccessToken() // 토큰 정보
super.loadUser(userRequest).getAttributes()로 엑세스토큰을 보내 받아온 정보 확인 가능
⇒ 이 받아온 정보를 사용해 회원가입 진행.
세션 정보를 확인하기 위해서 컨트롤러에 추가 해서 돌려보면
@GetMapping("/test/login")
public @ResponseBody String Testlogin(Authentication authentication){
System.out.println("/test/login -----------");
System.out.println("authentication: "+authentication.getPrincipal());
return "세션 정보확인";
}
authenticaion의 타입이 오브젝트인 것을 알 수 있다.
📍유저정보를 받아와보자
UserDetails를 extends한 PrincipalDetails로 받아오면 유저정보를 받아올 수 있다 !!!!
@GetMapping("/test/login")
public @ResponseBody String Testlogin(Authentication authentication){
System.out.println("/test/login -----------");
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println("authentication: "+principalDetails.getUser());
return "세션 정보확인";
}
그다음엔 매개변수로 UserDetails를 받아와보자 !
@GetMapping("/test/login")
public @ResponseBody String Testlogin(Authentication authentication, @AuthenticationPrincipal UserDetails userDetails){
System.out.println("/test/login -----------");
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println("authentication: "+principalDetails.getUser());
**System.out.println("userDetails"+userDetails.getUsername());**
return "세션 정보확인";
}
UserDetails를 extends한 PrincipalDetails도 적을수있다.
👉 @AuthenticationPrincipal UserDetails userDetails → PrincipalDetails userDetails
→ 이렇게 하면 getUser()이 가능!!
근데 callout에 적은것처럼 PrincipalDetails userDetails로 돌리면 안된다 왜 안되게 ….
⇒ PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); 이 줄에서 오류가 뜬다. 캐스팅이 안되기 때문!
그래서
@GetMapping("/test/oauth/login")
public @ResponseBody String Testlogin(Authentication authentication){
System.out.println("/test/login -----------");
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
System.out.println("authentication: "+oAuth2User.getAttributes());
return "세션 정보확인";
}
authentication을 OAuth2User로 다운캐스팅해서 getAttributes()하면 유저정보가 나온다!
⭐이 정보는
@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService{
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
System.out.println("getAttribute: "+super.loadUser(userRequest).getAttributes());
return super.loadUser(userRequest);
}
}
PrincipalOauth2UserService 에 있던 super.loadUser(userRequest).getAttributes()와 동일함
@GetMapping("/test/oauth/login")
public @ResponseBody String Testlogin(Authentication authentication,
@AuthenticationPrincipal OAuth2User oAuth){
System.out.println("/test/login -----------");
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
System.out.println("authentication: "+oAuth2User.getAttributes());
System.out.println("oauth2User: "+oAuth.getAttributes());
return "세션 정보확인";
}
컨트롤러에 Authentication 과 OAuth2User 를 각각 받아 출력해보면 값이 동일하다 !!!
결론
- Authentication 로 접근 가능하다
- @AuthenticationPrincipal 어노테이션 사용가능하다
→ 어노테이션을 사용하면 oauth 로그인을 해도 principalDetails 타입으로 받을수있고, 일반 로그인을 해도 PrincipalDetails 로 받을수있다! ⭐
⇒ 사실상 OAuth랑 일반 로그인이랑 같이 리턴하기 위해 PrincipalDetails 를 사용한다고 볼수있다.
정리하자면
스프링 시큐리티는 자신만의 세션을 가진다
근데 시큐리티는 세션을 따로 가짐
→ 시큐리티와 관련된 세션에 꼭 들어갈수있는 타입은 Authentication밖에 없음 !!
필요할때마다 의존성 주입을 통해 꺼내쓴다.
Authentication은 UserDetails와 OAuth2User 타입이 올 수 있다.
- UserDetails : 일반 로그인
- OAuth2User : 구글이나 페이스북 로그인
로그인의 형태에 따라 컨트롤러에서 바꿔서 사용해야하는 불편함이 있구나
⇒ 해결방법 : x라는 클래스를 만들어 UserDetails,OAuth2User 를 상속받아라!!! => PrincipalDetails!
📍principalDetails 오버라이딩 함수들 재구성
private Map<String,Object> attributes;
...
public PrincipalDetails (User user,Map<String,Object> attributes){
this.user=user;
this.attributes=attributes;
}
attributes를 받아주고 생성자를 하나 더 만들어준다.
회원가입 강제로 해보자
@Builder
public User(String username,String password,String email,String role,String provider,String providerId) {
this.username= username;
this.password=password;
this.email=email;
this.role=role;
this.provider=provider;
this.providerId=providerId;
}
User에 빌더패턴 추가
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oauth2User = super.loadUser(userRequest);
System.out.println("getAttribute: "+oauth2User.getAttributes());
//강제 회원가입진행
String provider = userRequest.getClientRegistration().getClientId();//google
String providerId = oauth2User.getAttributes().get("sub").toString();
String email = oauth2User.getAttributes().get("email").toString();
String userName = provider+"_"+providerId;
String password =bCryptPasswordEncoder.encode("겟인데어");
String role= "ROLE_USER";
//회원가입했었는지 확인
if(userEntity==null) {
System.out.println("구글로그인이 최초입니다.");
userEntity = User.builder()
.username(userName)
.email(email)
.password(password)
.role(role)
.provider(provider)
.providerId(providerId)
.build();
userRepository.save(userEntity);
}else {
System.out.println("구글 로그인을 한적이 있습니다.");
}
return new PrincipalDetails(userEntity,oauth2User.getAttributes());
}
PrincipalOauth2UserService는 다음과 같이 수정해준다.
동작을 확인해보면 다음과 같다.
UserDetails서비스와 OAuth2User서비스를 만드는 이유는 principalDetails를 리턴하기 위함이었다!
'Spring' 카테고리의 다른 글
[Spring Security] 필터 테스트 (0) | 2024.07.03 |
---|---|
[Spring Security] 구글 로그인 분리 + 네이버 로그인 (0) | 2024.07.01 |
[Spring Security] @PreAuthorize, @PostAuthorize, @Secured (0) | 2024.06.25 |
[Spring Security] 2. 회원가입 및 로그인/ 권한처리 (0) | 2024.06.25 |
[Spring Security] WebSecurityConfigurerAdapter deprecated 이슈 (0) | 2024.06.25 |