Skip to content

Commit

Permalink
release: 0.1.5 (#116)
Browse files Browse the repository at this point in the history
* feat: 모임 삭제 기능 구현 (#102)

* refactor: 기획 변경에 따른 회원 카드 등록 API 리팩토링 (#105)

* refactor: sql 변경 (#104)

* refactor: User 클래스 필드명 변경 (#104)

* refactor: API 변경에 따른 코드 리팩토링 (#104)

* refactor: 불필요한 getId() 제거 ( 피드백 반영 ) (#104)

* feat: 유효성 검증 관련 에러 일괄 처리을 위한 메소드 추가 (#104)

* test: API 변경에 따른 통합 테스트 수정 및 단위 테스트 구현 (#104)

* refactor: UserMeGetResponse 필드 변경 (#104)

* fix: CI 에러 수정 (#104)

* refactor: 피드백 반영 (#104)

* fix: CI 에러 수정 (#104)

* refactor: JWT 에러 응답 리팩토링 및 인가 인증 예최 처리 코드 수정 (#103)

* refactor: 기타 코드 리팩토링( 피드백 반영 ) (#101)

* chore: JWT 관련 의존성 변경 (#101)

* refactor: JWT 및 인증 관련 로직 리팩토링 (#101)

* test: 테스트 코드 및 설정 관련 변경(#101)

* refactor: ObjectMapper Autowired 로 주입 (#101)

* refactor: AuthService 반환 타입 Optional<User> -> User 변경 (#101)

* fix: V6_create_users_interests.sql 추가 (#109) (#110)

* refactor: 회원 카드 등록시 JWT 정보도 함께 반환하도록 수정 (#114)

* chore : application.properties 에 jwt 관련 설정 값 추가 (#113)

* refactor : userService 회원 카드 등록 로직 수정 (#113)

* feat : UserRegisterResponse 필드 추가(#113)

* test: API 변경에 따른 테스트 관련 코드 수정 (#113)

* fix: 소셜 로그인 관련 500 에러 수정 및 OAuth 로직 일부 개선 (#112)

* fix: OAuthLoginController @RestController 어노테이션 추가(나는 바보..) 및 favicon 관련 임시 컨트롤러 생성 (#111)

* refactor : 설정 yml 리팩토링 (#111)

* refactor : SecurityConfig 리팩토링 (#111)

* refactor : 기타 OAuth 관련 로직 리팩토링(#111)

* refactor : Cors 허용 주소 임시 전부 허용 (#111)

* fix : SonarCloud 오류 수정 (#111)

* fix : SonarCloud 오류 수정 (#111)

* feat: 위치 기반 API 구현 및 테스트 (#108)

* refactor: 기타 코드 리팩토링( 피드백 반영 ) (#101)

* chore: JWT 관련 의존성 변경 (#101)

* refactor: JWT 및 인증 관련 로직 리팩토링 (#101)

* test: 테스트 코드 및 설정 관련 변경(#101)

* refactor: ObjectMapper Autowired 로 주입 (#101)

* refactor: AuthService 반환 타입 Optional<User> -> User 변경 (#101)

* refactor: application.properties redis.port 변경 (#91)

* refactor: 기존 Redis 설정 리팩토링 및 추가 구현 (#91)

* feat: 유저 위치 기반 관련 DTO 및 VO 구현 (#91)

* feat: 유저 위치 기반 API 구현 (#91)

* test: 유저 위치 기반 통합 테스트 관련 클래스 구현 및 테스트 (#91)

* test: redis port 변경에 따른 테스트 수정 (#91)

* fix: CI 에러 수정 (#91)

* fix: sonarCloud 에러 수정 (#91)

* refactor: 리뷰 반영 (#108)

---------

Co-authored-by: ddingmin <[email protected]>
  • Loading branch information
choidongkuen and ddingmin authored Jan 18, 2024
1 parent b370f46 commit 6d9d376
Show file tree
Hide file tree
Showing 56 changed files with 1,355 additions and 526 deletions.
4 changes: 1 addition & 3 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,4 @@ sentryVersion=4.1.1
### AWS-CLOUD ###
springCloudAwsVersion=3.1.0
### JWT ###
jwtVersion=0.9.1
jwtBindingVersion=4.0.1
jwtJaxbApiVersion=2.3.1
jwtVersion=0.11.5
10 changes: 3 additions & 7 deletions gradle/devtool.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@ allprojects {
compileOnly "org.projectlombok:lombok:${lombokVersion}"
annotationProcessor "org.projectlombok:lombok"

implementation "io.jsonwebtoken:jjwt:${jwtVersion}"

// com.sun.xml.bind
implementation "com.sun.xml.bind:jaxb-impl:${jwtBindingVersion}"
implementation "com.sun.xml.bind:jaxb-core:${jwtBindingVersion}"
// javax.xml.bind
implementation "javax.xml.bind:jaxb-api:${jwtJaxbApiVersion}"
implementation "io.jsonwebtoken:jjwt-api:${jwtVersion}"
runtimeOnly "io.jsonwebtoken:jjwt-impl:${jwtVersion}"
runtimeOnly "io.jsonwebtoken:jjwt-jackson:${jwtVersion}"

testCompileOnly "org.projectlombok:lombok:${lombokVersion}"
testAnnotationProcessor "org.projectlombok:lombok"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,26 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequiredArgsConstructor
public class OAuthLoginController {

private final OAuthService oAuthService;

@GetMapping("/logins/callbacks/{provider}")
@ResponseStatus(HttpStatus.OK)
public TokenResponse oAuthLogin(
@PathVariable String provider,
@RequestParam String code) {
return oAuthService.oAuthLogin(provider, code);
public TokenResponse oAuthLogin(@PathVariable String provider,
@RequestParam String code,
@RequestParam String state) {
return oAuthService.oAuthLogin(provider, code, state);
}

@GetMapping("/favicon.ico")
@ResponseStatus(HttpStatus.OK)
public Void favicon() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public KakaoOAuthUserInfo(Map<String, Object> attributes) {

@Override
public String getOAuthId() {
return (String) attributes.get("id");
return String.valueOf(attributes.get("id"));
}
}
22 changes: 2 additions & 20 deletions src/main/java/net/teumteum/auth/domain/OAuthToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,11 @@
import com.fasterxml.jackson.annotation.JsonProperty;

public record OAuthToken(
@JsonProperty("token_type")
String tokenType,
@JsonProperty("access_token")
String accessToken,
String scope,

@JsonProperty("expires_in")
Integer expiresIn
@JsonProperty("token_type")
String tokenType
) {

public String getTokenType() {
return this.tokenType;
}

public String getAccessToken() {
return this.accessToken;
}

public String getScope() {
return this.scope;
}

public Integer getExpiresIn() {
return this.expiresIn;
}
}
10 changes: 4 additions & 6 deletions src/main/java/net/teumteum/auth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package net.teumteum.auth.service;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.teumteum.auth.domain.response.TokenResponse;
Expand All @@ -25,16 +24,15 @@ public TokenResponse reissue(HttpServletRequest request) {
String accessToken = jwtService.extractAccessToken(request);

checkRefreshTokenValidation(refreshToken);

User user = findUserByAccessToken(accessToken).orElseThrow(
() -> new IllegalArgumentException("access token 에 해당하는 user를 찾을 수 없습니다."));
User user = findUserByAccessToken(accessToken);

checkRefreshTokenMatch(user, refreshToken);
return issueNewToken(user);
}

public Optional<User> findUserByAccessToken(String accessToken) {
return userConnector.findUserById(Long.parseLong(jwtService.getUserIdFromToken(accessToken)));
public User findUserByAccessToken(String accessToken) {
return userConnector.findUserById(jwtService.getUserIdFromToken(accessToken))
.orElseThrow(() -> new IllegalArgumentException("access token 에 해당하는 user를 찾을 수 없습니다."));
}

private void checkRefreshTokenValidation(String refreshToken) {
Expand Down
42 changes: 26 additions & 16 deletions src/main/java/net/teumteum/auth/service/OAuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static net.teumteum.core.security.Authenticated.카카오;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;

import java.net.URLEncoder;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
Expand Down Expand Up @@ -41,12 +42,12 @@ public class OAuthService {
private final UserConnector userConnector;


public TokenResponse oAuthLogin(String registrationId, String code) {
public TokenResponse oAuthLogin(String registrationId, String code, String state) {
ClientRegistration clientRegistration = inMemoryClientRegistrationRepository.findByRegistrationId(
registrationId);
Authenticated authenticated = getAuthenticated(clientRegistration.getRegistrationId());
OAuthUserInfo oAuthUserInfo = getOAuthUserInfo(clientRegistration, authenticated, code);
return checkUserAndMakeResponse(oAuthUserInfo, authenticated);
OAuthUserInfo oAuthUserInfo = getOAuthUserInfo(clientRegistration, authenticated, code, state);
return makeResponse(oAuthUserInfo, authenticated);
}

private Authenticated getAuthenticated(String registrationId) {
Expand All @@ -57,47 +58,56 @@ private Authenticated getAuthenticated(String registrationId) {
}

private OAuthUserInfo getOAuthUserInfo(ClientRegistration clientRegistration, Authenticated authenticated,
String code) {
Map<String, Object> oAuthAttribute = getOAuthAttribute(clientRegistration, getToken(clientRegistration, code));
String code, String state) {
Map<String, Object> oAuthAttribute = getOAuthAttribute(clientRegistration,
getToken(clientRegistration, code, state));
if (authenticated == 네이버) {
return new NaverOAuthUserInfo(oAuthAttribute);
}
return new KakaoOAuthUserInfo(oAuthAttribute);
}

private TokenResponse checkUserAndMakeResponse(OAuthUserInfo oAuthUserInfo, Authenticated authenticated) {
private TokenResponse makeResponse(OAuthUserInfo oAuthUserInfo, Authenticated authenticated) {
String oauthId = oAuthUserInfo.getOAuthId();

return getUser(oauthId, authenticated)
.map(jwtService::createServiceToken)
.orElseGet(() -> new TokenResponse(oauthId));
}

private OAuthToken getToken(ClientRegistration clientRegistration, String code, String state) {
return WebClient.create().post()
.uri(clientRegistration.getProviderDetails().getTokenUri())
.headers(header -> {
header.setContentType(APPLICATION_FORM_URLENCODED);
header.setAcceptCharset(Collections.singletonList(UTF_8));
}).bodyValue(tokenRequest(clientRegistration, code, state))
.retrieve()
.bodyToMono(OAuthToken.class).block();
}

private Map<String, Object> getOAuthAttribute(ClientRegistration clientRegistration, OAuthToken oAuthToken) {
return WebClient.create().get().uri(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.headers(header -> header.setBearerAuth(oAuthToken.getAccessToken())).retrieve()
return WebClient.create().get()
.uri(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())
.headers(header -> header.setBearerAuth(oAuthToken.accessToken())).retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
}).block();
}

private OAuthToken getToken(ClientRegistration clientRegistration, String code) {
return WebClient.create().post().uri(clientRegistration.getProviderDetails().getTokenUri()).headers(header -> {
header.setContentType(APPLICATION_FORM_URLENCODED);
header.setAcceptCharset(Collections.singletonList(UTF_8));
}).bodyValue(tokenRequest(clientRegistration, code)).retrieve().bodyToMono(OAuthToken.class).block();
}

private Optional<User> getUser(String oauthId, Authenticated authenticated) {
return this.userConnector.findByAuthenticatedAndOAuthId(authenticated, oauthId);
}

private MultiValueMap<String, String> tokenRequest(ClientRegistration clientRegistration, String code) {
private MultiValueMap<String, String> tokenRequest(ClientRegistration clientRegistration, String code,
String state) {
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
formData.add("code", code);
formData.add("grant_type", "authorization_code");
formData.add("grant_type", clientRegistration.getAuthorizationGrantType().getValue());
formData.add("redirect_uri", clientRegistration.getRedirectUri());
formData.add("client_secret", clientRegistration.getClientSecret());
formData.add("client_id", clientRegistration.getClientId());
formData.add("state", URLEncoder.encode(state, UTF_8));
return formData;
}
}
5 changes: 3 additions & 2 deletions src/main/java/net/teumteum/core/config/RedisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,12 @@ public RedisConnectionFactory redisConnectionFactory() {
}

@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
15 changes: 9 additions & 6 deletions src/main/java/net/teumteum/core/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
@RequiredArgsConstructor
public class SecurityConfig {

private static final String[] PATTERNS = {"/css/**", "/images/**", "/js/**", "/favicon.ico", "/h2-console/**",
"/logins/**"};
private static final String[] PATTERNS = {"/css/**", "/images/**", "/js/**", "/favicon.ico/**", "/h2-console/**",
"/logins/**", "/auth/**"};

private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler accessDeniedHandler;
Expand All @@ -38,14 +38,18 @@ public class SecurityConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.ignoring()
.requestMatchers("/css/**", "/js/**", "/img/**", "/favicon.ico", "/error");
.requestMatchers("/css/**", "/js/**",
"/favicon.ico", "/resources/**"

);
}


@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable).cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(request -> request.requestMatchers("/auth/**", "/logins/**").permitAll()
.requestMatchers(HttpMethod.POST, "/users/registers").permitAll().requestMatchers(PATTERNS).permitAll()
.authorizeHttpRequests(request -> request.requestMatchers(PATTERNS).permitAll()
.requestMatchers(HttpMethod.POST, "/users/registers").permitAll()
.anyRequest().authenticated()).httpBasic(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(STATELESS))
Expand All @@ -61,7 +65,6 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("http://localhost:3000");
config.addAllowedOrigin("https://api.teum.org");
config.addAllowedHeader("*");
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"));
config.addExposedHeader("Authorization");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
package net.teumteum.core.security.filter;

import jakarta.servlet.ServletException;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.teumteum.core.error.ErrorResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

private final ObjectMapper objectMapper;

@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException
) throws IOException, ServletException {
HttpServletResponse response,
AccessDeniedException accessDeniedException
) throws IOException {
this.sendUnAuthorizedError(response, accessDeniedException);
}

private void sendUnAuthorizedError(HttpServletResponse response,
Exception exception) throws IOException {
Exception exception) throws IOException {
response.setStatus(SC_FORBIDDEN);
OutputStream os = response.getOutputStream();
log.error("Responding with unauthorized error. Message - {}", exception.getMessage());
response.sendError(SC_FORBIDDEN, exception.getMessage());
objectMapper.writeValue(os, ErrorResponse.of("인가 과정에서 오류가 발생했습니다."));
os.flush();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package net.teumteum.core.security.filter;

import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.teumteum.core.error.ErrorResponse;
import org.springframework.security.core.AuthenticationException;
Expand All @@ -13,22 +16,25 @@

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

private static final String ATTRIBUTE_NAME = "exception";

private final ObjectMapper objectMapper;

@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authenticationException
) throws IOException {
this.sendUnAuthenticatedError(response, authenticationException);
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authenticationException) throws IOException {
this.sendUnAuthenticatedError(request, response, authenticationException);
}

private void sendUnAuthenticatedError(HttpServletResponse response,
Exception exception) throws IOException {
private void sendUnAuthenticatedError(HttpServletRequest request, HttpServletResponse response, Exception exception)
throws IOException {
response.setStatus(SC_UNAUTHORIZED);
OutputStream os = response.getOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
log.error("Responding with unauthenticated error. Message - {}", exception.getMessage());
objectMapper.writeValue(os, ErrorResponse.of("인증 과정에서 오류가 발생했습니다."));
objectMapper.writeValue(os, ErrorResponse.of((String) request.getAttribute(ATTRIBUTE_NAME)));
os.flush();
}
}
Loading

0 comments on commit 6d9d376

Please sign in to comment.