로그인 후 앱만들기 클릭 → 앱 유형 선택에서 없음 선택 → 앱 이름 설정후 다음 → 'Facebook 로그인 설정 클릭'
웹 선택 → 사이트 URL 설정 (http://localhost:8080) → 모두 다음 누른 후 왼쪽편에 설정 기본 설정 클릭 → 앱ID/시크릿 코드를 이용하여 스프링부트에 등록 (YML파일에 등록) scop는 각 회사마다 다르므로 필히 따로 확인해서 적자
이렇게 등록을 하고나면 아래와 같은 오류가 뜨는데 시큐리티에서 설정을 좀 해줘야 한다
SecurityConfig로 이동하여 아래 코드를 추가
oauth2Login() → 내가 만든 form 로그인도 하는데 oauth2로그인도 사용 하겠다라는 의미 userInfoEndpoint() → oauth2로 로그인시에 최종 응답을 회원정보로 바로 받도록 해줌 userService() → 최종 응답을 받을 서비스 클래스를 넣어주면 된다.
최종 응답을 받을 서비스 세팅
@Service
public class OAuth2DetailsService extends DefaultOAuth2UserService{}
다시 SecurityConfig로 돌아가 세팅 마무리
OAuth2DetailsService 의 역할
우리 플래폼에서 페이스북 로그인을 하고싶을때 '페이스북 로그인' 버튼을 누리고 아이디와 비번을 치게되면 페이스북으로 이 로그인요청이 우리플랫폼에서 페이스북으로 간다
이때 페이스북에서 요청에대한 인증을 하고 인증이 정상적으로 처리되면 응답을 하게 되는데 이때 이 응답을 처리하는곳이 OAuth2DetailsService
OAuth2DetailsService가 복잡한 OAuth2의 개념을 알아서 처리를하고 우리는 OAuth2DetailsService으로 받은 회원 정보를 가지고 처리를 하면 된다.
02 OAuth2 의 진행 개념
OAuth는 Open Auth의 약자로 인증을 오픈해준다는 말이다. 보통 어느 플랫폼에서 클라이언트가 회원가입을 하게되면 그 플랫폼의 회원가입을 타고 가입 하고 로그인 또한 하게 된다.
이 OAuth2를 써서 이 회원가입과 로그인처리를 타 플랫폼(페이스북/카카오톡/네이버 등)에게 양도를 하게되면 클라이언트는 자기가 쓰고있는 플랫폼을 통해 회원가입 및 로그인 처리를 한다
클라이언트가 회원가입 혹은 로그인을 타 플랫폼(페이스북)을 통해 요청을 하면 페이스북 플랫폼은 먼저 이 클라이언트가 자기 플랫폼에 가입이 되어있는지 확인을 한다
확인이 되면 정상 회원가입을 처리하게 된다. 그 이후 원플랫폼(P1)에서 페이스북으로 로그인 요청을 다시 하면 아이디와 비번을 먼저 확인하는게 아니라 페이스북이 들고 있는 앱 리스트에서 P1플랫폼이 등록이 되어 있는지 먼저 확인을 한다.
이후 페이스북 DB에 이 클라이언트(ID/PW)가 등록되어있는지 확인을 한다. 확인이 되면 인증이 뜨게되고 이 인증 코드를 P1 플랫폼으로 돌려 준다
이제 P1플랫폼에서는 인증 코드를 받았지만 여전히 클라이언트의 정보가 필요하다. 그래서 인증 코드를 들고 P1은 페이스북에 ACCESS TOKEN을 요청하여 클라이언트의 정보에 접근 가능하도록 허락을 받는다. 이 정보가 위에서 세팅을한 SCOPE를 통해 넘어오는 정보들이다.
받아온 정보들로 회원가입 및 로그인을 시키면 되는 것. OAuth2가 위의 6개의 과정을 담고있고 우리는 정보만 받아와서 회원가입/로그인만 하면 되는 엄청나게 편한 라이브러리!
03 페이스북 로그인
signin.jsp가서 페이스북 로그인 창 설정
<!-- Oauth 소셜로그인 -->
<div class="login__facebook">
<button onclick="javascript:location.href='/oauth2/authorization/facebook'">
<i class="fab fa-facebook-square"></i>
<span>Facebook으로 로그인</span>
</button>
</div>
<!-- Oauth 소셜로그인end -->
<button onclick="javascript:location.href='/oauth2/authorization/facebook'">
주소의 경우는 스프링부트에서 지정해주는 방식이니 반드시 따라 해야 작동 한다
굿!이미 로그인이 되어있는 사용자를 로그아웃하면 이런 화면으로 바뀐다
OAuth2DetailsService 마무리 마무리하기전에 OAuth2DetailsService 가 받아오는 정보를 한번 확인하고 넘어가자
@Service
public class OAuth2DetailsService extends DefaultOAuth2UserService{
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
//System.out.println("OAuth2DetailsService의 loadUser 타나요????????????????");
OAuth2User oAuth2User = super.loadUser(userRequest);
System.out.println(oAuth2User.getAttributes());
return null;
}
}
이런정보를 이렇게 쉽게 받아와주니 얼마나 편한가!
OAuth2DetailsService에 유저정보를 담아서 처리 하자
@RequiredArgsConstructor
@Service
public class OAuth2DetailsService extends DefaultOAuth2UserService {
private final UserRepository userRepository;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
// System.out.println("OAuth2DetailsService의 loadUser 타나요????????????????");
OAuth2User oAuth2User = super.loadUser(userRequest);
// System.out.println(oAuth2User.getAttributes());
Map<String, Object> userInfo = oAuth2User.getAttributes();
String username = "facebook_" + (String) userInfo.get("id");
String password = new BCryptPasswordEncoder().encode(UUID.randomUUID().toString());
String email = (String) userInfo.get("email");
String name = (String) userInfo.get("name");
User userEntity = userRepository.findByUsername(username);
if (userEntity == null) {
User user = User.builder()
.username(username)
.password(password)
.email(email)
.name(name)
.role("ROLE_USER")
.build();
return new PrincipalDetails(userRepository.save(user), oAuth2User.getAttributes());
} else {
return new PrincipalDetails(userEntity);
}
}
}
- oAuth2User에서 넘어오는 정보들은 MAP형태로 오기때문에 이 정보를 쓰려면 MAP으로 받아야 한다 - 회원가입을 시키기위해서 유저를 만들어야 DB에 넣을 수 있기때문에 넘어오는 정보를 바탕으로 우리 플래폼에서 썻던 signupDto에 맞게 똑같이 받도록 세팅한다 - 그리고 회원가입을 무한으로 하면 안되기때문에 userEntity를 만들어 DB에서 비교하도록 세팅하고 없으면 DB에 새롭게 인서트, 있으면 회원가입이 안되도록 설정 - 비밀번호 해시처리시에 따로 BCryptPasswordEncoder()를 전역변수로 놓으면 빈 사이클 에러가 걸리므로 지역변수 대신에 직접 패스워드를 딸때에만 쓰도록 해야 한다. - 마지막에 PrincipalDetails로 처리하는 과정은 다음과 같다
PrincipalDetails 세팅
@Data
public class PrincipalDetails implements UserDetails, OAuth2User{
private static final long serialVersionUID = 1L;
private User user;
private Map<String, Object> attributes;
public PrincipalDetails(User user) {
this.user = user;
}
public PrincipalDetails(User user, Map<String, Object> attributes) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collector = new ArrayList<GrantedAuthority>();
collector.add(()->{return user.getRole();});
return collector;
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public Map<String, Object> getAttributes() {
return attributes;
}
@Override
public String getName() {
// TODO Auto-generated method stub
return (String) attributes.get("name");
}
}
- Implements를 UserDetails와 OAuth2User를 동시에 하여 둘다 PrincipalDetails가 둘 다 사용 가능하도록 한다 - 우리 플랫폼에서 가입을 하게 되면 UserDetails에 담겨서 처리가 되어 '@AuthenticationPrincipal PrincipalDetails principalDetails' 로 우리 플랫폼 유저 정보를 꺼내 오게 되고 - 타 플랫폼 가입을 하면 OAuth2User에 담기게 되어서 타플랫폼이라도 자유자재로 '@AuthenticationPrincipal PrincipalDetails principalDetails' 로 유저정보들을 들고 올 수 있게 된다 - 그러므로 마지막에 리턴을 'PrincipalDetails'로 할 수 있는 것!
테스트 테스트시에 User.java에 보면 username 길이가 20으로 제한되어있다는 오류가 뜬다 100으로 바꾸고 테스트
굿!
잘 뜬다!
길고 긴 인스타그램 클론코딩이 끝났다!!!!
** 혹시나 나처럼 구독정보를 클릭했을때 아래와 같은 오류가 뜬다면 (구독정보가 빈칸으로 나옴) subscribeDto에서 subscribeState/equalUserState 의 타입을 BigInteger로 변경하면 해결 된다!!