원문: https://medium.com/javarevisited/jwt-and-social-authentication-using-spring-boot-90e4faaa9204
이 글은 스프링부트와 Facebook Graph API를 이용해서 JWT 인증을 구현하고 페이스북 인증에 적용하는 법에 대한 가이드이다.
이 글은 주로 페이스북 인증에 대해서 다룬다. 하지만 소셜로그인은 구글, 링크드인, 트위터 등의 소셜플랫폼에서도 통용되는 이야기이다.
아래 내용에 대한 이해가 권장된다:
아래 비디오에서 이 글에서 구현하는 것을 확인할 수 있다.
소스코드는 여기에서 확인할 수 있다.
JWT 인증
JWT 인증 구현하기
소셜 로그인
소셜 로그인은 사용자들이 페이스북, 트위터, 링크드인 등의 소셜 계정으로 다른 웹사이트에 로그인할 수 있게 한다.
이는 웹사이트에 대한 사용자 경험을 향상시킨다. 회원가입 양식을 기입하거나 비밀번호를 기억할 필요가 없기 때문이다.
이 다이어그램은 소셜 로그인 흐름을 보여준다.
- 프론트엔드: 페이스북 로그인
- 페이스북: 페이스북 로그인 성공 시 엑세스토큰 반환
- 프론트엔드: 백엔드서버에 엑세스토큰과 함께 로그인 요청
- 백엔드서버: 페이스북에게 엑세스토큰 유효한지 확인 요청
- 페이스북: 엑세스토큰 유효성 검사. 유효한 경우 백엔드서버에 유저 프로필 반환
- 백엔드서버: 유저가 존재하지 않을 경우에 회원등록 + JWT 토큰 반환. 유저 존재할 경우에 JWT 토큰 반환
- 프론트엔드: JWT 토큰과 함께 유저 정보 요청
- 백엔드서버: JWT 토큰 유효성 검사. 유효한 경우 유저 프로필 반환
Facebook Graph API
페이스북이 제공하는 정보는 그래프 형식으로 표현된다. 그래프는 nodes(User나 Photo), edges(Photo의 comment) 그리고 fields(User의 birthday, email)로 구성된다. 예시로 든 그래프는 소셜 그래프라고 불린다.
Facebook Graph API에 대해 여기서 더 알아볼 수 있다.
페이스북 로그인 구현하기
프론트엔드에서 할 일
첫째로, 프론트엔드(클라이언트)는 페이스북으로부터 엑세스토큰을 받아서 이를 백엔드서버로 보내야 한다.
페이스북 개발자 계정과 페이스북 앱을 생성해야 한다. 여기의 가이드를 따라 앞의 두 가지를 생성할 수 있다.
그 다음 Facebook SDK를 포함하기 위해 index.html의 body 태그 바로 다음에 아래 스크립트를 추가한다.
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/en_US/sdk.js"></script>
login.js 페이지에서 Facebook API를 초기화하기 위해 다음 코드를 추가한다.
const initFacebookLogin = () => {
window.fbAsyncInit = function () {
FB.init({
appId: "{app_id}",
autoLogAppEvents: true,
xfbml: true,
version: "v7.0",
});
};
};
{app_id}는 실제 APP ID로 교체한다.
"페이스북으로 로그인하기" 버튼은 반드시 아래 함수를 호출해야 한다.
FB.login(function(response) {
if (response.status === 'connected') {
// Logged into your webpage and Facebook.
const facebookLoginRequest = {
accessToken: response.authResponse.accessToken,
};
facebookLogin(facebookLoginRequest)
.then((response) => {
localStorage.setItem("accessToken", response.accessToken);
});
} else {
// The person is not logged into your webpage or we are unable to tell.
}
}, {scope: 'email'});
위의 함수는 페이스북 로그인을 실행해서 엑세스토큰을 가져오고, 백엔드서버를 호출해 로그인 후 JWT 토큰을 로컬스토리지에 저장한다.
여기서 scope
는 사용자에게 이메일 정보에 대한 접근 권한을 요청한다는 것을 의미한다.
백엔드서버에서 할 일
FacebookService 코드를 보고 서버에서 어떻게 구현하고 있는 지 확인하자
public String loginUser(String fbAccessToken) {
var facebookUser = facebookClient.getUser(fbAccessToken);
return userService.findById(facebookUser.getId())
.or(() -> Optional.ofNullable(userService.registerUser(convertTo(facebookUser), Role.FACEBOOK_USER)))
.map(InstaUserDetails::new)
.map(userDetails -> new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()))
.map(tokenProvider::generateToken)
.orElseThrow(() ->
new InternalServerException("unable to login facebook user id " + facebookUser.getId()));
}
Line 2, FacebookClient.getUser 메서드를 호출한다. 이에 대해 뒤에 다룰 것이다.
Line 4, 사용자가 DB에 존재하는 지 확인한다.
Line 5, 사용자가 없으면 사용자를 등록한다.
Line 7-9, JWT 토큰을 생성하여 프론트엔드에 반환한다.
private User convertTo(FacebookUser facebookUser) {
return User.builder()
.id(facebookUser.getId())
.email(facebookUser.getEmail())
.username(generateUsername(facebookUser.getFirstName(), facebookUser.getLastName()))
.password(generatePassword(8))
.userProfile(Profile.builder()
.displayName(String
.format("%s %s", facebookUser.getFirstName(), facebookUser.getLastName()))
.profilePictureUrl(facebookUser.getPicture().getData().getUrl())
.build())
.build();
}
물론, 위에서 언급했듯이, 사용자를 새로 추가할 때, 임의의 사용자명과 임의의 페스워드를 생성한다.
드디어, FacebookClient를 확인하자.
@Autowired private RestTemplate restTemplate;
private final String FACEBOOK_GRAPH_API_BASE = "https://graph.facebook.com";
public FacebookUser getUser(String accessToken) {
var path = "/me?fields={fields}&redirect={redirect}&access_token={access_token}";
var fields = "email,first_name,last_name,id,picture.width(720).height(720)";
final Map<String, String> variables = new HashMap<>();
variables.put("fields", fields);
variables.put("redirect", "false");
variables.put("access_token", accessToken);
return restTemplate
.getForObject(FACEBOOK_GRAPH_API_BASE + path, FacebookUser.class, variables);
}
이 함수는 다음 Facebook Graph API에 대한 단순한 GET 호출이다.
https://graph.facebook.com/me?fields=email,first_name,last_name,id,picture.width(720).height(720)&access_token={access_token}
흥미로운 점은 field 키를 통해 필요한 필드에 대해 명시한다는 점이다.
결론
소셜 로그인과 JWT 인증을 엮는 일은 꽤 작업이 필요하다. 그러나 이는 사용자 경험을 향상시켜 사용자가 회원가입 폼 입력없이 쉽게 웹사이트에 가입하게 한다.
좋아할만한 다른 Java와 Spring과 관련된 아티클이다.
https://javarevisited.blogspot.com/2021/09/microservices-design-patterns-principles.html
'백엔드' 카테고리의 다른 글
타임존과 날짜 다루기 (0) | 2022.04.14 |
---|---|
『JAVA ORM 표준 JPA 프로그래밍』1장~3장 (0) | 2022.03.04 |
『JAVA ORM 표준 JPA 프로그래밍』2장. 실습 준비 - H2 DB 설치하기 (0) | 2022.02.09 |