Skip to content

Commit

Permalink
Merge pull request #179 from GEON-PPANG/feature/#176
Browse files Browse the repository at this point in the history
강제 머지.. 죄송티비..
  • Loading branch information
sung-silver authored Aug 30, 2023
2 parents 7a9fc2c + d99c2c7 commit e23e80f
Show file tree
Hide file tree
Showing 18 changed files with 370 additions and 110 deletions.
3 changes: 1 addition & 2 deletions api/src/main/java/com/org/gunbbang/AOP/LoggingAspect.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
// @Aspect
@Component
@RequiredArgsConstructor
public class LoggingAspect {
Expand Down
32 changes: 19 additions & 13 deletions api/src/main/java/com/org/gunbbang/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
import com.org.gunbbang.jwt.filter.JwtExceptionFilter;
import com.org.gunbbang.jwt.service.JwtService;
import com.org.gunbbang.login.filter.JsonUsernamePasswordAuthenticationFilter;
import com.org.gunbbang.login.handler.CustomLogoutHandler;
import com.org.gunbbang.login.handler.LoginFailureHandler;
import com.org.gunbbang.login.handler.LoginSuccessHandler;
import com.org.gunbbang.login.handler.LogoutSuccessHandler;
import com.org.gunbbang.login.handler.*;
import com.org.gunbbang.login.service.CustomOAuth2UserService;
import com.org.gunbbang.login.service.CustomUserDetailsService;
import com.org.gunbbang.repository.MemberRepository;
import javax.servlet.Filter;
Expand Down Expand Up @@ -37,6 +35,9 @@ public class SecurityConfig {
private final JwtService jwtService;
private final MemberRepository memberRepository;
private final ObjectMapper objectMapper;
private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler;
private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler;
private final CustomOAuth2UserService customOAuth2UserService;

@Qualifier("handlerExceptionResolver")
private final HandlerExceptionResolver handlerExceptionResolver;
Expand Down Expand Up @@ -73,19 +74,24 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
"/validation/email",
"/profile",
"/actuator/health")
.permitAll()
.and()
.permitAll();

// 소셜 로그인 설정
http.oauth2Login()
.successHandler(oAuth2LoginSuccessHandler)
.failureHandler(oAuth2LoginFailureHandler)
.userInfoEndpoint()
.userService(customOAuth2UserService);

// logout 구현
.logout()
// logout 구현
http.logout()
.logoutUrl("/auth/logout") // 로그아웃 URL 설정
.addLogoutHandler(customlogoutHandler())
.logoutSuccessHandler(customLogoutSuccessHandler())
.and()
.logoutSuccessHandler(customLogoutSuccessHandler());

// 필터 순서: JwtExceptionFilter -> JwtAuthenticationProcessingFilter -> LogoutFilter
// -> CustomJsonUsernamePasswordAuthenticationFilter
.addFilterAfter(customJsonUsernamePasswordAuthenticationFilter(), LogoutFilter.class)
// 필터 순서: JwtExceptionFilter -> JwtAuthenticationProcessingFilter -> LogoutFilter
// -> CustomJsonUsernamePasswordAuthenticationFilter
http.addFilterAfter(customJsonUsernamePasswordAuthenticationFilter(), LogoutFilter.class)
.addFilterBefore(
jwtAuthenticationProcessingFilter(), JsonUsernamePasswordAuthenticationFilter.class)
.addFilterBefore(jwtExceptionFilter(), JwtAuthenticationProcessingFilter.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ public class JwtAuthenticationProcessingFilter extends OncePerRequestFilter {
"/validation/nickname",
"/validation/email",
"/actuator/health",
"/favicon.ico");
"/favicon.ico",
"/oauth2/authorization/kakao",
"/login/oauth2/code/kakao");

private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

Expand Down Expand Up @@ -131,6 +133,7 @@ public void checkAccessTokenAndAuthentication(HttpServletRequest request)

String accessToken = jwtService.extractAccessTokenAsString(request);
Long memberId = jwtService.extractMemberIdClaim(accessToken);
System.out.println("memberId = " + memberId);

memberRepository.findById(memberId).ifPresent(this::saveAuthentication);
}
Expand All @@ -156,7 +159,8 @@ public void saveAuthentication(Member myMember) {
new UsernamePasswordAuthenticationToken(
userDetailsUser, // principle
null, // credential (보통 비밀번호. 인증 시에는 null로 들어감)
authoritiesMapper.mapAuthorities(userDetailsUser.getAuthorities()));
authoritiesMapper.mapAuthorities(
userDetailsUser.getAuthorities())); // 여기서 반환되는 값이 달라져야할거같은디

