application-oauth.yml
spring:
security:
oauth2:
client:
registration:
kakao:
client-id: "Kakao Developers에서 받은 client-id "
client-secret: "Kakao Developers에서 받은 client-secret"
redirect-uri: "Kakao Developers에서 지정한 redirect-uri "
authorization-grant-type: authorization_code
client-authentication-method: POST
client-name: Kakao
CustomOAuth2UserService
DefaultOAuth2UserService을 상속 받은 커스텀 클래스. loadUser라는 메소드에 의해 후처리가 일어난다. userRequest에는 프로필 정보가 들어있다.
@Service
@Slf4j
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final AccountRepository accountRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
return processOAuth2User(userRequest, oAuth2User);
}
private OAuth2User processOAuth2User(OAuth2UserRequest userRequest, OAuth2User oAuth2User) {
OAuth2UserInfo oAuth2UserInfo = null;
if (userRequest.getClientRegistration().getRegistrationId().equals("google")) {
System.out.println("구글 로그인 요청");
oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
} else if (userRequest.getClientRegistration().getRegistrationId().equals("facebook")) {
System.out.println("페이스북 로그인 요청");
oAuth2UserInfo = new FaceBookUserInfo(oAuth2User.getAttributes());
} else if (userRequest.getClientRegistration().getRegistrationId().equals("github")) {
System.out.println("깃허브 로그인 요청");
oAuth2UserInfo = new GitHubUserInfo(oAuth2User.getAttributes());
} else if (userRequest.getClientRegistration().getRegistrationId().equals("kakao")) {
System.out.println("카카오 로그인 요청");
oAuth2UserInfo = new KakaoUserInfo(oAuth2User.getAttributes());
} else if (userRequest.getClientRegistration().getRegistrationId().equals("naver")) {
System.out.println("네이버 로그인 요청");
oAuth2UserInfo = new NaverUserInfo(oAuth2User.getAttributes());
} else {
System.out.println("우리는 구글과 페이스북만");
}
Optional<Account> userOptional =
accountRepository.findByProviderAndProviderId(oAuth2UserInfo.getProvider(), oAuth2UserInfo.getProviderId());
Account user;
if (userOptional.isPresent()) {
user = userOptional.get();
// user가 존재하면 update 해주기
user.setEmail(oAuth2UserInfo.getEmail());
accountRepository.save(user);
} else {
// user의 패스워드가 null이기 때문에 OAuth 유저는 일반적인 로그인을 할 수 없음.
user = Account.builder()
.nickname(oAuth2UserInfo.getProvider() + "_" + oAuth2UserInfo.getProviderId())
.email(oAuth2UserInfo.getEmail())
.name(oAuth2UserInfo.getName())
.password(bCryptPasswordEncoder.encode("oauth2_password"))
.roles("USER")
.provider(oAuth2UserInfo.getProvider())
.providerId(oAuth2UserInfo.getProviderId())
.build();
accountRepository.save(user);
}
return new UserAccount(user, oAuth2User.getAttributes());
}
}
OAuth2SuccessHandler
AuthenticationSuccessHandler을 상속 받은 커스텀 클래스. RedirectStrategy을 이용해 소셜 로그인 성공 후 이동할 url을 지정한다.
@Component
@RequiredArgsConstructor
public class OAuth2SuccessHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
private final JWTUtil jwtUtil;
private final ObjectMapper objectMapper;
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
protected Log logger = LogFactory.getLog(this.getClass());
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// SecurityContextHolder.getContext().setAuthentication(authentication);
response.addHeader(JWTUtil.AUTHORIZATION_HEADER, JWTUtil.BEARER + jwtUtil.generate(authentication));
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
protected void handle(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException {
String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug(
"Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(final Authentication authentication) {
Map<String, String> roleTargetUrlMap = new HashMap<>();
roleTargetUrlMap.put("ROLE_USER", "/");
roleTargetUrlMap.put("ROLE_ADMIN", "/");
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
String authorityName = grantedAuthority.getAuthority();
if(roleTargetUrlMap.containsKey(authorityName)) {
return roleTargetUrlMap.get(authorityName);
}
}
throw new IllegalStateException();
}
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
SecurityConfig
CustomOAuth2UserService와 OAuth2SuccessHandler을 설정 메소드에 등록한다.
@Override
protected void configure(HttpSecurity http) throws Exception {
JWTLoginFilter jwtLoginFilter = new JWTLoginFilter(objectMapper, jwtUtil, accountService, authenticationManager());
JWTCheckFilter jwtCheckFilter = new JWTCheckFilter(authenticationManager(), accountService, jwtUtil);
http.
csrf().disable()
.authorizeRequests().antMatchers("/", "/css/**", "/img/**", "/js/**", "/h2-console/**").permitAll()
.and()
.formLogin(
config -> {
config.loginPage("/login")
.successForwardUrl("/")
.failureForwardUrl("/login?error=true");
}
)
.addFilter(jwtLoginFilter)
.addFilter(jwtCheckFilter)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.oauth2Login(oauth -> {
oauth
.userInfoEndpoint(userinfo -> {
userinfo.userService(customOAuth2UserService);
})
.successHandler(oAuth2SuccessHandler)
.loginPage("/login");
});
}
참고
'Programming > Spring' 카테고리의 다른 글
| Spring Security: Authentication (0) | 2022.01.21 |
|---|---|
| The Custom Authentication Success Handle (0) | 2022.01.20 |
| OAuth2(2) OAuth2 Client 라이브러리 없이 카카오 로그인 (0) | 2022.01.20 |
| OAuth2(1) OAuth2 구성 및 동작 (0) | 2022.01.18 |
| CORS (0) | 2021.12.14 |
