-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 티모 9주차 과제 JWT 토큰 방식 회원가입, 로그인 구현 및 마이페이지 개선 #53
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
base: teemo
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,8 @@ | |
| import com.example.umc10th.domain.user.repository.FoodRepository; | ||
| import com.example.umc10th.domain.user.repository.TermRepository; | ||
| import com.example.umc10th.domain.user.repository.UserRepository; | ||
| import com.example.umc10th.global.security.entity.AuthUser; | ||
| import com.example.umc10th.global.security.util.JwtUtil; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.stereotype.Service; | ||
|
|
@@ -29,9 +31,14 @@ public class UserService { | |
| private final FoodRepository foodRepository; | ||
| private final TermRepository termRepository; | ||
| private final PasswordEncoder passwordEncoder; | ||
| private final JwtUtil jwtUtil; | ||
|
|
||
| @Transactional | ||
| public UserResDTO.JoinResult joinUser(UserReqDTO.Join request) { | ||
| if (userRepository.existsByEmail(request.getEmail())) { | ||
| throw new UserException(UserErrorCode.EMAIL_ALREADY_EXISTS); | ||
| } | ||
|
|
||
| User newUser = UserConverter.toUser(request); | ||
| newUser.encodePassword(passwordEncoder.encode(request.getPassword())); | ||
|
|
||
|
|
@@ -53,12 +60,31 @@ public UserResDTO.JoinResult joinUser(UserReqDTO.Join request) { | |
| List<UserTerm> userTermList = UserConverter.toUserTermList(termList); | ||
| userTermList.forEach(userTerm -> userTerm.setMapping(newUser)); | ||
|
|
||
| return UserConverter.toJoinResultDTO(userRepository.save(newUser)); | ||
| User savedUser = userRepository.save(newUser); | ||
| String accessToken = jwtUtil.createAccessToken(new AuthUser(savedUser)); | ||
|
|
||
| return UserConverter.toJoinResultDTO(savedUser, accessToken); | ||
| } | ||
|
|
||
| public UserResDTO.LoginResult login(UserReqDTO.Login request) { | ||
| User user = userRepository.findByEmail(request.getEmail()) | ||
| .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); | ||
|
|
||
| if (!passwordEncoder.matches(request.getPassword(), user.getPassword())) { | ||
| throw new UserException(UserErrorCode.PASSWORD_NOT_MATCH); | ||
| } | ||
|
|
||
| String accessToken = jwtUtil.createAccessToken(new AuthUser(user)); | ||
| return UserConverter.toLoginResultDTO(user, accessToken); | ||
| } | ||
|
|
||
| public UserResDTO.MyPage getMyPage(Long userId) { | ||
| User user = userRepository.findById(userId) | ||
| .orElseThrow(() -> new UserException(UserErrorCode.USER_NOT_FOUND)); | ||
| return UserConverter.toMyPageDTO(user); | ||
| } | ||
|
|
||
| public UserResDTO.MyPage getMyPage(AuthUser member) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 위에 메소드랑 중복 이름인데 같은 역할이면 둘 중 하나 삭제해야할 듯 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요거 이전에 userId 기반으로 잘 해줬는데 왜 AuthUser 받는 메서드 추가했을까 ?? authUser 는 따지자면 인증 계층에서 쓰이는 객체니까, 서비스 계층까지 넘겨주면 책임이 좀 섞이게 보일 수 있을거같아. |
||
| return UserConverter.toMyPageDTO(member.getUser()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,14 @@ | |
| 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.AbstractHttpConfigurer; | ||
| import org.springframework.security.config.http.SessionCreationPolicy; | ||
| import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
| import org.springframework.security.web.SecurityFilterChain; | ||
| import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
| import com.example.umc10th.global.security.filter.JwtAuthFilter; | ||
| import com.example.umc10th.global.security.service.CustomUserDetailsService; | ||
| import com.example.umc10th.global.security.util.JwtUtil; | ||
|
|
||
| @Configuration | ||
| @EnableWebSecurity | ||
|
|
@@ -17,10 +22,13 @@ public class SecurityConfig { | |
|
|
||
| private final CustomAccessDenied customAccessDenied; | ||
| private final CustomEntryPoint customEntryPoint; | ||
| private final JwtUtil jwtUtil; | ||
| private final CustomUserDetailsService customUserDetailsService; | ||
|
|
||
| private final String[] allowUris = { | ||
| "/api/auth/signup", | ||
| // "/h2-console/**", // DB 확인용 콘솔 허용 | ||
| "/api/auth/login", | ||
| "/h2-console/**", | ||
| "/swagger-ui/**", | ||
| "/v3/api-docs/**" | ||
| }; | ||
|
|
@@ -29,28 +37,29 @@ public class SecurityConfig { | |
| public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
| http | ||
| .csrf(AbstractHttpConfigurer::disable) | ||
| .httpBasic(AbstractHttpConfigurer::disable) | ||
| .formLogin(AbstractHttpConfigurer::disable) | ||
| .logout(AbstractHttpConfigurer::disable) | ||
| .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) | ||
|
Comment on lines
+40
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 굿굿 |
||
| .authorizeHttpRequests(auth -> auth | ||
| .requestMatchers(allowUris).permitAll() | ||
| .anyRequest().authenticated() | ||
| ) | ||
| .formLogin(form -> form | ||
| .defaultSuccessUrl("/swagger-ui/index.html", true) | ||
| .permitAll() | ||
| ) | ||
| .logout(logout -> logout | ||
| .logoutUrl("/logout") | ||
| .logoutSuccessUrl("/login?logout") | ||
| .permitAll() | ||
| ) | ||
| .exceptionHandling(exception -> exception | ||
| .accessDeniedHandler(customAccessDenied) | ||
| .authenticationEntryPoint(customEntryPoint) | ||
| ) | ||
| .headers(headers -> headers.frameOptions(options -> options.sameOrigin())); | ||
| .headers(headers -> headers.frameOptions(options -> options.sameOrigin())) | ||
| .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class); | ||
|
|
||
| return http.build(); | ||
| } | ||
|
|
||
| @Bean | ||
| public JwtAuthFilter jwtAuthFilter() { | ||
| return new JwtAuthFilter(jwtUtil, customUserDetailsService); | ||
| } | ||
|
|
||
| /// 남이 만들어준 라이브러리인데 내가 DI해서 쓰고싶을 때.. 빈 등록을 해서 쓴다. @Configuration도 붙여줘야 한다. | ||
| @Bean | ||
| public PasswordEncoder passwordEncoder() { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| package com.example.umc10th.global.security.filter; | ||
|
|
||
| import com.example.umc10th.global.apiPayload.ApiResponse; | ||
| import com.example.umc10th.global.apiPayload.code.BaseErrorCode; | ||
| import com.example.umc10th.global.apiPayload.code.GeneralErrorCode; | ||
| import com.example.umc10th.global.security.service.CustomUserDetailsService; | ||
| import com.example.umc10th.global.security.util.JwtUtil; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import jakarta.servlet.FilterChain; | ||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import lombok.NonNull; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.security.core.context.SecurityContextHolder; | ||
| import org.springframework.security.core.userdetails.UserDetails; | ||
| import org.springframework.web.filter.OncePerRequestFilter; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| @RequiredArgsConstructor | ||
| public class JwtAuthFilter extends OncePerRequestFilter { | ||
|
|
||
| private final JwtUtil jwtUtil; | ||
| private final CustomUserDetailsService customUserDetailsService; | ||
|
|
||
| @Override | ||
| protected void doFilterInternal( | ||
| @NonNull HttpServletRequest request, | ||
| @NonNull HttpServletResponse response, | ||
| @NonNull FilterChain filterChain | ||
| ) throws ServletException, IOException { | ||
|
|
||
| try { | ||
| // 토큰 가져오기 | ||
| String token = request.getHeader("Authorization"); | ||
| // token이 없거나 Bearer가 아니면 넘기기 | ||
| if (token == null || !token.startsWith("Bearer ")) { | ||
| filterChain.doFilter(request, response); | ||
| return; | ||
| } | ||
| // Bearer이면 추출 | ||
| token = token.replace("Bearer ", ""); | ||
| // AccessToken 검증하기: 올바른 토큰이면 | ||
| if (jwtUtil.isValid(token)) { | ||
| // 토큰에서 이메일 추출 | ||
| String email = jwtUtil.getEmail(token); | ||
| // 인증 객체 생성: 이메일로 찾아온 뒤, 인증 객체 생성 | ||
| UserDetails user = customUserDetailsService.loadUserByUsername(email); | ||
| Authentication auth = new UsernamePasswordAuthenticationToken( | ||
| user, | ||
| null, | ||
| user.getAuthorities() | ||
| ); | ||
| // 인증 완료 후 SecurityContextHolder에 넣기 | ||
| SecurityContextHolder.getContext().setAuthentication(auth); | ||
| } | ||
| filterChain.doFilter(request, response); | ||
| } catch (Exception e) { | ||
| ObjectMapper mapper = new ObjectMapper(); | ||
| BaseErrorCode code = GeneralErrorCode.UNAUTHORIZED; | ||
|
|
||
| response.setContentType("application/json;charset=UTF-8"); | ||
| response.setStatus(code.getHttpStatus().value()); | ||
|
|
||
| ApiResponse<Void> errorResponse = ApiResponse.onFailure(code, null); | ||
|
|
||
| mapper.writeValue(response.getOutputStream(), errorResponse); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이렇게 비밀번호 가 틀렸다는 걸 그대로 내려주는건 보안상으로는 좋은 느낌은 아냐..!!
이메일 불일치랑 비밀번호 불일치를 다르게 내려주면, 어떤 이메일들이 가입되어있는지 확인할수가 있겠지?? user enumeration 라는 키워드로 찾아보면 이런 관련된 여러 이슈들이 나오니까 한 번 찾아보면 좋겠다!