// SecurityContext에 Authentication 객체 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ protected void doFilterInternal(
filterChain.doFilter(request, response);
} catch (Exception e) {
log.info("JwtAuthenticationProcessingFilter에서 에러 발생. 에러 클래스 이름: " + e.getClass().getName());
log.info(e.getStackTrace()[0].toString());
log.info(e.getStackTrace()[1].toString());
log.info(e.getStackTrace()[2].toString());
log.info(e.getStackTrace()[3].toString());
log.info(e.getStackTrace()[4].toString());
log.info(e.getStackTrace()[5].toString());
log.info(e.getStackTrace()[6].toString());

request.setAttribute("exception", e);
resolver.resolveException(
request, response, null, (Exception) request.getAttribute("exception"));
Expand Down
20 changes: 20 additions & 0 deletions api/src/main/java/com/org/gunbbang/jwt/service/JwtService.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ public String createAccessToken(String email, Long memberId) {
.sign(Algorithm.HMAC512(secretKey));
}

// role.guest인 유저가 닉네임 변경할 수 있도록 발급할 액세스 토큰을 생성하는 로직
// public String createAccessToken(String email) {
// Date now = new Date();
// return JWT.create()
// .withSubject(ACCESS_TOKEN_SUBJECT) // jwt의 subject 지정 (AccessToken으로 지정)
// .withExpiresAt(new Date(now.getTime() + accessTokenExpirationPeriod)) // 토큰 만료시간 지정
// .withClaim(EMAIL_CLAIM, email)
// .sign(Algorithm.HMAC512(secretKey));
// }

// refreshToken 생성
public String createRefreshToken() {
Date now = new Date();
Expand Down Expand Up @@ -176,4 +186,14 @@ public boolean isTokenExpired(String token) {
return true;
}
}

public void updateRefreshToken(String email, String refreshToken) {
memberRepository
.findByEmail(email)
.ifPresent(
member -> {
member.updateRefreshToken(refreshToken);
memberRepository.saveAndFlush(member);
});
}
}
33 changes: 33 additions & 0 deletions api/src/main/java/com/org/gunbbang/login/CustomOAuth2User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.org.gunbbang.login;

import com.org.gunbbang.Role;
import java.util.Collection;
import java.util.Map;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;

