Skip to content

Commit

Permalink
[MERGE] Merge pull request #150 from Team-WSS/feat/#145
Browse files Browse the repository at this point in the history
[FEAT] 카카오 로그인 구현
  • Loading branch information
Kim-TaeUk authored Sep 2, 2024
2 parents 42aa5af + 1adb4cc commit 776cdb3
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 13 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

//spring boot oauth2
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}

tasks.named('test') {
Expand Down
27 changes: 16 additions & 11 deletions src/main/java/org/websoso/WSSServer/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.websoso.WSSServer.config.jwt.CustomAccessDeniedHandler;
import org.websoso.WSSServer.config.jwt.CustomJwtAuthenticationEntryPoint;
import org.websoso.WSSServer.config.jwt.JwtAuthenticationFilter;
import org.websoso.WSSServer.oauth2.CustomAuthenticationSuccessHandler;

@Configuration
@RequiredArgsConstructor
Expand All @@ -22,6 +24,8 @@ public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler;
private final DefaultOAuth2UserService customOAuth2UserService;
private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;

private final String[] permitAllPaths = {
"/users/login",
Expand All @@ -42,23 +46,24 @@ public class SecurityConfig {

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
http
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(
AbstractHttpConfigurer::disable)
.sessionManagement(session -> {
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
})
.exceptionHandling(exception ->
{
.httpBasic(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exception -> {
exception.authenticationEntryPoint(customJwtAuthenticationEntryPoint);
exception.accessDeniedHandler(customAccessDeniedHandler);
});

http.authorizeHttpRequests(auth -> {
})
.authorizeHttpRequests(auth -> {
auth.requestMatchers(permitAllPaths).permitAll();
auth.anyRequest().authenticated();
})
.oauth2Login(oauth2 -> {
oauth2.redirectionEndpoint(endpoint -> endpoint.baseUri("/oauth2/callback/*"));
oauth2.userInfoEndpoint(endpoint -> endpoint.userService(customOAuth2UserService));
oauth2.successHandler(customAuthenticationSuccessHandler);
})
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
Expand Down
28 changes: 26 additions & 2 deletions src/main/java/org/websoso/WSSServer/domain/User.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.websoso.WSSServer.domain;

import static org.websoso.WSSServer.domain.common.Gender.M;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
Expand Down Expand Up @@ -27,7 +29,10 @@
@Table(uniqueConstraints = {
@UniqueConstraint(
name = "UNIQUE_NICKNAME_CONSTRAINT",
columnNames = "nickname")
columnNames = "nickname"),
@UniqueConstraint(
name = "UNIQUE_SOCIAL_ID_CONSTRAINT",
columnNames = "social_id")
})
public class User {

Expand All @@ -36,6 +41,9 @@ public class User {
@Column(nullable = false)
private Long userId;

@Column(nullable = false)
private String socialId;

@Column(columnDefinition = "varchar(10)", nullable = false)
private String nickname;
//TODO 일부 특수문자 제외, 앞뒤 공백 불가능
Expand Down Expand Up @@ -90,9 +98,25 @@ public void updateUserInfo(RegisterUserInfoRequest registerUserInfoRequest) {
public UserBasicInfo getUserBasicInfo(String avatarImage) {
return UserBasicInfo.of(this.getUserId(), this.getNickname(), avatarImage);
}

public void editMyInfo(EditMyInfoRequest editMyInfoRequest) {
this.gender = Gender.valueOf(editMyInfoRequest.gender());
this.birth = Year.of(editMyInfoRequest.birth());
}

private User(String socialId, String nickname, String email) {
this.intro = "안녕하세요";
this.gender = M;
this.birth = Year.now();
this.avatarId = 1;
this.isProfilePublic = true;
this.role = Role.USER;
this.socialId = socialId;
this.nickname = nickname;
this.email = email;
}

public static User createBySocial(String socialId, String nickname, String email) {
return new User(socialId, nickname, email);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.websoso.WSSServer.oauth2;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import org.websoso.WSSServer.config.jwt.JwtProvider;
import org.websoso.WSSServer.config.jwt.UserAuthentication;
import org.websoso.WSSServer.domain.User;
import org.websoso.WSSServer.oauth2.dto.CustomOAuth2User;
import org.websoso.WSSServer.repository.UserRepository;

@Component
@RequiredArgsConstructor
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

private final JwtProvider jwtProvider;
private final UserRepository userRepository;

@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException {
CustomOAuth2User customOAuth2UserDetails = (CustomOAuth2User) authentication.getPrincipal();
String socialId = customOAuth2UserDetails.getName();
User user = userRepository.findBySocialId(socialId);
UserAuthentication userAuthentication = new UserAuthentication(user.getUserId(), null, null);
String token = jwtProvider.generateToken(userAuthentication);

response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write("{\"authorization\": \"" + token + "\"}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.websoso.WSSServer.oauth2.dto;

import java.util.Collection;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

@RequiredArgsConstructor
public class CustomOAuth2User implements OAuth2User {

private final OAuth2UserDTO OAuth2UserDTO;

@Override
public Map<String, Object> getAttributes() {
return null;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

@Override
public String getName() {
return OAuth2UserDTO.socialId();
}

public String getUsername() {
return OAuth2UserDTO.name();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.websoso.WSSServer.oauth2.dto;

import java.util.Map;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class KakaoOAuth2Response implements OAuth2Response {

private final Map<String, Object> attributes;

@Override
public String getProvider() {
return "kakao";
}

@Override
public String getProviderId() {
return attributes.get("id").toString();
}

@Override
public String getName() {
Map<String, String> properties = (Map<String, String>) attributes.get("properties");
return properties.get("nickname");
}

@Override
public String getEmail() {
Map<String, String> kakaoAccount = (Map<String, String >) attributes.get("kakao_account");
return kakaoAccount.get("email");
}
}
12 changes: 12 additions & 0 deletions src/main/java/org/websoso/WSSServer/oauth2/dto/OAuth2Response.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.websoso.WSSServer.oauth2.dto;

public interface OAuth2Response {

String getProvider();

String getProviderId();

String getName();

String getEmail();
}
16 changes: 16 additions & 0 deletions src/main/java/org/websoso/WSSServer/oauth2/dto/OAuth2UserDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.websoso.WSSServer.oauth2.dto;

public record OAuth2UserDTO(
String name,
String email,
String socialId
) {

public static OAuth2UserDTO of(String name, String email, String socialId) {
return new OAuth2UserDTO(
name,
email,
socialId
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.websoso.WSSServer.oauth2.service;

import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.websoso.WSSServer.domain.User;
import org.websoso.WSSServer.oauth2.dto.CustomOAuth2User;
import org.websoso.WSSServer.oauth2.dto.KakaoOAuth2Response;
import org.websoso.WSSServer.oauth2.dto.OAuth2Response;
import org.websoso.WSSServer.oauth2.dto.OAuth2UserDTO;
import org.websoso.WSSServer.repository.UserRepository;

@Service
@Transactional
@RequiredArgsConstructor
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

private final UserRepository userRepository;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String registrationId = userRequest.getClientRegistration().getRegistrationId();

OAuth2Response oAuth2Response = null;
if (registrationId.equals("kakao")) {
oAuth2Response = new KakaoOAuth2Response(oAuth2User.getAttributes());
}

String socialId = oAuth2Response.getProvider() + "_" + oAuth2Response.getProviderId();
String defaultNickname =
oAuth2Response.getProvider().charAt(0) + "*" + oAuth2Response.getProviderId().substring(2, 10);
User existedUserOrNull = userRepository.findBySocialId(socialId);
if (existedUserOrNull == null) {
userRepository.save(User.createBySocial(socialId, defaultNickname, oAuth2Response.getEmail()));
OAuth2UserDTO oAuth2UserDTO = OAuth2UserDTO.of(
defaultNickname, oAuth2Response.getEmail(), socialId);
return new CustomOAuth2User(oAuth2UserDTO);
} else {
OAuth2UserDTO oAuth2UserDTO = OAuth2UserDTO.of(
existedUserOrNull.getNickname(), existedUserOrNull.getEmail(), existedUserOrNull.getSocialId());
return new CustomOAuth2User(oAuth2UserDTO);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,6 @@
public interface UserRepository extends JpaRepository<User, Long> {

boolean existsByNickname(String nickname);

User findBySocialId(String socialId);
}

0 comments on commit 776cdb3

Please sign in to comment.