OAuth2LoginSuccessHandler 수정사항
해당 메서드의 대해서 jwtProvider에 메서드에 대해서 매치하지 않는 부분들을 수정했습니다.
@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {
private final JwtProvider jwtProvider;
private final UserRepository userRepository;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("OAuth2 Login 성공!");
try {
CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();
// User의 Role이 GUEST일 경우 처음 요청한 회원이므로 회원가입 페이지로 리다이렉트
// TODO : createToken 매개인자값 추가
if(oAuth2User.getSocialRole() == Role.GUEST) {
String accessToken = jwtProvider.createToken(oAuth2User.getEmail(), null, null, null);
// TODO : 토큰값 확인, accessToken, refreshToken 모두 생성해야함
response.addHeader("Authorization", "Bearer " + accessToken);
response.sendRedirect("oauth2/sign-up"); // 프론트의 회원가입 추가 정보 입력 폼으로 리다이렉트
jwtProvider.sendAccessAndRefreshToken(response, accessToken, null);
} else {
loginSuccess(response, oAuth2User); // 로그인에 성공한 경우 access, refresh 토큰 생성
}
} catch (Exception e) {
throw e;
}
}
// TODO : 소셜 로그인 시에도 무조건 토큰 생성하지 말고 JWT 인증 필터처럼 RefreshToken 유/무에 따라 다르게 처리해보기
private void loginSuccess(HttpServletResponse response, CustomOAuth2User oAuth2User) throws IOException {
String accessToken = jwtProvider.createToken(oAuth2User.getEmail(), null, null, null);
String refreshToken = jwtProvider.createRefreshToken();
response.addHeader("Authorization", "Bearer " + accessToken);
response.addHeader("Authorization-refresh", "Bearer " + refreshToken);
jwtProvider.sendAccessAndRefreshToken(response, accessToken, refreshToken);
jwtProvider.updateRefreshToken(oAuth2User.getEmail(), refreshToken);
}
}
기능
1. 로그인 사용자 정보 가져오기 : 'CustomOAuth2User' 객체를 통해서 OAuth2 인증으로부터 얻어 온 사용자 정보를 가져온다.
2. 최초 로그인 사용자 확인 : 사용자의 역할이 'GUEST'인지 확인 후, 최초 로그인 이라면 아래에 로직을 실행한다.
- GUEST 라면
- JWT 액세스 토큰 생성
- 생성된 토큰을 헤더에 추가
- 회원가입 페이지(추가 정보 입력) 페이지로 리다이렉트
- 엑세스 토큰 - 리프레시 토큰을 응답 헤더에 추가
- GUEST가 아니라면 ( 최초 로그인이 아니라면 )
- 'loginSuccess' 메서드를 호출하여 토큰 생성 및처리
User 수정사항
SecurityConfig 수정사항
...
private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler;
private final CustomOAuth2UserService customOAuth2UserService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ID, Password 문자열을 Base64로 인코딩하여 전달하는 구조
.httpBasic().disable()
// 쿠키 기반이 아닌 JWT 기반이므로 사용하지 않음
.csrf().disable()
// CORS 설정
.cors(c -> {
CorsConfigurationSource source = request -> {
// Cors 허용 패턴
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(
List.of("*")
);
config.setAllowedMethods(
List.of("*")
);
return config;
};
c.configurationSource(source);
}
)
// Spring Security 세션 정책 : 세션을 생성 및 사용하지 않음
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// 조건별로 요청 허용/제한 설정
.authorizeRequests()
// 회원가입과 로그인은 모두 승인
.antMatchers("/api/register", "/api/login").permitAll()
// /admin으로 시작하는 요청은 ADMIN 권한이 있는 유저에게만 허용
.antMatchers("/api/admin/**").hasRole("ADMIN")
// /user 로 시작하는 요청은 USER 권한이 있는 유저에게만 허용
.antMatchers("/api/user/**").hasRole("USER")
.anyRequest().denyAll()
.and()
// JWT 인증 필터 적용
.addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class)
// 에러 핸들링
.exceptionHandling()
.accessDeniedHandler(new AccessDeniedHandler() {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// 권한 문제가 발생했을 때 이 부분을 호출한다.
response.setStatus(403);
response.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write("권한이 없는 사용자입니다.");
}
})
.authenticationEntryPoint(new AuthenticationEntryPoint() {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
// 인증문제가 발생했을 때 이 부분을 호출한다.
response.setStatus(401);
response.setCharacterEncoding("utf-8");
response.setContentType("text/html; charset=UTF-8");
response.getWriter().write("인증되지 않은 사용자입니다.");
}
})
.and()
.oauth2Login()
.successHandler(oAuth2LoginSuccessHandler)
.failureHandler(oAuth2LoginFailureHandler)
.userInfoEndpoint().userService(customOAuth2UserService)
;
OAuth2 Login 성공, 실패, 커스텀서비스로 스코프를 돌리기 위해 설정한 config 코드이다. 새롭게 추가된 필드 3가지를 주입해준 후 사용하자. 추가된 부분은 아래에 따로 명시했다.
private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler;
private final CustomOAuth2UserService customOAuth2UserService;
{ ...
.and()
.oauth2Login()
.successHandler(oAuth2LoginSuccessHandler)
.failureHandler(oAuth2LoginFailureHandler)
.userInfoEndpoint().userService(customOAuth2UserService);
}
CustomOAuth2User 수정사항
@Getter
public class CustomOAuth2User extends DefaultOAuth2User {
private String email;
private Role socialRole;
public CustomOAuth2User(Collection<? extends GrantedAuthority> authorities,
Map<String, Object> attributes, String nameAttributeKey, String email, Role socialRole) {
super(authorities, attributes, nameAttributeKey);
this.email = email;
this.socialRole = socialRole;
}
}
Role 필드값을 헷갈려서 다른 필드의 생성자를 주입했기 떄문에 해당 값을 수정해주었다. Role이 아니라 SocialRole이다.
카카오 로그인 구현
http://localhost:8080/oauth2/authorization/kakao 링크로 접속했을 경우, 아래와 같은 콘솔창 로그가 출력된다.
올바르게 설정됐을 경우는 위와 같은 페이지, 익숙한 페이지가 제공된다.
위 카카오 개인정보 제3자 제공 동의같은 경우는, 카카오 어플리케이션을 제작할 때 설정했던 부분들을 가져온다.
올바르게 진행했을 시, 아래의 콘솔 창과 함께 페이지 전환이 일어난다.
추가 정보를 입력하기 위해, 리다이렉트 시키는 링크주소가 security 설정이 안되있기 때문에 발생하는 페이지
db에도 제대로 정보가 들어온 것을 볼 수 있다. 우리는 social_role에 정보를 이용해서 GUEST라면 추가정보가 필요한 페이지로 리다이렉트 시키고, 추가정보를 입력 후 회원가입을 완료하면 USER로 변경시키고 메인페이지로 이동시킬 예정이다.
'SpringBoot > 프로젝트 게시판 만들기' 카테고리의 다른 글
React props에서 정보 받기 (부모-아들) (1) | 2023.11.06 |
---|---|
쇼핑몰 물품 등록하기 [1] - DB (1) | 2023.10.18 |
커스텀 Login + OAuth2 (구글, 네이버, 카카오) API 구현 [-] 트러블 슈팅 (0) | 2023.10.06 |
커스텀 Login + OAuth2 (구글, 네이버, 카카오) API 구현 [4] - OAuth2 로그인 구현 (0) | 2023.09.26 |
Spring 쇼핑몰 프로젝트 (6) - 로그인 / React with SpringBoot (0) | 2023.09.22 |