OAuth

byeol·2023년 4월 18일
0

로그인 후 세션정보를 확인하는 방법

⏺️ oauth를 이용하지 않고 개인 회원
1번

@GetMapping("/test/login")
    public @ResponseBody String testLogin(
            Authentication authentication){ //DI
        System.out.println("/test/login==========");
        PrincipalDetails principalDetails = (PrincipalDetails)authentication.getPrincipal();

        System.out.println("authentication:"+principalDetails.getUser());

        return "세션 정보 확인하기";
    }
  1. Authentication authentication를 DI(의존성 주입)
  2. PrincipalDetails principalDetails =(PrincipalDetails)authentication.getPrincipal();
    다운캐스팅해서
  3. principalDetails.getUser() 세션에 저장되는 user 정보 가져오기
public class PrincipalDetails implements UserDetails {
    //PrincipalDetail Type = UserDatails
    //그러면 PrincipalDetails 객체를 Authentication 객체에 넣을 수 있다.

    private User user;//콤포지션

    public PrincipalDetails(User user){
        this.user=user;
    }

앞서 배웠지만 Security는 세션 영역을 만드는데
Security Session 영역 => 여기 들어갈 객체 Authentication => 여기에 저장된 User 정보 UserDetails
저 UserDetails라는 정보를 우리는 PrincipalDetails로 구현해서 받는다.
결과

/test/login==========
cation:User(id=1, username=ssar, password=$2a$10$Rr.j.RXpXBvMiTTyT6NWguEtTcYIGmqq22GeqYYUjc8lpaTuaZ/iS, email=j35635a@naver.com, role=ROLE_USER, createDate=2023-04-17 22:47:29.702)

2번 @AuthenticationPrincipal을 통해 UserDetails 받기

  @GetMapping("/test/login")
    public @ResponseBody String testLogin(
            Authentication authentication,
            @AuthenticationPrincipal UserDetails userDetails){ //DI
        System.out.println("/test/login==========");
        PrincipalDetails principalDetails = (PrincipalDetails)authentication.getPrincipal();

        System.out.println("authentication:"+principalDetails.getUser());
        System.out.println("userDetails:"+userDetails.getUsername());

        return "세션 정보 확인하기";
    }
/test/login==========
authentication:User(id=1, username=ssar, password=$2a$10$Rr.j.RXpXBvMiTTyT6NWguEtTcYIGmqq22GeqYYUjc8lpaTuaZ/iS, email=j35635a@naver.com, role=ROLE_USER, createDate=2023-04-17 22:47:29.702)
userDetails:ssar

3번 @AuthenticationPrincipal을 통해 UserDetails Type인 PrincipalDetails로 User 정보 받기

@GetMapping("/test/login")
    public @ResponseBody String testLogin(
            Authentication authentication,
            @AuthenticationPrincipal PrincipalDetails userDetails){ //DI
        System.out.println("/test/login==========");
        PrincipalDetails principalDetails = (PrincipalDetails)authentication.getPrincipal();

        System.out.println("authentication:"+principalDetails.getUser());
        System.out.println("userDetails:"+userDetails.getUser());

        return "세션 정보 확인하기";
    }
/test/login==========
authentication:User(id=1, username=ssar, password=$2a$10$Rr.j.RXpXBvMiTTyT6NWguEtTcYIGmqq22GeqYYUjc8lpaTuaZ/iS, email=j35635a@naver.com, role=ROLE_USER, createDate=2023-04-17 22:47:29.702)
userDetails:User(id=1, username=ssar, password=$2a$10$Rr.j.RXpXBvMiTTyT6NWguEtTcYIGmqq22GeqYYUjc8lpaTuaZ/iS, email=j35635a@naver.com, role=ROLE_USER, createDate=2023-04-17 22:47:29.702)

⏺️ oauth를 이용하는 회원

구글 로그인 후에 "/test/login"을 통해서 세션정보를 얻으려고 하니
아래와 같이 ClassCastException 오류가 발생했습니다.

java.lang.ClassCastException: class org.springframework.security.oauth2.core.user.DefaultOAuth2User cannot be cast to class com.cos.security1.config.auth.PrincipalDetails (org.springframework.security.oauth2.core.user.DefaultOAuth2User is in unnamed module of loader 'app'; com.cos.security1.config.auth.PrincipalDetails is in unnamed module of loader org.springframework.boot.devtools.restart.classloader.RestartClassLoader @20faef74)

PrincipalDetails로 cast할 수 없다고 합니다.

따라서 아래와 같이 고쳐줍니다.
1번

