내일배움캠프 D+53: 0609 🐢

enyo9rt·2022년 6월 9일

TIL-S

목록 보기
36/79

🌌 실시간 강의

심화반 2주차에 나오는 로그인 위주로 진행되었다.


🍃 스프링 심화반

✳ 2주차 ❇

✔ 접근 불가 페이지 만들기

▶ 관리자가 아닌 회원이 /admin으로 접속을 시도할 때 forbidden 페이지가 뜨도록 해보자.

시큐리티가 알 수 있게끔 (private static final String ROLE_PREFIX = "ROLE_"; 이렇게 지정해줘야 알 수 있다고 한다.) 해당 사용자의 권한을 유저 정보에서 가져올 수 있도록 한다. 권한 정보를 담은 SimpleGrantedAuthority 객체를 생성하는데, 그를 리스트에 담아 보내야 한다.

❔ 리스트에 왜 담는 이유는? 사용자마다 여러 권한을 설정하는 것이 가능하다는 것을 의미!

WebSecurityConfig에 어노테이션 추가(API마다 접근 가능한 role을 설정할 수 있도록 한다.)
@EnableGlobalMethodSecurity(securedEnabled = true)
인가가 되지 않을시 forbidden 페이지로 이동
.accessDeniedPage("/user/forbidden");

HomeController, ProductController에서 인가가 필요한 API에 어노테이션 추가
@Secured("{ROLE 이름}")

UserController에서 forbidden 연결


✔ 소셜 로그인

▶ 사용자에게도 매번 회원가입을 하는 것이 부담이며 사이트 운영 측에서도 보안이 부담이기에, 이런 문제들을 해결하게끔 OAuth를 사용한 소셜 로그인이 등장하게 된다.

❔ OAuth? 접근 위임을 위한 개방형 표준으로, 사용자 대신 서비스를 이용할 수 있게 해주는 HTTP 기반의 보안 프로토콜

카카오디벨로퍼스 사이트에 가입 후 애플리케이션을 등록
❔ Redirect URI? 정상적으로 로그인됐을 때 보내는 url

로그인 html에서 REST API키를 넣어준다.

https://kauth.kakao.com/oauth/authorize?client_id={본인의 REST API키}
&redirect_uri=http://localhost:8080/user/kakao/callback&response_type=code

카카오 로그인을 완료하면 미리 설정해둔 Redirect URI로 인가토큰이 전달된다.
❔ 연결했는데 에러? UserController에서 Redirect URI로 연결되면 실행될 API를 구현해준다.(인가 코드를 받아서 전달하게끔)


▶ 카카오 로그인 처리 구현

  1. Users에 kakaoId 컬럼 추가
    nullable = true 로 설정
    생성자에 kakaoid를 받는 경우 저장하도록 한다.

  2. UserService에 KakaoLogin 함수 추가
    전달받은 인가 코드를 사용해 kakaoOAuth2에서 사용자 정보를 조회한다.
    kakaoId가 null일 경우 회원 가입을 진행하도록 한다.
    authenticationManager로 카카오id를 username으로 하고 임의로 생성된 pw를 담은 authentication 토큰을 객체로 갖게 해준다.
    SecurityContextHolder에 해당 객체를 넣어주면 세션이 생성되고 로그인 처리가 가능해진다.

  3. UserRepository에 카카오 아이디로 조회 추가

  4. WebSecurityConfig에 인가 관련 빈 추가

  5. kakao패키지에 KakaoUserInfo 추가
    @AllArgsConstructor

  6. kakao패키지에 KakaoOAuth2 추가

    a. 전달받은 인가 코드로 액세스 토큰을 요청한다.
    설정값을 지정하고 RestTemplate 라이브러리(서버에서 api 요청할 때 쓴다.)를 사용, post방식으로 http 요청하고 response를 받아온다.
    JSON 형태로 받아오기 때문에 파싱이 선행되고 액세스 토큰 값을 가져오도록 한다.

    b. 액세스 토큰으로 카카오 사용자 정보를 요청한다.
    헤더에 액세스 토큰을 담아 RestTemplate 라이브러리를 사용, post방식으로 http 요청하고 response를 받아온다.
    마찬가지로 JSON 형태로 받아오기 때문에 파싱이 선행되어야 한다.


액세스 토큰을 받아, 카카오 사용자 정보를 가져오는 과정이 위와 같이 진행되었다.

