Google Social 로그인
OAuth2UserService
1. DefaultOAuth2UserService 클래스를 상속해서 구현
2. 이메일을 이용한 회원가입 처리
@RequiredArgsConstructor
@Log4j2
@Service
public class ClubOAuthUserDetailsService extends DefaultOAuth2UserService {
private final ClubMemberRepository clubMemberRepository;
private final PasswordEncoder passwordEncoder;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("---------------Start---------------");
log.info("userRequest: " + userRequest);
String clientName = userRequest.getClientRegistration().getClientName();
log.info("clientName: " + clientName);
log.info(userRequest.getAdditionalParameters());
OAuth2User oAuth2User = super.loadUser(userRequest);
log.info("==============================");
oAuth2User.getAttributes().forEach((k,v) -> {
log.info(k + ":" + v);
});
String email = null;
if(clientName.equals("Google")) {
email = oAuth2User.getAttribute("email");
}
log.info("EMAIL: " + email);
ClubMember clubMember = saveSocialMember(email);
ClubAuthMemberDTO clubAuthMemberDTO = new ClubAuthMemberDTO(
clubMember.getEmail(),
clubMember.getPassword(),
true,
clubMember.getRoleSet().stream().map(
role -> new SimpleGrantedAuthority("ROLE_" + role.name())
).collect(Collectors.toList()),
oAuth2User.getAttributes()
);
clubAuthMemberDTO.setName(clubMember.getName());
return clubAuthMemberDTO;
}
private ClubMember saveSocialMember(String email) {
Optional<ClubMember> result = clubMemberRepository.findByEmail(email, true);
if(result.isPresent()) {
return result.get();
}
ClubMember clubMember = ClubMember.builder().email(email)
.name(email)
.password(passwordEncoder.encode("1111"))
.fromSocial(true)
.build();
clubMember.addMemberRole(ClubMemberRole.USER);
clubMemberRepository.save(clubMember);
return clubMember;
}
}
3. OAuth2User 타입
3-1. OAuth2User 타입을 ClubAuthMemberDTO 타입으로 사용할 수 있도록 DTO 수정
@Log4j2
@Getter
@Setter
@ToString
public class ClubAuthMemberDTO extends User implements OAuth2User {
private String email;
private String password;
private String name;
private boolean fromSocial;
private Map<String, Object> attr;
public ClubAuthMemberDTO(String username, String password, boolean fromSocial, Collection<? extends GrantedAuthority> authorities, Map<String, Object> attr ) {
this(username, password, fromSocial, authorities);
this.attr = attr;
}
public ClubAuthMemberDTO(String username, String password, boolean fromSocial, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
this.email = username;
this.password = password;
this.fromSocial = fromSocial;
}
@Override
public Map<String, Object> getAttributes() {
return this.attr;
}
}
4. 로그인 성공 이후 처리
4-1. AuthenticationSuccessHandler, AuthenticationFailureHandler
@Log4j2
public class ClubLoginSuccessHandler implements AuthenticationSuccessHandler {
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private PasswordEncoder passwordEncoder;
public ClubLoginSuccessHandler(PasswordEncoder passwordEncoder) {
this.passwordEncoder = passwordEncoder;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("----------------------------");
log.info("onAuthenticationSuccess");
ClubAuthMemberDTO authMember = (ClubAuthMemberDTO)authentication.getPrincipal();
boolean fromSocial = authMember.isFromSocial();
log.info("Need Modify Member?" + fromSocial);
boolean passwordResult = passwordEncoder.matches("1111", authMember.getPassword());
if(fromSocial && passwordResult) {
redirectStrategy.sendRedirect(request, response, "/member/modify?from=social");
}
}
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin();
http.csrf().disable();
http.logout();
http.oauth2Login().successHandler(successHandler());
http.rememberMe().tokenValiditySeconds(60*60*7).userDetailsService(userDetailsService);
return http.build();
}
5. Remember me
5-1. 웹 인증 방식 중에 쿠키를 사용하는 방식
> 한번 로그인한 사용자가 브라우저를 닫은 후에 다시 서비스에 접속해도 별도의 로그인 절차 없이 바로 로그인 처리가 진행 됨
5-2. Remember me 설정
SecurityConfig
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin();
http.csrf().disable();
http.logout();
http.oauth2Login().successHandler(successHandler());
http.rememberMe().tokenValiditySeconds(60*60*7).userDetailsService(userDetailsService);
return http.build();
}
6. 어노테이션 기반의 접근 제한 설정
6-1. @EnableGlobalMethodSecurity의 적용
- securedEnabled > 예전 버전의 @Secure 어노테이션이 사용 가능한지 지정
- @PreAuthoriuze를 이용하기 위해 prePostEnable 속성을 사용
6-2. @PreAuthorize
- 접근 제한이 필요한 컨트롤러의 메서드에 @PreAuthorize 적용 (value로는 문자열로 된 표현식을 넣어줌, # 기호 사용)
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Log4j2
public class SecurityConfig {
}
@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public void exAdmin() {
log.info("관리자가능..");
}
@GetMapping("/all")
@PreAuthorize("permitAll()")
public void exAll() {
log.info("전체가능..");
}
@PreAuthorize("#clubAuthMember != null && #clubAuthMember.username eq \"user95@kmj.org\"")
@GetMapping("/exOnly")
public String exMemberOnly(@AuthenticationPrincipal ClubAuthMemberDTO clubAuthMember {
log.info("exMemberOnly");
log.info("clubAuthMemberDTO: " + clubAuthMember);
return "/sample/admin";
}