Skip to content

Commit

Permalink
Merge pull request #32 from kakao-tech-campus-2nd-step3/feat/#2-autho…
Browse files Browse the repository at this point in the history
…rization

[Feat] #2 authorization을 구현했어요.
  • Loading branch information
sanghee0820 authored Oct 4, 2024
2 parents 8ede26a + db2553e commit 7738f1d
Show file tree
Hide file tree
Showing 21 changed files with 483 additions and 64 deletions.
24 changes: 24 additions & 0 deletions src/main/java/team7/inplace/global/exception/InplaceException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package team7.inplace.global.exception;

import lombok.Getter;
import org.springframework.http.HttpStatus;
import team7.inplace.global.exception.code.ErrorCode;

@Getter
public class InplaceException extends RuntimeException {

private final HttpStatus httpStatus;
private final String errorCode;
private final String errorMessage;

private InplaceException(ErrorCode errorCode) {
super(errorCode.message());
this.httpStatus = errorCode.httpStatus();
this.errorCode = errorCode.code();
this.errorMessage = errorCode.message();
}

public static InplaceException of(ErrorCode errorCode) {
return new InplaceException(errorCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package team7.inplace.global.exception.code;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@AllArgsConstructor
@Getter
public enum AuthorizationErrorCode implements ErrorCode {
TOKEN_IS_EMPTY(HttpStatus.BAD_REQUEST, "A001", "Token is Empty"),
INVALID_TOKEN(HttpStatus.BAD_REQUEST, "A002", "Invalid Token"),
TOKEN_IS_EXPIRED(HttpStatus.BAD_REQUEST, "A003", "Token is Expired");

private final HttpStatus httpStatus;
private final String errorCode;
private final String message;

@Override
public HttpStatus httpStatus() {
return httpStatus;
}

@Override
public String code() {
return errorCode;
}

@Override
public String message() {
return message;
}
}
12 changes: 12 additions & 0 deletions src/main/java/team7/inplace/global/exception/code/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package team7.inplace.global.exception.code;

import org.springframework.http.HttpStatus;

public interface ErrorCode {

HttpStatus httpStatus();

String code();

String message();
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
import org.springframework.security.oauth2.core.user.OAuth2User;
import team7.inplace.security.application.dto.CustomOAuth2User;
import team7.inplace.security.application.dto.KakaoOAuthResponse;
import team7.inplace.security.domain.User;
import team7.inplace.security.persistence.UserRepository;
import team7.inplace.user.application.UserService;
import team7.inplace.user.application.dto.UserCommand;

public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

private final DefaultOAuth2UserService defaultOAuth2UserService;
private final UserRepository userRepository;
private final UserService userService;

public CustomOAuth2UserService(DefaultOAuth2UserService defaultOAuth2UserService,
UserRepository userRepository) {
UserService userService) {
this.defaultOAuth2UserService = defaultOAuth2UserService;
this.userRepository = userRepository;
this.userService = userService;
}

@Transactional
Expand All @@ -28,21 +28,10 @@ public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest)
throws OAuth2AuthenticationException {
OAuth2User oAuth2User = defaultOAuth2UserService.loadUser(oAuth2UserRequest);
KakaoOAuthResponse kakaoOAuthResponse = new KakaoOAuthResponse(oAuth2User.getAttributes());
User user = register(kakaoOAuthResponse);
return CustomOAuth2User.of(user);
}

private User register(KakaoOAuthResponse kakaoOAuthResponse) {
if (isExistUser(kakaoOAuthResponse)) {
return userRepository.findByUsername(kakaoOAuthResponse.getEmail())
.orElseThrow();
if (userService.isExistUser(kakaoOAuthResponse.getEmail())) {
return CustomOAuth2User.makeExistUser(kakaoOAuthResponse);
}
User user = kakaoOAuthResponse.toUser();
userRepository.save(user);
return user;
}

private boolean isExistUser(KakaoOAuthResponse kakaoOAuthResponse) {
return userRepository.existsByUsername(kakaoOAuthResponse.getEmail());
userService.registerUser(UserCommand.Create.of(kakaoOAuthResponse));
return CustomOAuth2User.makeNewUser(kakaoOAuthResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;
import team7.inplace.security.domain.User;
import team7.inplace.security.domain.UserType;

public record CustomOAuth2User(
String username,
String nickname,
UserType userType
Long id,
Boolean firstUser
) implements OAuth2User {

@Override
Expand All @@ -28,7 +26,15 @@ public String getName() {
return username;
}

public static CustomOAuth2User of(User user) {
return new CustomOAuth2User(user.getUsername(), user.getNickname(), user.getUserType());
public static CustomOAuth2User makeExistUser(KakaoOAuthResponse kakaoOAuthResponse) {
return new CustomOAuth2User(kakaoOAuthResponse.getEmail(), null, false);
}

public static CustomOAuth2User makeNewUser(KakaoOAuthResponse kakaoOAuthResponse) {
return new CustomOAuth2User(kakaoOAuthResponse.getEmail(), null, true);
}

public Boolean isFirstUser() {
return this.firstUser;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package team7.inplace.security.application.dto;

import java.util.Map;
import team7.inplace.security.domain.User;
import team7.inplace.security.domain.UserType;

public record KakaoOAuthResponse(
Map<String, Object> attribute
Expand All @@ -12,10 +10,6 @@ public KakaoOAuthResponse(Map<String, Object> attribute) {
this.attribute = (Map<String, Object>) attribute.get("kakao_account");
}

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

public String getEmail() {
return attribute.get("email").toString();
}
Expand All @@ -24,7 +18,4 @@ public String getNickname() {
return ((Map<String, Object>) attribute.get("profile")).get("nickname").toString();
}

public User toUser() {
return new User(this.getEmail(), null, this.getNickname(), UserType.KAKAO);
}
}
22 changes: 20 additions & 2 deletions src/main/java/team7/inplace/security/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import team7.inplace.security.application.CustomOAuth2UserService;
import team7.inplace.security.filter.AuthorizationFilter;
import team7.inplace.security.filter.ExceptionHandlingFilter;
import team7.inplace.security.handler.CustomSuccessHandler;

@Configuration
Expand All @@ -17,15 +20,21 @@ public class SecurityConfig {

private final CustomOAuth2UserService customOauth2UserService;
private final CustomSuccessHandler customSuccessHandler;
private final AuthorizationFilter authorizationFilter;
private final ExceptionHandlingFilter exceptionHandlingFilter;

public SecurityConfig(CustomOAuth2UserService customOAuth2UserService,
CustomSuccessHandler customSuccessHandler) {
CustomSuccessHandler customSuccessHandler, AuthorizationFilter authorizationFilter,
ExceptionHandlingFilter exceptionHandlingFilter) {
this.customOauth2UserService = customOAuth2UserService;
this.customSuccessHandler = customSuccessHandler;
this.authorizationFilter = authorizationFilter;
this.exceptionHandlingFilter = exceptionHandlingFilter;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
public SecurityFilterChain securityFilterChain(HttpSecurity http)
throws Exception {

//h2-console 접속 가능
http.headers((headers) -> headers.frameOptions(FrameOptionsConfig::sameOrigin))
Expand All @@ -36,13 +45,22 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
http.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable)

//authentication Service, Handler 설정
.oauth2Login((oauth2) -> oauth2
.userInfoEndpoint((userInfoEndPointConfig) -> userInfoEndPointConfig
.userService(customOauth2UserService)).successHandler(customSuccessHandler))

//authentication Filter 설정
.addFilterBefore(authorizationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(exceptionHandlingFilter, AuthorizationFilter.class)
//authentication 경로 설정
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/login").permitAll()
.requestMatchers("/hello").authenticated()
.anyRequest().permitAll())

//session 설정
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package team7.inplace.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import team7.inplace.security.filter.AuthorizationFilter;
import team7.inplace.security.filter.ExceptionHandlingFilter;
import team7.inplace.security.util.JwtUtil;

@Component
@Configuration
public class SecurityFilterConfig {

@Bean
public AuthorizationFilter authorizationFilter(JwtUtil jwtUtil) {
return new AuthorizationFilter(jwtUtil);
}

@Bean
public ExceptionHandlingFilter exceptionHandlingFilter() {
return new ExceptionHandlingFilter();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
import org.springframework.context.annotation.Configuration;
import team7.inplace.security.handler.CustomSuccessHandler;
import team7.inplace.security.util.JwtUtil;
import team7.inplace.user.application.UserService;

@Configuration
public class SecurityHandlerConfig {

@Bean
public CustomSuccessHandler customSuccessHandler(JwtUtil jwtUtil) {
return new CustomSuccessHandler(jwtUtil);
public CustomSuccessHandler customSuccessHandler(JwtUtil jwtUtil, UserService userService) {
return new CustomSuccessHandler(jwtUtil, userService);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import team7.inplace.security.application.CustomOAuth2UserService;
import team7.inplace.security.persistence.UserRepository;
import team7.inplace.user.application.UserService;

@Configuration
public class SecurityServiceConfig {
Expand All @@ -16,7 +16,7 @@ public DefaultOAuth2UserService defaultOAuth2UserService() {

@Bean
public CustomOAuth2UserService customOAuth2UserService(
DefaultOAuth2UserService defaultOAuth2UserService, UserRepository userRepository) {
return new CustomOAuth2UserService(defaultOAuth2UserService, userRepository);
DefaultOAuth2UserService defaultOAuth2UserService, UserService userService) {
return new CustomOAuth2UserService(defaultOAuth2UserService, userService);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package team7.inplace.security.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import team7.inplace.global.exception.InplaceException;
import team7.inplace.global.exception.code.AuthorizationErrorCode;
import team7.inplace.security.application.dto.CustomOAuth2User;
import team7.inplace.security.util.JwtUtil;

public class AuthorizationFilter extends OncePerRequestFilter {

private final JwtUtil jwtUtil;

public AuthorizationFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
Cookie[] cookies = request.getCookies();
if (Objects.isNull(cookies)) {
filterChain.doFilter(request, response);
return;
}
String token = getTokenCookie(cookies).getValue();
addUserToAuthentication(token);
filterChain.doFilter(request, response);
}

private Cookie getTokenCookie(Cookie[] cookies) throws InplaceException {
Cookie tokenCookie = Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals("Authorization"))
.findFirst()
.orElseThrow(() -> InplaceException.of(AuthorizationErrorCode.TOKEN_IS_EMPTY));
validateToken(tokenCookie);
return tokenCookie;
}

private void addUserToAuthentication(String token) throws InplaceException {
String username = jwtUtil.getUsername(token);
Long id = jwtUtil.getId(token);
CustomOAuth2User customOAuth2User = new CustomOAuth2User(username, id, false);
Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null);
SecurityContextHolder.getContext().setAuthentication(authToken);
}

private void validateToken(Cookie authorizationCookie) throws InplaceException {
validateTokenEmpty(authorizationCookie);
jwtUtil.validateExpired(authorizationCookie.getValue());
}

private void validateTokenEmpty(Cookie authorizationCookie) throws InplaceException {
if (authorizationCookie.getValue() == null) {
throw InplaceException.of(AuthorizationErrorCode.TOKEN_IS_EMPTY);
}
}

}
Loading

0 comments on commit 7738f1d

Please sign in to comment.