diff --git a/server/src/main/java/com/talkka/server/config/SecurityConfig.java b/server/src/main/java/com/talkka/server/config/SecurityConfig.java index 561bcca8..81fd89de 100644 --- a/server/src/main/java/com/talkka/server/config/SecurityConfig.java +++ b/server/src/main/java/com/talkka/server/config/SecurityConfig.java @@ -5,9 +5,9 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; -import com.talkka.server.oauth.OAuth2LoginFailureHandler; -import com.talkka.server.oauth.OAuth2LoginSuccessHandler; +import com.talkka.server.oauth.filter.UnregisteredUserFilter; import com.talkka.server.oauth.service.CustomOAuth2Service; import lombok.RequiredArgsConstructor; @@ -17,8 +17,6 @@ public class SecurityConfig { private final CustomOAuth2Service customOAuth2Service; - private final OAuth2LoginSuccessHandler oAuth2LoginSuccessHandler; - private final OAuth2LoginFailureHandler oAuth2LoginFailureHandler; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -26,12 +24,18 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .csrf(AbstractHttpConfigurer::disable) .cors(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authorize -> authorize - .requestMatchers("/auth/**").permitAll() - .anyRequest().authenticated()) + .requestMatchers("/auth/**", "/login/**").permitAll() + .anyRequest().authenticated() + ) + .addFilterAfter(new UnregisteredUserFilter(), BasicAuthenticationFilter.class) .oauth2Login(oauth -> oauth .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2Service)) - .successHandler(oAuth2LoginSuccessHandler) - .failureHandler(oAuth2LoginFailureHandler) + .defaultSuccessUrl("/") + ) + .exceptionHandling(exceptionHandling -> exceptionHandling + .authenticationEntryPoint((request, response, authException) -> { + response.sendRedirect("/auth/login"); + }) ); return http.build(); } diff --git a/server/src/main/java/com/talkka/server/oauth/OAuth2LoginFailureHandler.java b/server/src/main/java/com/talkka/server/oauth/OAuth2LoginFailureHandler.java deleted file mode 100644 index 5951e25b..00000000 --- a/server/src/main/java/com/talkka/server/oauth/OAuth2LoginFailureHandler.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.talkka.server.oauth; - -import java.io.IOException; - -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.stereotype.Component; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; - -@Component -public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { - - @Override - public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, - AuthenticationException exception) throws IOException, ServletException { - - request.getSession().invalidate(); - String defaultFailureUrl = "/auth/login"; - setDefaultFailureUrl(defaultFailureUrl); - super.onAuthenticationFailure(request, response, exception); - } -} diff --git a/server/src/main/java/com/talkka/server/oauth/OAuth2LoginSuccessHandler.java b/server/src/main/java/com/talkka/server/oauth/OAuth2LoginSuccessHandler.java deleted file mode 100644 index 333fb68a..00000000 --- a/server/src/main/java/com/talkka/server/oauth/OAuth2LoginSuccessHandler.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.talkka.server.oauth; - -import java.io.IOException; - -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; -import org.springframework.stereotype.Component; - -import com.talkka.server.oauth.domain.AuthUserDto; -import com.talkka.server.oauth.domain.CustomUserPrincipal; -import com.talkka.server.user.service.UserService; - -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.RequiredArgsConstructor; - -@Component -@RequiredArgsConstructor -public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { - - private final UserService userService; - - @Override - public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, - Authentication authentication) throws IOException, ServletException { - AuthUserDto userDto = ((CustomUserPrincipal)authentication.getPrincipal()).getUser(); - if (!userDto.getIsRegistered()) { - getRedirectStrategy().sendRedirect(request, response, "/auth/signUp"); - } - } -} diff --git a/server/src/main/java/com/talkka/server/oauth/controller/AuthController.java b/server/src/main/java/com/talkka/server/oauth/controller/AuthController.java index 60c6b6ff..f8a210ce 100644 --- a/server/src/main/java/com/talkka/server/oauth/controller/AuthController.java +++ b/server/src/main/java/com/talkka/server/oauth/controller/AuthController.java @@ -8,8 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; -import com.talkka.server.oauth.domain.AuthUserDto; -import com.talkka.server.oauth.domain.CustomUserPrincipal; +import com.talkka.server.oauth.domain.OAuth2UserInfo; import com.talkka.server.user.dto.UserCreateDto; import com.talkka.server.user.dto.UserDto; import com.talkka.server.user.service.UserService; @@ -30,32 +29,30 @@ public String loginForm() { } @GetMapping("/signUp") - public String signUpForm(Model model, @AuthenticationPrincipal CustomUserPrincipal principal) { - AuthUserDto userDto = principal.getUser(); - model.addAttribute("user", userDto); + public String signUpForm(Model model, @AuthenticationPrincipal OAuth2UserInfo principal) { + model.addAttribute("name", principal.getName()); + model.addAttribute("email", principal.getEmail()); return "signUpForm"; } - @SuppressWarnings("checkstyle:WhitespaceAround") @PostMapping("/signUp") public String signUp(@RequestParam("nickname") String nickname, Model model, - @AuthenticationPrincipal CustomUserPrincipal principal, + @AuthenticationPrincipal OAuth2UserInfo principal, HttpServletRequest request) { if (userService.isDuplicatedNickname(nickname)) { return "signUpForm"; } - AuthUserDto authUserDto = principal.getUser(); UserCreateDto userCreateDto = UserCreateDto.builder() - .name(authUserDto.getName()) - .email(authUserDto.getEmail()) - .oauthProvider(authUserDto.getProvider()) + .name(principal.getName()) + .email(principal.getEmail()) + .oauthProvider(principal.getProvider()) .nickname(nickname) - .accessToken(authUserDto.getAccessToken()) + .accessToken(principal.getAccessToken()) .build(); UserDto user = userService.createUser(userCreateDto); request.getSession().invalidate(); - return "redirect:/"; + return "redirect:/oauth2/authorization/naver"; } @GetMapping("/login/naver") diff --git a/server/src/main/java/com/talkka/server/oauth/controller/BaseController.java b/server/src/main/java/com/talkka/server/oauth/controller/BaseController.java new file mode 100644 index 00000000..c5da0243 --- /dev/null +++ b/server/src/main/java/com/talkka/server/oauth/controller/BaseController.java @@ -0,0 +1,23 @@ +package com.talkka.server.oauth.controller; + +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; + +import com.talkka.server.oauth.domain.OAuth2UserInfo; + +// 인증 테스트를 위한 임시 컨트롤러 +@Controller +public class BaseController { + @GetMapping("/") + public String authIndex(Model model, @AuthenticationPrincipal OAuth2UserInfo userInfo) { + model.addAttribute("name", userInfo.getName()); + model.addAttribute("email", userInfo.getEmail()); + model.addAttribute("nickname", userInfo.getNickName()); + model.addAttribute("oauth2Id", userInfo.getOAuth2Id()); + model.addAttribute("provider", userInfo.getProvider()); + model.addAttribute("accessToken", userInfo.getAccessToken()); + return "index"; + } +} diff --git a/server/src/main/java/com/talkka/server/oauth/domain/AuthUserDto.java b/server/src/main/java/com/talkka/server/oauth/domain/AuthUserDto.java deleted file mode 100644 index 1e06e44a..00000000 --- a/server/src/main/java/com/talkka/server/oauth/domain/AuthUserDto.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.talkka.server.oauth.domain; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@Builder -public class AuthUserDto { - private String name; - private String email; - private String accessToken; - private String provider; - private Boolean isRegistered; -} diff --git a/server/src/main/java/com/talkka/server/oauth/domain/CustomUserPrincipal.java b/server/src/main/java/com/talkka/server/oauth/domain/CustomUserPrincipal.java deleted file mode 100644 index 22c1c32e..00000000 --- a/server/src/main/java/com/talkka/server/oauth/domain/CustomUserPrincipal.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.talkka.server.oauth.domain; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; - -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.oauth2.core.user.OAuth2User; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public class CustomUserPrincipal implements OAuth2User { - private final AuthUserDto user; - private final Map attributes; - - @Override - public Map getAttributes() { - return attributes; - } - - @Override - public Collection getAuthorities() { - return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); - } - - @Override - public String getName() { - return user.getName(); - } -} diff --git a/server/src/main/java/com/talkka/server/oauth/domain/NaverOAuth2User.java b/server/src/main/java/com/talkka/server/oauth/domain/NaverOAuth2User.java new file mode 100644 index 00000000..5beb43e4 --- /dev/null +++ b/server/src/main/java/com/talkka/server/oauth/domain/NaverOAuth2User.java @@ -0,0 +1,23 @@ +package com.talkka.server.oauth.domain; + +import java.util.Collection; +import java.util.Map; + +import org.springframework.security.core.GrantedAuthority; + +public class NaverOAuth2User extends OAuth2UserInfo { + + public NaverOAuth2User(Map attributes) { + super((Map)attributes.get("response")); + } + + public NaverOAuth2User(Map attributes, Collection authorities) { + super((Map)attributes, authorities); + } + + @Override + public String getProvider() { + return "NAVER"; + } + +} diff --git a/server/src/main/java/com/talkka/server/oauth/domain/OAuth2UserInfo.java b/server/src/main/java/com/talkka/server/oauth/domain/OAuth2UserInfo.java new file mode 100644 index 00000000..7832f1e6 --- /dev/null +++ b/server/src/main/java/com/talkka/server/oauth/domain/OAuth2UserInfo.java @@ -0,0 +1,60 @@ +package com.talkka.server.oauth.domain; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.core.user.OAuth2User; + +public abstract class OAuth2UserInfo implements OAuth2User { + + protected final Map attributes; + private final Collection authorities; + + public OAuth2UserInfo(Map attributes, Collection authorities) { + this.attributes = attributes; + this.authorities = authorities; + } + + // OAuth2 인증까지만 통과하면 UNREGISTERED 권한 부여 + public OAuth2UserInfo(Map attributes) { + this.attributes = attributes; + this.authorities = List.of(new SimpleGrantedAuthority("UNREGISTERED")); + } + + @Override + public Map getAttributes() { + return attributes; + } + + @Override + public Collection getAuthorities() { + return authorities; + } + + @Override + public String getName() { + return (String)attributes.get("name"); + } + + public String getOAuth2Id() { + return (String)attributes.get("id"); + } + + public String getEmail() { + return (String)attributes.get("email"); + } + + public String getAccessToken() { + return (String)attributes.get("accessToken"); + } + + public String getNickName() { + return (String)attributes.get("nickname"); + } + + // oauth provider 는 각 제공자 별로 구현 + public abstract String getProvider(); +} diff --git a/server/src/main/java/com/talkka/server/oauth/filter/UnregisteredUserFilter.java b/server/src/main/java/com/talkka/server/oauth/filter/UnregisteredUserFilter.java new file mode 100644 index 00000000..3cbda63e --- /dev/null +++ b/server/src/main/java/com/talkka/server/oauth/filter/UnregisteredUserFilter.java @@ -0,0 +1,51 @@ +package com.talkka.server.oauth.filter; + +import java.io.IOException; +import java.util.Collection; + +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; + +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class UnregisteredUserFilter implements Filter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest)request; + HttpServletResponse httpResponse = (HttpServletResponse)response; + + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + if (authentication != null) { + Collection authorities = authentication.getAuthorities(); + for (GrantedAuthority authority : authorities) { + if (authority.getAuthority().equals("UNREGISTERED") + && !httpRequest.getRequestURI().equals("/auth/signUp")) { + httpResponse.sendRedirect("/auth/signUp"); + return; + } + } + } + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } + +} diff --git a/server/src/main/java/com/talkka/server/oauth/service/CustomOAuth2Service.java b/server/src/main/java/com/talkka/server/oauth/service/CustomOAuth2Service.java index f2fda553..5e563731 100644 --- a/server/src/main/java/com/talkka/server/oauth/service/CustomOAuth2Service.java +++ b/server/src/main/java/com/talkka/server/oauth/service/CustomOAuth2Service.java @@ -1,16 +1,19 @@ package com.talkka.server.oauth.service; -import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Optional; +import org.springframework.security.core.authority.SimpleGrantedAuthority; 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 com.talkka.server.oauth.domain.AuthUserDto; -import com.talkka.server.oauth.domain.CustomUserPrincipal; +import com.talkka.server.oauth.domain.NaverOAuth2User; +import com.talkka.server.oauth.domain.OAuth2UserInfo; +import com.talkka.server.user.dao.UserEntity; import com.talkka.server.user.dao.UserRepository; import lombok.RequiredArgsConstructor; @@ -21,26 +24,23 @@ public class CustomOAuth2Service extends DefaultOAuth2UserService { private final UserRepository userRepository; - @SuppressWarnings("checkstyle:RegexpSingleline") @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { - OAuth2User oAuth2User = super.loadUser(userRequest); - Map tmpMap = oAuth2User.getAttribute("response"); - assert tmpMap != null; - String email = (String)tmpMap.get("email"); - String name = (String)tmpMap.get("name"); + + // Spring Security가 auth provider(naver, kakao, google..)에게 인증 정보를 받아옴 + // 이 시점에선 UNREGISTERED 권한 가지고 있음 + OAuth2UserInfo oAuth2User = new NaverOAuth2User(super.loadUser(userRequest).getAttributes()); String accessToken = userRequest.getAccessToken().getTokenValue(); - String provider = userRequest.getClientRegistration().getClientName(); - boolean isRegistered = userRepository.existsByEmail(email); - - AuthUserDto userDto = AuthUserDto.builder() - .name(name) - .email(email) - .accessToken(accessToken) - .provider(provider) - .isRegistered(isRegistered) - .build(); - - return new CustomUserPrincipal(userDto, new HashMap<>()); + oAuth2User.getAttributes().put("accessToken", accessToken); + // 만약 DB에 회원정보가 있으면 ROLE_USER 권한 새로 부여 + Optional optionalUser = userRepository.findByEmail(oAuth2User.getEmail()); + if (optionalUser.isPresent()) { + UserEntity user = optionalUser.get(); + Map attributes = oAuth2User.getAttributes(); + attributes.put("nickname", user.getNickname()); + return new NaverOAuth2User(attributes, + List.of(new SimpleGrantedAuthority("ROLE_USER"))); + } + return oAuth2User; } } diff --git a/server/src/main/java/com/talkka/server/user/dao/UserRepository.java b/server/src/main/java/com/talkka/server/user/dao/UserRepository.java index f1515865..dd31ba04 100644 --- a/server/src/main/java/com/talkka/server/user/dao/UserRepository.java +++ b/server/src/main/java/com/talkka/server/user/dao/UserRepository.java @@ -1,5 +1,7 @@ package com.talkka.server.user.dao; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -7,5 +9,5 @@ public interface UserRepository extends JpaRepository { boolean existsByNickname(String nickname); - boolean existsByEmail(String email); + Optional findByEmail(String email); } diff --git a/server/src/main/resources/templates/index.html b/server/src/main/resources/templates/index.html new file mode 100644 index 00000000..f178ab8c --- /dev/null +++ b/server/src/main/resources/templates/index.html @@ -0,0 +1,18 @@ + + + + + + + + +

이건 홈이에요

+

현재 로그인 된 유저

+

이름:

+

이메일:

+

닉네임:

+

OAuth2 아이디:

+

제공자:

+

엑세스토큰:

+ + diff --git a/server/src/main/resources/templates/loginForm.html b/server/src/main/resources/templates/loginForm.html index 196c1c8d..13b88807 100644 --- a/server/src/main/resources/templates/loginForm.html +++ b/server/src/main/resources/templates/loginForm.html @@ -1,3 +1,4 @@ + diff --git a/server/src/main/resources/templates/signUpForm.html b/server/src/main/resources/templates/signUpForm.html index 81eff877..53513376 100644 --- a/server/src/main/resources/templates/signUpForm.html +++ b/server/src/main/resources/templates/signUpForm.html @@ -1,43 +1,20 @@ + 회원가입 페이지 -

회원가입 페이지 입니다

-
+
- +
- +