-
Notifications
You must be signed in to change notification settings - Fork 13
Feat/week/4 #32
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: 김지윤
Are you sure you want to change the base?
Feat/week/4 #32
Changes from all commits
acbba17
345af32
70b519d
eb5fbb1
9cd5eb6
05ba092
1f907f4
dcf5dc3
a121347
db18d32
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 |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package com.example.devSns.config; | ||
|
|
||
| import com.example.devSns.security.JwtFilter; | ||
| import com.example.devSns.security.JwtTokenProvider; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.security.config.annotation.web.builders.HttpSecurity; | ||
| import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; | ||
| 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; | ||
|
|
||
| @Configuration | ||
| @EnableWebSecurity | ||
| @RequiredArgsConstructor | ||
| public class SecurityConfig { | ||
|
|
||
| private final JwtTokenProvider jwtTokenProvider; | ||
|
|
||
| @Bean | ||
| public PasswordEncoder passwordEncoder() { | ||
| return new BCryptPasswordEncoder(); | ||
| } | ||
|
|
||
| @Bean | ||
| public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { | ||
| http | ||
| // CSRF 설정 Disable | ||
| .csrf((csrf) -> csrf.disable()) | ||
|
|
||
| // 세션 사용 안 함 (STATELESS) | ||
| .sessionManagement((session) -> session | ||
| .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) | ||
|
|
||
| // 요청 권한 설정 | ||
| .authorizeHttpRequests((requests) -> requests | ||
| .requestMatchers("/api/auth/**", "/h2-console/**").permitAll() // 로그인, 회원가입은 누구나 접근 가능 | ||
| .anyRequest().authenticated() // 그 외 모든 요청은 인증 필요 | ||
| ) | ||
|
|
||
| // H2 Console iframe 허용 | ||
| .headers((headers) -> headers.frameOptions(frameOptions -> frameOptions.disable())) | ||
|
|
||
| // JwtFilter 를 UsernamePasswordAuthenticationFilter 앞에 등록 | ||
| .addFilterBefore(new JwtFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); | ||
|
|
||
| return http.build(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.example.devSns.controller; | ||
|
|
||
| import com.example.devSns.dto.MemberLoginRequestDto; | ||
| import com.example.devSns.dto.MemberResponseDto; | ||
| import com.example.devSns.dto.MemberSignUpRequestDto; | ||
| import com.example.devSns.dto.TokenRequestDto; | ||
| import com.example.devSns.dto.TokenDto; | ||
| import com.example.devSns.service.AuthService; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/auth") | ||
| @RequiredArgsConstructor | ||
| public class AuthController { | ||
| private final AuthService authService; | ||
|
|
||
| @PostMapping("/signup") | ||
| public ResponseEntity<MemberResponseDto> signup(@RequestBody MemberSignUpRequestDto requestDto) { | ||
| return ResponseEntity.ok(authService.signup(requestDto)); | ||
| } | ||
|
|
||
| @PostMapping("/login") | ||
| public ResponseEntity<TokenDto> login(@RequestBody MemberLoginRequestDto requestDto) { | ||
| return ResponseEntity.ok(authService.login(requestDto)); | ||
| } | ||
|
|
||
| @PostMapping("/reissue") | ||
| public ResponseEntity<TokenDto> reissue(@RequestBody TokenRequestDto requestDto) { | ||
| return ResponseEntity.ok(authService.reissue(requestDto)); | ||
| } | ||
|
Comment on lines
+32
to
+35
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. 액세스 토큰 재발급로직까지 구현해 주셨네요! 좋습니다 |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package com.example.devSns.domain; | ||
|
|
||
| public enum Authority { | ||
| ROLE_USER, ROLE_ADMIN | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,21 @@ | ||
| package com.example.devSns.domain; | ||
|
|
||
| import jakarta.persistence.*; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| @Entity | ||
| @Getter | ||
| @NoArgsConstructor | ||
| @Table(name = "members") // 'user'는 H2 DB 등에서 예약어일 수 있으므로 'members' 사용 | ||
|
|
||
| @AllArgsConstructor // Builder 패턴 사용 시 필요 | ||
| @Builder // ★ 클래스 레벨에 추가하여 builder() 메서드 자동 생성 ★ | ||
| @Table(name = "members") | ||
|
|
||
| public class Member { | ||
|
|
||
| @Id | ||
|
|
@@ -21,19 +26,25 @@ public class Member { | |
| private String username; // 로그인 ID | ||
|
|
||
| @Column(nullable = false) | ||
| private String password; // 실제로는 해싱(Hashing) 필요 | ||
|
|
||
| private String password; | ||
|
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. 이건 코드 리뷰 사항은 아니긴 한데 해싱과 암호화의 차이에 대해서도 알아두시면 좋을 것 같습니다! 해싱 - 단방향 비밀번호 저장 시 암호화는 키 값 노출 시 복호화가 가능한데, 복호화가 불가능한 해싱 기법을 사용하면 해싱에는 복호화 키 개념이 없기에 상대적으로 안전해서 보통 해싱을 사용하는 것 같아요 |
||
|
|
||
| @Column(nullable = false, unique = true) | ||
| private String nickname; // 사용자가 표시할 이름 | ||
| private String nickname; | ||
|
|
||
| @Enumerated(EnumType.STRING) | ||
| private Authority authority; // 권한 | ||
|
|
||
| // Member가 삭제되면, 관련 Post도 모두 삭제 (Cascade) | ||
| @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE, orphanRemoval = true) | ||
| @Builder.Default // Builder 사용 시 초기화 값을 유지하기 위해 필요 | ||
| private List<Post> posts = new ArrayList<>(); | ||
|
|
||
| @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE, orphanRemoval = true) | ||
| @Builder.Default | ||
| private List<Comment> comments = new ArrayList<>(); | ||
|
|
||
| @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE, orphanRemoval = true) | ||
| @Builder.Default | ||
| private List<PostLike> likes = new ArrayList<>(); | ||
|
|
||
| public Member(String username, String password, String nickname) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package com.example.devSns.domain; | ||
|
|
||
| import jakarta.persistence.Column; | ||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Table; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @Table(name = "refresh_token") | ||
| @Entity | ||
| public class RefreshToken { | ||
|
|
||
| @Id | ||
| @Column(name = "rt_key") | ||
| private String key; // Member ID (username) | ||
|
|
||
| @Column(name = "rt_value") | ||
| private String value; // Refresh Token 값 | ||
|
|
||
| @Builder | ||
| public RefreshToken(String key, String value) { | ||
| this.key = key; | ||
| this.value = value; | ||
| } | ||
|
|
||
| public RefreshToken updateValue(String token) { | ||
| this.value = token; | ||
| return this; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.example.devSns.dto; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor | ||
| public class MemberLoginRequestDto { | ||
| private String username; | ||
| private String password; | ||
|
Comment on lines
+8
to
+13
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. DTO의 경우는 Record를 사용하시는 걸 추천드립니다! 그러면 |
||
|
|
||
| // 아이디/비번을 인증 토큰 형태로 변환하는 메서드 | ||
| public UsernamePasswordAuthenticationToken toAuthentication() { | ||
| return new UsernamePasswordAuthenticationToken(username, password); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,25 @@ | ||
| package com.example.devSns.dto; | ||
|
|
||
| import com.example.devSns.domain.Member; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @AllArgsConstructor | ||
| @NoArgsConstructor | ||
| @Builder | ||
| public class MemberResponseDto { | ||
| private Long id; | ||
| private String username; | ||
| private String nickname; | ||
|
Comment on lines
8
to
15
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. username도 담아주는 게 활용하기 좋겠네요! 그런데 id의 경우 나중에 프론트와 협업할 때는 응답에 필요한 경우가 종종 생깁니다. 예를 들어 목록 조회 -> 상세 페이지 흐름에서 상세 페이지 요청을 위해 대상 memberId를 프론트 단에서 알아야 하는 상황 등이 있겠네요. 비즈니스 로직 흐름 고려하셔서 진행하시면 될 것 같습니다 |
||
|
|
||
| public MemberResponseDto(Member member) { | ||
| this.id = member.getId(); | ||
| this.nickname = member.getNickname(); | ||
| // Member 엔티티를 받아서 DTO로 변환해주는 정적 메서드 | ||
| public static MemberResponseDto of(Member member) { | ||
| return MemberResponseDto.builder() | ||
| .username(member.getUsername()) | ||
| .nickname(member.getNickname()) | ||
| .build(); | ||
|
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,29 @@ | ||
| package com.example.devSns.dto; | ||
|
|
||
| import com.example.devSns.domain.Authority; | ||
| import com.example.devSns.domain.Member; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.Setter; | ||
| import org.springframework.security.crypto.password.PasswordEncoder; | ||
|
|
||
| @Getter | ||
| @Setter | ||
|
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. DTO의 경우 말 그대로 데이터 전송용 객체다 보니 처음 만들 때 완전하게 만들고 setter를 지양하는 게 좋을 것 같습니다! |
||
| @NoArgsConstructor | ||
|
|
||
| public class MemberSignUpRequestDto { | ||
| private String username; | ||
| private String password; | ||
| private String nickname; | ||
|
|
||
| // DTO 정보를 바탕으로 Member 엔티티를 만드는 메서드 | ||
| public Member toMember(PasswordEncoder passwordEncoder) { | ||
| return Member.builder() | ||
| .username(username) | ||
| .password(passwordEncoder.encode(password)) | ||
| .nickname(nickname) | ||
| .authority(Authority.ROLE_USER) | ||
| .build(); | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.example.devSns.dto; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Builder; | ||
| import lombok.Data; | ||
|
|
||
| @Data | ||
|
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.
|
||
| @Builder | ||
| @AllArgsConstructor | ||
| public class TokenDto { | ||
| private String grantType; | ||
| private String accessToken; | ||
| private String refreshToken; | ||
| private Long accessTokenExpiresIn; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.example.devSns.dto; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| public class TokenRequestDto { | ||
| private String accessToken; | ||
| private String refreshToken; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.example.devSns.repository; | ||
|
|
||
| import com.example.devSns.domain.RefreshToken; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
| import org.springframework.stereotype.Repository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| @Repository | ||
| public interface RefreshTokenRepository extends JpaRepository<RefreshToken, String> { | ||
| Optional<RefreshToken> findByKey(String key); | ||
| } |
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.
Spring Security를 사용하셨네요! 모듈이 복잡한 편이라서 나중에 인증/인가 전체 흐름 관련해서 파트별로 기능을 정리해두면 좋을 것 같아요~ 해당 부분 템플릿화해두면 초기 프로젝트 진행 시 인증/인가 부분을 빠르게 처리해서 넘겨줄 수 있어서 좋습니다