@Getter
public class CustomOAuth2User extends DefaultOAuth2User {
private Long memberId;
private String email;
private Role role;

public CustomOAuth2User(
Collection<? extends GrantedAuthority> authorities,
Map<String, Object> attributes,
String nameAttributeKey,
Long memberId,
String email,
Role role) {
super(authorities, attributes, nameAttributeKey);
this.memberId = memberId;
this.email = email;
this.role = role;
}

@Override
public String toString() {
return "CustomOAuth2User{" + "email='" + email + '\'' + ", role=" + role + '}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public CustomUserDetails(
public Collection<? extends GrantedAuthority> getAuthorities() {
List<GrantedAuthority> auth = new ArrayList<>();
auth.add(new SimpleGrantedAuthority("USER"));
auth.add(new SimpleGrantedAuthority("Guest"));
return auth;
// return null;
}
Expand Down
61 changes: 61 additions & 0 deletions api/src/main/java/com/org/gunbbang/login/OAuthAttributes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.org.gunbbang.login;

import com.org.gunbbang.PlatformType;
import com.org.gunbbang.Role;
import com.org.gunbbang.entity.Member;
import com.org.gunbbang.login.userinfo.KakaoOAuth2UserInfo;
import com.org.gunbbang.login.userinfo.OAuth2UserInfo;
import com.org.gunbbang.repository.BreadTypeRepository;
import com.org.gunbbang.repository.NutrientTypeRepository;
import java.util.Map;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

// 소셜별로 받아오는 데이터가 다르므로 데이터 분기 처리하는 DTO 클래스
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class OAuthAttributes {
private String nameAttributeKey; // OAuth2 로그인 진행 시 키가 되는 필드 값
private OAuth2UserInfo oauth2UserInfo; // 소셜 타입별 로그인 유저 정보
private BreadTypeRepository breadTypeRepository;
private NutrientTypeRepository nutrientTypeRepository;

/**
* SocialType에 맞는 메소드 호출하여 OAuthAttributes 객체 변환 파라미터: userNameAttributeName -> OAuth2 로그인 시
* 키가(pk) 되는 값 / attribute OAuth 서비스의 유저 정보들 소셜 별 of 메서드 -> 각각 소셜 로그인 API에서 제공하는 회원의 식별값,
* attribute, nameAttribute를 저장 후 build
*/
public static OAuthAttributes of(
PlatformType platformType, String userNameAttribute, Map<String, Object> attributes) {
if (platformType == PlatformType.APPLE) {
// return ofAppple(userNameAttribute, attributes);
}
return ofKakao(userNameAttribute, attributes);
}

private static OAuthAttributes ofKakao(
String userNameAttributeName, Map<String, Object> attribute) {
return OAuthAttributes.builder()
.nameAttributeKey(userNameAttributeName)
.oauth2UserInfo(new KakaoOAuth2UserInfo(attribute))
.build();
}

// apple 로그인 필요한 정보 어떤식으로 넘기는지 찾아봐야 함
// private static OAuthAttribute ofApple(){
//
// }

// TODO: breadtype, mainpurpose,
public Member toEntity(PlatformType platformType, OAuth2UserInfo oauth2UserInfo) {
return Member.builder()
.platformType(platformType)
.email(oauth2UserInfo.getEmail())
.nickname("GUEST")
.role(Role.GUEST)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,7 @@ public void onAuthenticationSuccess(

// accessToken 및 refreshToken 헤더에 전송
jwtService.sendAccessAndRefreshToken(response, accessToken, refreshToken);

memberRepository
.findByEmail(email)
.ifPresent(
member -> {
member.updateRefreshToken(refreshToken);
memberRepository.saveAndFlush(member);
});
jwtService.updateRefreshToken(email, refreshToken);
log.info("로그인 요청 성공. 이메일 : {} || memberId : {} ", email, memberId);
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
log.info("auth: {}", auth.toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.org.gunbbang.login.handler;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

@Slf4j
@Component
public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(
HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("소셜 로그인 실패! 서버 로그를 확인해주세요.");
log.info("소셜 로그인에 실패했습니다. 에러메시지: {}", exception.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.org.gunbbang.login.handler;

import com.org.gunbbang.Role;
import com.org.gunbbang.jwt.service.JwtService;
import com.org.gunbbang.login.CustomOAuth2User;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler {
private final JwtService jwtService;

@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
System.out.println("OAuth2 Login 성공!");
log.info("OAuth2 Login 성공!");
try {
CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();
System.out.println("oAuth2User = " + oAuth2User);

// User의 Role이 GUEST일 경우 처음 요청한 회원이므로 닉네임 설정으로 갈 수 있도록 값을 알려줌
// TODO: 닉네임 업데이트 시 Role.GUEST인 경우에는 변경 완료되면 Role을 업데이트해야함
if (oAuth2User.getRole() == Role.GUEST) {
String accessToken = jwtService.createAccessToken(oAuth2User.getEmail(), 6L);
response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken);
} else {
loginSuccess(response, oAuth2User);
}
} catch (Exception e) {
throw e;
}
}

private void loginSuccess(HttpServletResponse response, CustomOAuth2User oAuth2User)
throws IOException {
String accessToken = jwtService.createAccessToken(oAuth2User.getEmail(), 6L);
String refreshToken = jwtService.createRefreshToken();
response.addHeader(jwtService.getAccessHeader(), "Bearer " + accessToken);
response.addHeader(jwtService.getRefreshHeader(), "Bearer " + accessToken);

jwtService.sendAccessAndRefreshToken(response, accessToken, refreshToken);
jwtService.updateRefreshToken(oAuth2User.getEmail(), refreshToken);
}
}
Loading

0 comments on commit e23e80f

Please sign in to comment.