  @GetMapping("/test/oauth/login")
    public @ResponseBody String testOAuthLogin(
            Authentication authentication){ //DI
        System.out.println("/test/oauth/login==========");
        OAuth2User oAuth2User = (OAuth2User)authentication.getPrincipal();
        System.out.println("authentication:"+oAuth2User.getAttributes());

        return "OAuth 세션 정보 확인하기";
    }
/test/oauth/login==========
authentication:{sub=구글의 primary key, name=김별, given_name=별, family_name=김, picture=https://lh3.googleusercontent.com/a/AGNmyxY5v34l62dtSiZ3T4lkjPgJQ2qVZCSBvb09K0FZ=s96-c, email=j35635aa@gmail.com, email_verified=true, locale=ko}

2번 @AuthenticationPrincipal

@GetMapping("/test/oauth/login")
    public @ResponseBody String testOAuthLogin(
            Authentication authentication,
            @AuthenticationPrincipal OAuth2User oAuth){ //DI
        System.out.println("/test/oauth/login==========");
        OAuth2User oAuth2User = (OAuth2User)authentication.getPrincipal();
        System.out.println("authentication:"+oAuth2User.getAttributes());
        System.out.println("oauth2User : "+oAuth.getAttributes());

        return "OAuth 세션 정보 확인하기";
    }
/test/oauth/login==========
authentication:{sub=구글의 primary key, name=김별, given_name=별, family_name=김, picture=https://lh3.googleusercontent.com/a/AGNmyxY5v34l62dtSiZ3T4lkjPgJQ2qVZCSBvb09K0FZ=s96-c, email=j35635aa@gmail.com, email_verified=true, locale=ko}
oauth2User : {sub=구글의 primary key, name=김별, given_name=별, family_name=김, picture=https://lh3.googleusercontent.com/a/AGNmyxY5v34l62dtSiZ3T4lkjPgJQ2qVZCSBvb09K0FZ=s96-c, email=j35635aa@gmail.com, email_verified=true, locale=ko}

로그인이 완료되면
구글서버에서 우리 쪽으로 코드를 돌려준다.
인증이 되었다는 코드
그 코드를 받아서 access token을 서버에 요청
access token을 받아서 사용자 대신에 우리 서버가 사용자의 개인정보 등의 민감한 정보를
접근할 수 있는 권한이 생긴다.

그 코드를 받을 수 있는 주소를 정해야 한다.

login/oauth2/code 고정

GetMapping(login/oauth2/code/google) 이거 만들 필요 없음
왜냐하면
우리가 재를 제어하지 않고 라이브러리가 알아서 제어해줌

155009207765-obhkmhdm5clk23bbflcq5n65ooqu048m.apps.googleusercontent.com
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

  1. 코드 받기 (구글에 로그인을 통해 인증)
  2. 코드를 통해 엑세스 토근 받기 ( 사용자 정보에 접근할 수 있는 권한이 생김)
  3. 엑세스 토근을 받아서 구글에 접근하여 사용자 프로필 정보를 가져오고
    4-1. 그 정보를 토대로 회원 가입을 자동으로 진행시키기도 함
    4-2. (구글 제공 정보 :이메일, 전화번호, 이름,아이디) + 추가로 집주소, 등급
    추가적인 구성이 필요함-> 그래서 자동 회원가입이 아닌 추가적인 정보 제공 페이지가 필요함

우리는 4-1를 선택

super.loadUser(userRequest).getAttributes(): {
sub=구글 PK,
name=김별, given_name=별, family_name=김, picture=https://lh3.googleusercontent.com/a/AGNmyxY5v34l62dtSiZ3T4lkjPgJQ2qVZCSBvb09K0FZ=s96-c,
email=j35635aa@gmail.com,
email_verified=true,
locale=ko}

username = google의 primarykey인 google_sub
password = "암호화(겟인데어)" null만 아니고 아무거나 해도 상관없음
email = 구굴의 이메일 그대로
role = ROLE_USER
provider = "google"
providerId = google의 primarykey

// oauth 통해 로그인 한 것인가?
private String provider;
private String providerId;

스프링 시큐리티는
자기만의 시큐리티 세션을 가지고 있다.

서버의 session이 있고 여기에 시큐리디가 관리하는 세션이 있다.
여기에 들어갈 수 있는 타입은 Authentication객체밖에 없다.

Contoller에서 DI할 수 있다.
Authentication에 들어갈 수 있는 타입은 UserDetails와 OAuth2User 단 두개이다.

시큐리티가 들고 있는 세션에는 Authentication객체만 들어갈 수 있고 여기에 들어오는 순간
로그인이 된 것이다.
Authentication에 들어갈 수 있는 타입은 UserDetails와 OAuth2User 단 두개이다.
UserDetails : 일반적인 로그인
OAuth2User : OAuth 로그인
하면 타입이 Authenticaion에 들어간다.

그러면 우리가 필요할 때 세션 정보를 꺼내써야 하는데 불편한 것이 있다.
어떤 Controller에서
일반적인 로그인 세션 정보는
구글로 로그인 세션 정보
나눠서 uri매핑을 해야하는가?

그래서 X라는 클래스를 만들어서 부모로 만들어버림
그리고 그 X를 Authentication으로 들어가게 한다.

일반적인 로그인은 loadByUsername함수가 호출될 때
UserDetails가 Authentication에 들어간다.

저 X를 PrincipalDetails를 부모로 넣기

PrincipalDetails 두 가지 목적
시큐리티 세션이 들어갈 수 있는 객체는 Authentication
여기에 담을 수는 필드는
OAuth2User, UserDetails

회원가입을 하면 User 객체가 필요
그러나 OAuth2User, UserDetails는 User객체를 포함하고 있지않다.
그래서 PrincipalDetail 클래스를 만들고 UserDetail를 구현해서 같은 타입으로 만들어
User 객체를 품었다.

그래서 UserDetail->PrincipalDetail로 change해서 세션정보에 저장된 User객체를 접근할 수 있게 된다.

그러나 문제는 OAuth2User도 있다는 것
그래서 PrincipalDetail가 OAth2User도 구현-> OAuth2User도 User 객체를 품게된다.

oauth2User :
{sub=구글PK,
name=김별, given_name=별, family_name=김, picture=https://lh3.googleusercontent.com/a/AGNmyxY5v34l62dtSiZ3T4lkjPgJQ2qVZCSBvb09K0FZ=s96-c,
email=j35635aa@gmail.com,
email_verified=true,
locale=ko}

PrincipalOauth2UserService랑 PrincipalUserService를 만들지 않아도
기본적으로 loadUser와 loadUserByUsername는 작동을 한다.
굳이 만드는 이유는 PrincipalDetails를 return하기 위해서
저 return되는 객체가 Authentication에 저장된다.

profile
꾸준하게 Ready, Set, Go!

0개의 댓글