Skip to content

Commit 6f0708a

Browse files
authored
Merge pull request #37 from FixLog/feature/#14-login-cw
2 parents 07fdff5 + 5fd66f1 commit 6f0708a

16 files changed

Lines changed: 463 additions & 142 deletions

build.gradle

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,14 @@ repositories {
2525

2626
dependencies {
2727
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
28-
// implementation 'org.springframework.boot:spring-boot-starter-security'
28+
implementation 'org.springframework.boot:spring-boot-starter-security'
2929
implementation 'org.springframework.boot:spring-boot-starter-web'
30-
compileOnly 'org.projectlombok:lombok'
30+
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
31+
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
32+
runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.11.5')
3133
runtimeOnly 'com.h2database:h2'
3234
runtimeOnly 'com.mysql:mysql-connector-j'
35+
compileOnly 'org.projectlombok:lombok'
3336
annotationProcessor 'org.projectlombok:lombok'
3437
testImplementation 'org.springframework.boot:spring-boot-starter-test'
3538
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.example.FixLog.config;
2+
3+
import com.example.FixLog.domain.member.Member;
4+
import com.example.FixLog.exception.CustomException;
5+
import com.example.FixLog.repository.MemberRepository;
6+
import com.example.FixLog.util.JwtUtil;
7+
import java.io.IOException;
8+
import jakarta.servlet.FilterChain;
9+
import jakarta.servlet.ServletException;
10+
import jakarta.servlet.http.HttpServletRequest;
11+
import jakarta.servlet.http.HttpServletResponse;
12+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
13+
import org.springframework.security.core.Authentication;
14+
import org.springframework.security.core.context.SecurityContextHolder;
15+
import org.springframework.util.StringUtils;
16+
import org.springframework.web.filter.OncePerRequestFilter;
17+
import com.example.FixLog.exception.ErrorCode;
18+
19+
public class JwtAuthenticationFilter extends OncePerRequestFilter {
20+
21+
private final JwtUtil jwtUtil;
22+
private final MemberRepository memberRepository;
23+
24+
public JwtAuthenticationFilter(JwtUtil jwtUtil, MemberRepository memberRepository) {
25+
this.jwtUtil = jwtUtil;
26+
this.memberRepository = memberRepository;
27+
}
28+
29+
@Override
30+
protected void doFilterInternal(HttpServletRequest request,
31+
HttpServletResponse response,
32+
FilterChain filterChain)
33+
throws ServletException, IOException {
34+
35+
String token = resolveToken(request);
36+
37+
if (token != null && jwtUtil.isTokenValid(token)) {
38+
String email = jwtUtil.getEmailFromToken(token);
39+
Member member = memberRepository.findByEmail(email)
40+
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));
41+
42+
Authentication auth = new UsernamePasswordAuthenticationToken(member, null, member.getAuthorities());
43+
SecurityContextHolder.getContext().setAuthentication(auth);
44+
}
45+
46+
filterChain.doFilter(request, response);
47+
}
48+
49+
private String resolveToken(HttpServletRequest request) {
50+
String bearerToken = request.getHeader("Authorization");
51+
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
52+
return bearerToken.substring(7);
53+
}
54+
return null;
55+
}
56+
}
Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,58 @@
1-
//package com.example.FixLog.config;
2-
//
3-
//import org.springframework.context.annotation.Bean;
4-
//import org.springframework.context.annotation.Configuration;
5-
//import org.springframework.http.HttpMethod;
6-
//import org.springframework.security.config.annotation.web.builders.HttpSecurity;
7-
//import org.springframework.security.web.SecurityFilterChain;
8-
//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
9-
//import org.springframework.security.crypto.password.PasswordEncoder;
10-
//
11-
//@Configuration
12-
//public class SecurityConfig {
13-
//
14-
// @Bean
15-
// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
16-
// http
17-
// .csrf(csrf -> csrf.disable())
18-
// .authorizeHttpRequests(auth -> auth
19-
// .requestMatchers(HttpMethod.POST, "/api/members/signup").permitAll()
20-
// .requestMatchers(HttpMethod.GET, "/api/members/check-email").permitAll()
21-
// .requestMatchers(HttpMethod.GET, "/api/members/check-nickname").permitAll()
22-
// .anyRequest().authenticated()
23-
// );
24-
// return http.build();
25-
// }
26-
//
27-
// @Bean
28-
// public PasswordEncoder passwordEncoder() {
29-
// return new BCryptPasswordEncoder();
30-
// }
31-
//}
1+
package com.example.FixLog.config;
2+
3+
import com.example.FixLog.repository.MemberRepository;
4+
import com.example.FixLog.util.JwtUtil;
5+
import jakarta.servlet.Filter;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.http.HttpMethod;
10+
import org.springframework.security.authentication.AuthenticationManager;
11+
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
12+
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
13+
import org.springframework.security.web.SecurityFilterChain;
14+
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
15+
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
16+
import org.springframework.security.crypto.password.PasswordEncoder;
17+
18+
@Configuration
19+
@RequiredArgsConstructor
20+
public class SecurityConfig {
21+
22+
private final JwtUtil jwtUtil;
23+
private final MemberRepository memberRepository;
24+
25+
@Bean
26+
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
27+
http
28+
.csrf(csrf -> csrf.disable())
29+
.authorizeHttpRequests(auth -> auth
30+
.requestMatchers(HttpMethod.POST, "/api/members/signup").permitAll()
31+
.requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
32+
.requestMatchers(HttpMethod.GET, "/api/members/check-email").permitAll()
33+
.requestMatchers(HttpMethod.GET, "/api/members/check-nickname").permitAll()
34+
.requestMatchers(HttpMethod.GET, "/h2-console/**").permitAll()
35+
.anyRequest().authenticated()
36+
)
37+
.headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔용
38+
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
39+
40+
return http.build();
41+
}
42+
43+
@Bean
44+
public Filter jwtAuthenticationFilter() {
45+
return new JwtAuthenticationFilter(jwtUtil, memberRepository);
46+
}
47+
48+
@Bean
49+
public PasswordEncoder passwordEncoder() {
50+
return new BCryptPasswordEncoder();
51+
}
52+
53+
// 인증 매니저 (선택: 로그인 시 AuthenticationManager 사용 가능)
54+
@Bean
55+
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
56+
return config.getAuthenticationManager();
57+
}
58+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.example.FixLog.controller;
2+
3+
import com.example.FixLog.dto.Response;
4+
import com.example.FixLog.dto.member.LoginRequestDto;
5+
import com.example.FixLog.dto.member.LoginResponseDto;
6+
import com.example.FixLog.service.AuthService;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.*;
10+
11+
@RestController
12+
@RequestMapping("/api/auth")
13+
@RequiredArgsConstructor
14+
public class AuthController {
15+
16+
private final AuthService authService;
17+
18+
@PostMapping("/login")
19+
public ResponseEntity<Response<LoginResponseDto>> login(@RequestBody LoginRequestDto requestDto) {
20+
LoginResponseDto result = authService.login(requestDto);
21+
return ResponseEntity.ok(Response.success("로그인 성공", result));
22+
}
23+
}
Lines changed: 53 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,53 @@
1-
//package com.example.FixLog.controller;
2-
//
3-
//
4-
//import com.example.FixLog.dto.Response;
5-
//import com.example.FixLog.dto.member.SignupRequestDto;
6-
//import com.example.FixLog.dto.member.DuplicateCheckResponseDto;
7-
//import com.example.FixLog.service.MemberService;
8-
//import lombok.RequiredArgsConstructor;
9-
//import org.springframework.http.ResponseEntity;
10-
//import org.springframework.web.bind.annotation.*;
11-
//
12-
//@RestController
13-
//@RequestMapping("/api/members")
14-
//@RequiredArgsConstructor
15-
//public class MemberController {
16-
//
17-
// private final MemberService memberService;
18-
//
19-
// @PostMapping("/signup")
20-
// public ResponseEntity<Response<String>> signup(@RequestBody SignupRequestDto request) {
21-
// memberService.signup(request);
22-
// return ResponseEntity.ok(Response.success("회원가입이 완료되었습니다.", null));
23-
// }
24-
//
25-
// @GetMapping("/check-email")
26-
// public ResponseEntity<Response<DuplicateCheckResponseDto>> checkEmail(@RequestParam String email) {
27-
// boolean exists = memberService.isEmailDuplicated(email);
28-
// String msg = exists ? "이미 사용 중인 이메일입니다." : "사용 가능한 이메일입니다.";
29-
// return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists)));
30-
// }
31-
//
32-
// @GetMapping("/check-nickname")
33-
// public ResponseEntity<Response<DuplicateCheckResponseDto>> checkNickname(@RequestParam String nickname) {
34-
// boolean exists = memberService.isNicknameDuplicated(nickname);
35-
// String msg = exists ? "이미 사용 중인 닉네임입니다." : "사용 가능한 닉네임입니다.";
36-
// return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists)));
37-
// }
38-
//}
1+
package com.example.FixLog.controller;
2+
3+
4+
import com.example.FixLog.domain.member.Member;
5+
import com.example.FixLog.dto.Response;
6+
import com.example.FixLog.dto.member.MemberInfoResponseDto;
7+
import com.example.FixLog.dto.member.SignupRequestDto;
8+
import com.example.FixLog.dto.member.DuplicateCheckResponseDto;
9+
import com.example.FixLog.service.MemberService;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
13+
import org.springframework.web.bind.annotation.*;
14+
15+
@RestController
16+
@RequestMapping("/api/members")
17+
@RequiredArgsConstructor
18+
public class MemberController {
19+
20+
private final MemberService memberService;
21+
22+
@PostMapping("/signup")
23+
public ResponseEntity<Response<String>> signup(@RequestBody SignupRequestDto request) {
24+
memberService.signup(request);
25+
return ResponseEntity.ok(Response.success("회원가입 성공", null));
26+
}
27+
28+
@GetMapping("/check-email")
29+
public ResponseEntity<Response<DuplicateCheckResponseDto>> checkEmail(@RequestParam String email) {
30+
boolean exists = memberService.isEmailDuplicated(email);
31+
String msg = exists ? "이미 사용 중인 이메일입니다." : "사용 가능한 이메일입니다.";
32+
return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists)));
33+
}
34+
35+
@GetMapping("/check-nickname")
36+
public ResponseEntity<Response<DuplicateCheckResponseDto>> checkNickname(@RequestParam String nickname) {
37+
boolean exists = memberService.isNicknameDuplicated(nickname);
38+
String msg = exists ? "이미 사용 중인 닉네임입니다." : "사용 가능한 닉네임입니다.";
39+
return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists)));
40+
}
41+
42+
@GetMapping("/me")
43+
public ResponseEntity<Response<MemberInfoResponseDto>> getMyInfo(@AuthenticationPrincipal Member member) {
44+
MemberInfoResponseDto responseDto = new MemberInfoResponseDto(
45+
member.getEmail(),
46+
member.getNickname(),
47+
member.getProfileImageUrl(),
48+
member.getBio(),
49+
member.getSocialType()
50+
);
51+
return ResponseEntity.ok(Response.success("회원 정보 조회 성공", responseDto));
52+
}
53+
}

