Spring에서는 JWT, OAuth2 등을 사용하여 인증, 인가 및 회원가입, 로그인을 구현할 수 있습니다.
id, password를 사용 후 session, jwt등을 사용하여 로그인을 유지할 수 있고 OAuth2를 사용해 인증, 인가 후 마찬가지로 session, jwt등을 사용해 로그인을 유지할 수도 있습니다.
그리고 오늘은 앱 개발 프로젝트를 진행하며 Flutter, fireabase Authencation를 사용하여 소셜 로그인을 진행 후 받은 idToken을 사용하여 인증, 인가 진행하겠습니다.
먼저 소셜 로그인 로직은 프론트, 앱 개발자들이 구현하여 생략하겠습니다.

먼저 개발용으로 소셜 로그인을 사용하여 로그인을 하면 긴 문자열을 반환받습니다.
해당 문자열이 idToken입니다.
@Configuration
public class FirebaseInitializer {
@Bean
public FirebaseAuth getFirebaseAuth() throws IOException {
FileInputStream serviceAccount = new FileInputStream("./firebase.json");
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.setDatabaseUrl("firebase 데이터베이스 url")
.build();
FirebaseApp.initializeApp(options);
return FirebaseAuth.getInstance();
}
}
firebase를 초기화하는 코드입니다.
해당 내용은

그림처럼 프로젝트 설정의 서비스 계정 선택 후 admin sdk를 자바로 선택하면 나오는 내용입니다.
해당 페이지에서 데이터베이스 주소 정보를 가져오시면 됩니다.
@AllArgsConstructor
public class FirebaseTokenFilter extends OncePerRequestFilter {
private final UserDetailCustomService userDetailCustomService;
private final FirebaseAuth firebaseAuth;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
FirebaseToken decodedToken;
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
setUnauthorizedResponse(response, "INVALID_HEADER");
return;
}
String token = header.substring(7);
try {
decodedToken = firebaseAuth.verifyIdToken(token);
} catch (FirebaseAuthException e) {
setUnauthorizedResponse(response, "INVALID_TOKEN");
return;
}
UserDto user = userDetailCustomService.loadUser(decodedToken);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, List.of(new SimpleGrantedAuthority(UserRole.USER.getKey())));
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
private void setUnauthorizedResponse(HttpServletResponse response, String code) throws IOException {
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"code\":\"" + code + "\"}");
}
}
firebae 초기화 후 인증, 인가 로직입니다.
먼저 header를 검사 후 토큰을 검사하는 로직입니다.
filter에서는 @RestControllerAdvice를 사용한 전역 예외처리가 안먹힙니다.

그림처럼 filter는 dispatcher servlet, controller 앞에 위치해 있는데 @RestControllerAdvice는 spring context에서 발생한 예외를 처리하기 때문입니다.
그래서 인증, 인가에 발생한 예외는 따로 처리해야합니다.
@AllArgsConstructor
@Service
public class UserDetailCustomService {
private final UserRepository userRepository;
public UserDto loadUser(FirebaseToken token) {
User user = registerUser(token);
return UserDto.from(user);
}
private User registerUser(FirebaseToken token) {
return userRepository.findByUid(token.getUid()).orElseGet(() -> userRepository.save(
User.builder().uid(token.getUid())
.email(token.getEmail())
.username(token.getName())
.role(UserRole.USER)
.build()));
}
}
idToken에서 정보를 가져와 데이터베이스와 비교 후 삽입, 조회하는 로직입니다.
프로젝트 특성 상 user_not_found 등의 에러를 반환하는 것이 아닌 새로운 데이터를 삽입하는 방식으로 진행했습니다.
여기서 가져온 데이터를 spring context에 저장 후 controller에서 해당 유저 정보를 사용하면됩니다 :)
@PatchMapping("{organization}")
@Operation(summary = "사용자 소속 변경", description = "응답 없음")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void changeOrganization(
@PathVariable("organization") final String organization,
@AuthenticationPrincipal final UserDto user
) {
memberService.updateOrganization(user.id(), organization);
}
spring context의 principal에 UserDto class를 저장하여 controller에서 로그인 한 유저의 정보를 가져올 때 위와 같이 바로 UserDto를 가져올 수 있습니다!