카카오 사용자 정보는 아래와 같은 JSON 형태로 전달받는다.

{
  "id": 1632335751,
  "properties": {
    "nickname": "르탄이",
    "profile_image": "http://k.kakaocdn.net/...jpg",
    "thumbnail_image": "http://k.kakaocdn.net/...jpg"
  },
  "kakao_account": {
    "profile_needs_agreement": false,
    "profile": {
      "nickname": "르탄이",
      "thumbnail_image_url": "http://k.kakaocdn.net/...jpg",
      "profile_image_url": "http://k.kakaocdn.net/...jpg"
    },
    "has_email": true,
    "email_needs_agreement": false,
    "is_email_valid": true,
    "is_email_verified": true,
    "email": "letan@spartacondingclub.com"
  }
}

카카오 로그인을 이용하지만 회원에 따른 관심 상품을 보여주기 위해 서비스 내에서도 회원 가입이 필요하다.
강의에서는 아래와 같은 방식으로 진행하였다.
1. username을 카카오 id로 저장
2. password를 카카오 id에 관리자 토큰값을 붙여 임의로 생성 (사용자가 알 수 없으므로 따로 로그인이 불가하다.)
3. 한 번이라도 카카오 로그인을 했다면 회원 가입이 완료되므로 재가입이 되지 않게끔 DB에서 카카오 id 중복 조회를 한다.
4. 스프링 시큐리티를 사용해 로그인 처리를 한다.

숙제

▶ 지금은 DB에서 kakao id로만 중복 조회를 하는 상태!
그러나 만약 카카오 사용자 정보에 있는 이메일과 같은 이메일이 존재한다면?
이를 방지하기 위해 이메일로도 중복 조회를 해보자!

다른 건 다 했는데 눌러도 로그인이 감감 무소식이었다...
어떻게 로그인이 되어야할까?
사실 로그인 과정은 시큐리티가 알아서 해줘서 내부 원리가 잘 이해가 안됐던 것도 문제였다.
아무튼 답안 코드를 슬쩍 봤는데ㅎㅎ,,
원래 코드는 이랬다.

Authentication kakaoUsernamePassword = new UsernamePasswordAuthenticationToken(username, password);
Authentication authentication = authenticationManager.authenticate(kakaoUsernamePassword);
SecurityContextHolder.getContext().setAuthentication(authentication);

카카오 사용자 정보의 nickname을 username으로 설정하고 임의로 생성한 pw를 담은 토큰을 생성했었다.

UserDetailsImpl userDetails = new UserDetailsImpl(kakaoUser);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);

카카오 로그인이나 서비스 내 로그인이나 모두 가입이 되기 때문에 Users 클래스의 유저 정보를 담아서 토큰을 만들어 준다. 인가 정보도 함께 담아 토큰을 생성한다.

이 부분은 풀이에서도 설명이 간단했다. 글로도 말로도 이해하기 간단하다. 그러나 혼자서 생각해내기에는 어려웠던 부분이었다ㅠㅠ


🐢 스터디

오버로딩과 오버라이딩에 대해 다뤘다. 내가 봤던 기본서의 내용을 그대로 다뤄서 복습하는 기분으로 들었다.
오버로딩은 과적한다, 오버라이딩은 덮어쓴다로 기억하면 좋겠다.

public class Car {
	String brand;
	String color;
	int price;
	
	public Car() {}
	public Car(String brand, String color, int price) {
		this.brand = brand;
		this.color = color;
		this.price = price;
	}
	void engineStart() {
		System.out.println(brand+" 열쇠로 시동 켜기");
	}
	
	void engineEnd() {
		System.out.println(brand+" 열쇠로 시동 끄기");
	}
}
class SuperCar extends Car {
	String mode;
	
	@Override
	void engineStart() {
		System.out.println(brand+" 음성으로 시동 켜기");
	}
	
	@Override
	void engineEnd() {
		System.out.println(brand+" 음성으로 시동 끄기");
	}
	
	public SuperCar(String brand, String color, int price, String mode) {
		super(brand, color, price); // 재사용
		this.mode = mode;
	}
}

// 불필요하지만 일부러 다운캐스팅으로 했다.
public class Prac {
	
	public static void main(String[] args) {
		Car Car = new SuperCar("Chevrolet Trailblazer", "blue", 3000, "dailymode");
		SuperCar superCar = (SuperCar)Car;
		superCar.engineStart();
	}
}

0개의 댓글