src/main/java/com/example/FixLog/domain/member/Member.java

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,20 @@
99
import org.springframework.data.annotation.CreatedDate;
1010
import org.springframework.data.annotation.LastModifiedDate;
1111
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
12+
import org.springframework.security.core.GrantedAuthority;
13+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
14+
import org.springframework.security.core.userdetails.UserDetails;
1215

1316
import java.time.LocalDateTime;
1417
import java.util.ArrayList;
1518
import java.util.List;
19+
import java.util.Collection;
1620

1721
@Entity
1822
@Getter
1923
@NoArgsConstructor(access = AccessLevel.PROTECTED)
2024
@EntityListeners(AuditingEntityListener.class)
21-
public class Member {
25+
public class Member implements UserDetails {
2226

2327
@Id
2428
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -76,6 +80,41 @@ public static Member of(String email, String password, String nickname, SocialTy
7680
member.nickname = nickname;
7781
member.socialType = socialType;
7882
member.isDeleted = false;
83+
member.profileImageUrl = "https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"; // 기본 프로필 이미지(임시)
7984
return member;
8085
}
86+
87+
public void setProfileImageUrl(String profileImageUrl) {
88+
this.profileImageUrl = profileImageUrl;
89+
}
90+
91+
@Override
92+
public Collection<? extends GrantedAuthority> getAuthorities() {
93+
return List.of(new SimpleGrantedAuthority("ROLE_USER")); // 기본 권한
94+
}
95+
96+
@Override
97+
public String getUsername() {
98+
return this.email; // 로그인 시 사용할 사용자 식별자
99+
}
100+
101+
@Override
102+
public boolean isAccountNonExpired() {
103+
return true; // 계정 만료 여부 (true = 사용 가능)
104+
}
105+
106+
@Override
107+
public boolean isAccountNonLocked() {
108+
return true; // 계정 잠금 여부 (true = 잠금 아님)
109+
}
110+
111+
@Override
112+
public boolean isCredentialsNonExpired() {
113+
return true; // 비밀번호 만료 여부
114+
}
115+
116+
@Override
117+
public boolean isEnabled() {
118+
return !this.isDeleted; // 탈퇴 여부 기반 활성 상태
119+
}
81120
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.example.FixLog.dto.member;
2+
3+
import lombok.Getter;
4+
5+
@Getter
6+
public class LoginRequestDto {
7+
private String email;
8+
private String password;
9+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.example.FixLog.dto.member;
2+
3+
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
7+
@JsonInclude(JsonInclude.Include.NON_NULL)
8+
@Getter
9+
@AllArgsConstructor
10+
public class LoginResponseDto {
11+
private Long userId;
12+
private String accessToken;
13+
private String nickname;
14+
private String profileImageUrl;
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.example.FixLog.dto.member;
2+
3+
import com.example.FixLog.domain.member.SocialType;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@AllArgsConstructor
9+
public class MemberInfoResponseDto {
10+
private String email;
11+
private String nickname;
12+
private String profileImageUrl;
13+
private String bio;
14+
private SocialType socialType;
15+
}

src/main/java/com/example/FixLog/exception/ErrorCode.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public enum ErrorCode {
2121
FOLDER_NOT_FOUND(HttpStatus.NOT_FOUND, "폴더를 찾을 수 없습니다."),
2222
ACCESS_DENIED(HttpStatus.FORBIDDEN, "권한이 없습니다."),
2323
TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "없는 태그 번호입니다."),
24-
SORT_NOT_EXIST(HttpStatus.BAD_REQUEST, "사용할 수 없는 정렬입니다.");
24+
SORT_NOT_EXIST(HttpStatus.BAD_REQUEST, "사용할 수 없는 정렬입니다."),
25+
INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."),
26+
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 사용자입니다.");
2527

2628
private final HttpStatus status;
2729
private final String message;

0 commit comments

Comments
 (0)