Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: DIG-65 카카오 소셜로그인 구현방법 변경 #34

Merged
merged 3 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 24 additions & 20 deletions src/main/java/com/ogjg/daitgym/config/security/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ogjg.daitgym.config.security;

import com.ogjg.daitgym.config.security.jwt.authentication.JwtAuthenticationProvider;
import com.ogjg.daitgym.config.security.jwt.filter.JwtAccessTokenAuthenticationFilter;
import com.ogjg.daitgym.config.security.jwt.filter.JwtRefreshTokenAuthenticationFilter;
import com.ogjg.daitgym.config.security.jwt.handler.JwtAuthenticationEntryPoint;
import com.ogjg.daitgym.config.security.oauth.CustomOAuth2UserService;
import com.ogjg.daitgym.domain.Role;
Expand All @@ -15,10 +17,12 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.CorsUtils;
Expand All @@ -45,16 +49,17 @@ public class SecurityConfig {

private final List<String> permitJwtUrlList = new ArrayList<>(
List.of(

"/",
"/favicon.ico",
"/login/oauth2/callback/kakao.*",
"/login/oauth2/code/.*",
"/oauth2/authorization/.*",
"/api/users/token",
"/api/token/new",
"/health",
"/ws/.*",
"/chat/.*"

"/chat/.*",
"/h2-console/.*"
));

@Bean
Expand All @@ -68,16 +73,15 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.frameOptions((frameOptionsConfig) -> frameOptionsConfig.sameOrigin())
)
)
// .addFilterBefore(jwtAccessTokenAuthenticationFilter(), OAuth2AuthorizationRequestRedirectFilter.class)
// .addFilterAfter(jwtRefreshTokenAuthenticationFilter(), JwtAccessTokenAuthenticationFilter.class)
.addFilterBefore(jwtAccessTokenAuthenticationFilter(), OAuth2AuthorizationRequestRedirectFilter.class)
.addFilterAfter(jwtRefreshTokenAuthenticationFilter(), JwtAccessTokenAuthenticationFilter.class)
.authorizeHttpRequests(
authorize -> authorize
.requestMatchers(CorsUtils::isPreFlightRequest)
.permitAll()
.requestMatchers("/**").permitAll()
.requestMatchers("/api/admins/**").hasRole(Role.ADMIN.name())
.requestMatchers("/api/trainers/**").hasRole(Role.TRAINER.name())
.requestMatchers("/api/profiles/**").hasRole(Role.USER.name())
.requestMatchers(new AntPathRequestMatcher("/api/admins/**")).hasRole(Role.ADMIN.name())
.requestMatchers(new AntPathRequestMatcher("/api/trainers/**")).hasRole(Role.TRAINER.name())
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll()
.anyRequest().authenticated()
).exceptionHandling((exceptionHandle) -> exceptionHandle
.accessDeniedHandler(accessDeniedHandler)
Expand Down Expand Up @@ -115,17 +119,17 @@ public CorsConfigurationSource corsConfigurationSource() {
public AuthenticationManager authenticationManager() throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
//
// @Bean
// public JwtAccessTokenAuthenticationFilter jwtAccessTokenAuthenticationFilter() throws Exception {
// authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider());
// return new JwtAccessTokenAuthenticationFilter(authenticationManager(), jwtAuthenticationEntryPoint(), permitJwtUrlList);
// }
//
// @Bean
// public JwtRefreshTokenAuthenticationFilter jwtRefreshTokenAuthenticationFilter() throws Exception {
// return new JwtRefreshTokenAuthenticationFilter(authenticationManager(), jwtAuthenticationEntryPoint());
// }

@Bean
public JwtAccessTokenAuthenticationFilter jwtAccessTokenAuthenticationFilter() throws Exception {
authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider());
return new JwtAccessTokenAuthenticationFilter(authenticationManager(), jwtAuthenticationEntryPoint(), permitJwtUrlList);
}

@Bean
public JwtRefreshTokenAuthenticationFilter jwtRefreshTokenAuthenticationFilter() throws Exception {
return new JwtRefreshTokenAuthenticationFilter(authenticationManager(), jwtAuthenticationEntryPoint());
}

@Bean
public AuthenticationProvider jwtAuthenticationProvider() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ public class JwtAccessTokenAuthenticationFilter extends OncePerRequestFilter {

private final List<String> permitUrlList;

/**
* AccessToken, RefreshToken에 사용하는 Provider의 기능이 완전히 같아서 1개의 Provider만 사용
* Provider에서 발생한 예외가 AccessToken에서 발생한 예외인지, RefreshToken에서 발생한 예외인지 구분이 필요했습니다.
* 그래서 기존 메시지를 집어넣어 되던졌습니다.
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (isPermitted(request.getRequestURI())) {
Expand All @@ -50,7 +45,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
} catch (JwtException jwtException) {
authenticationEntryPoint.commence(
request, response,
new AccessTokenException(ErrorCode.ACCESS_TOKEN_AUTHENTICATION_FAIL.getMessage() + SPACE + jwtException.getMessage())
new AccessTokenException(ErrorCode.ACCESS_TOKEN_AUTHENTICATION_FAIL.getMessage())
);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.ogjg.daitgym.config.security.jwt.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ogjg.daitgym.common.exception.ErrorCode;
import com.ogjg.daitgym.common.response.ApiResponse;
import com.ogjg.daitgym.config.security.details.OAuth2JwtUserDetails;
import com.ogjg.daitgym.config.security.jwt.authentication.JwtAuthenticationToken;
import com.ogjg.daitgym.config.security.jwt.dto.JwtUserClaimsDto;
import com.ogjg.daitgym.config.security.jwt.exception.RefreshTokenException;
import io.jsonwebtoken.JwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -19,6 +22,7 @@
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Arrays;

import static com.ogjg.daitgym.config.security.jwt.util.JwtUtils.*;

Expand Down Expand Up @@ -46,11 +50,20 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
String accessToken = generateAccessToken(authentication);
addTokenInHeader(response, accessToken);

filterChain.doFilter(request, response);
ObjectMapper objectMapper = new ObjectMapper();

ApiResponse<?> apiResponse = new ApiResponse<>(ErrorCode.SUCCESS);
String successResponse = objectMapper.writeValueAsString(apiResponse);

response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(successResponse);
response.getWriter().flush();

} catch (JwtException jwtException) {
authenticationEntryPoint.commence(
request, response,
new RefreshTokenException(ErrorCode.ACCESS_TOKEN_AUTHENTICATION_FAIL.getMessage() + SPACE + jwtException.getMessage())
new RefreshTokenException(ErrorCode.ACCESS_TOKEN_AUTHENTICATION_FAIL.getMessage())
);
}
}
Expand All @@ -60,18 +73,17 @@ private boolean isAccessTokenExpired(HttpServletRequest request) {
}

private Authentication authenticate(HttpServletRequest request) {
String jwt = validAndGetAccessToken(request);
Authentication authentication = authenticationManager.authenticate(new JwtAuthenticationToken(jwt));
String refreshToken = getRefreshToken(request);
Authentication authentication = authenticationManager.authenticate(new JwtAuthenticationToken(refreshToken));
return authentication;
}

private String validAndGetAccessToken(HttpServletRequest request) {
String jwt = request.getHeader(HEADER_AUTHORIZATION);

TokenValidator.validateHasToken(jwt);
TokenValidator.validatePrefix(jwt);

return jwt.substring(TOKEN_PREFIX.length());
private String getRefreshToken(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
return Arrays.stream(cookies)
.filter(cookie -> "refreshToken".equals(cookie.getName()))
.findFirst()
.orElseThrow(RefreshTokenException::new)
.getValue();
}

private static String generateAccessToken(Authentication authentication) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package com.ogjg.daitgym.config.security.oauth.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ogjg.daitgym.common.exception.ErrorCode;
import com.ogjg.daitgym.common.response.ApiResponse;
import com.ogjg.daitgym.config.security.details.OAuth2JwtUserDetails;
import com.ogjg.daitgym.config.security.jwt.dto.JwtUserClaimsDto;
import com.ogjg.daitgym.config.security.oauth.dto.LoginResponseDto;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -49,13 +46,13 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo

String cachedUrl = getCachedUrlOrDefault(request, response);

objectMapper.writeValue(
response.getWriter(),
new ApiResponse<>(
ErrorCode.SUCCESS,
LoginResponseDto.of(OAuth2UserDetails, cachedUrl)
)
);
// objectMapper.writeValue(
// response.getWriter(),
// new ApiResponse<>(
// ErrorCode.SUCCESS,
// LoginResponseDto.of(OAuth2UserDetails, cachedUrl)
// )
// );
}

private void addTokensInHeader(HttpServletResponse response, JwtUserClaimsDto jwtUserClaimsDto) {
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/com/ogjg/daitgym/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,8 @@ public void changeHealthClub(HealthClub newHealthClub) {
}
this.healthClub = newHealthClub;
}
}

public boolean isAdmin() {
return this.role == Role.ADMIN;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.ogjg.daitgym.user.controller;

import com.ogjg.daitgym.common.exception.ErrorCode;
import com.ogjg.daitgym.common.response.ApiResponse;
import com.ogjg.daitgym.user.dto.LoginResponseDto;
import com.ogjg.daitgym.user.service.AuthService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class LoginController {

private final AuthService authService;

@GetMapping("/login/oauth2/callback/kakao")
public ApiResponse<LoginResponseDto> kakaoLogin(
@RequestParam("code") String code,
HttpServletResponse httpServletResponse
) {
String kakaoAccessToken = authService.getKakaoAccessToken(code).getAccess_token();

return new ApiResponse<>(
ErrorCode.SUCCESS,
authService.kakaoLogin(kakaoAccessToken, httpServletResponse)
);
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/ogjg/daitgym/user/dto/KakaoAccountDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.ogjg.daitgym.user.dto;

import lombok.Data;
import lombok.Getter;

@Getter
public class KakaoAccountDto {

private Long id;
private KakaoAccount kakao_account;
@Data
public static class KakaoAccount {

private String email;
private Profile profile;

@Data
public static class Profile {

private String nickname;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
package com.ogjg.daitgym.config.security.oauth.dto;
package com.ogjg.daitgym.user.dto;

import com.ogjg.daitgym.config.security.details.OAuth2JwtUserDetails;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.UUID;

@Getter
@Builder
@NoArgsConstructor
public class LoginResponseDto {
private String email;

private String nickname;

private String userImg;

private String initialRequestUrl;
private String preferredSplit;

private boolean isAlreadyJoined;

public LoginResponseDto(String email, String nickname, String userImg, String initialRequestUrl, boolean isAlreadyJoined) {
this.email = email;
private boolean isAdmin;

private boolean isDeleted;

@Builder
public LoginResponseDto(String nickname, String userImg, String preferredSplit, boolean isAlreadyJoined, boolean isAdmin, boolean isDeleted) {
this.nickname = nickname;
this.userImg = userImg;
this.initialRequestUrl = initialRequestUrl;
this.preferredSplit = preferredSplit;
this.isAlreadyJoined = isAlreadyJoined;
this.isAdmin = isAdmin;
this.isDeleted = isDeleted;
}

public static LoginResponseDto of(OAuth2JwtUserDetails oAuth2UserDetails, String initialRequestUrl) {
public static LoginResponseDto from(OAuth2JwtUserDetails oAuth2UserDetails) {
return LoginResponseDto.builder()
.nickname(oAuth2UserDetails.getNickname())
.email(oAuth2UserDetails.getEmail())
.nickname(UUID.randomUUID().toString())
.userImg("default")
.initialRequestUrl(initialRequestUrl)
.isAlreadyJoined(oAuth2UserDetails.isAlreadyJoined())
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,19 @@

import com.ogjg.daitgym.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User,String> {
Optional<User> findByNickname(String nickName);

Optional<User> findByEmail(String email);


@Query("""
select u from User u where u.email = :email
""")
Optional<User> findByEmailIncludingDeleted(@Param("email") String email);
}
Loading