Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly('io.jsonwebtoken:jjwt-jackson:0.11.5')
runtimeOnly 'com.h2database:h2'
runtimeOnly 'com.mysql:mysql-connector-j'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.example.FixLog.config;

import com.example.FixLog.domain.member.Member;
import com.example.FixLog.exception.CustomException;
import com.example.FixLog.repository.MemberRepository;
import com.example.FixLog.util.JwtUtil;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.example.FixLog.exception.ErrorCode;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtUtil jwtUtil;
private final MemberRepository memberRepository;

public JwtAuthenticationFilter(JwtUtil jwtUtil, MemberRepository memberRepository) {
this.jwtUtil = jwtUtil;
this.memberRepository = memberRepository;
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {

String token = resolveToken(request);

if (token != null && jwtUtil.isTokenValid(token)) {
String email = jwtUtil.getEmailFromToken(token);
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new CustomException(ErrorCode.MEMBER_NOT_FOUND));

Authentication auth = new UsernamePasswordAuthenticationToken(member, null, member.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}

filterChain.doFilter(request, response);
}

private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
89 changes: 58 additions & 31 deletions src/main/java/com/example/FixLog/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,31 +1,58 @@
//package com.example.FixLog.config;
//
//import org.springframework.context.annotation.Bean;
//import org.springframework.context.annotation.Configuration;
//import org.springframework.http.HttpMethod;
//import org.springframework.security.config.annotation.web.builders.HttpSecurity;
//import org.springframework.security.web.SecurityFilterChain;
//import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
//import org.springframework.security.crypto.password.PasswordEncoder;
//
//@Configuration
//public class SecurityConfig {
//
// @Bean
// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// http
// .csrf(csrf -> csrf.disable())
// .authorizeHttpRequests(auth -> auth
// .requestMatchers(HttpMethod.POST, "/api/members/signup").permitAll()
// .requestMatchers(HttpMethod.GET, "/api/members/check-email").permitAll()
// .requestMatchers(HttpMethod.GET, "/api/members/check-nickname").permitAll()
// .anyRequest().authenticated()
// );
// return http.build();
// }
//
// @Bean
// public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
// }
//}
package com.example.FixLog.config;

import com.example.FixLog.repository.MemberRepository;
import com.example.FixLog.util.JwtUtil;
import jakarta.servlet.Filter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtUtil jwtUtil;
private final MemberRepository memberRepository;

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.POST, "/api/members/signup").permitAll()
.requestMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
.requestMatchers(HttpMethod.GET, "/api/members/check-email").permitAll()
.requestMatchers(HttpMethod.GET, "/api/members/check-nickname").permitAll()
.requestMatchers(HttpMethod.GET, "/h2-console/**").permitAll()
.anyRequest().authenticated()
)
.headers(headers -> headers.frameOptions(frame -> frame.disable())) // H2 콘솔용
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

return http.build();
}

@Bean
public Filter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter(jwtUtil, memberRepository);
}

@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

// 인증 매니저 (선택: 로그인 시 AuthenticationManager 사용 가능)
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/example/FixLog/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.example.FixLog.controller;

import com.example.FixLog.dto.Response;
import com.example.FixLog.dto.member.LoginRequestDto;
import com.example.FixLog.dto.member.LoginResponseDto;
import com.example.FixLog.service.AuthService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {

private final AuthService authService;

@PostMapping("/login")
public ResponseEntity<Response<LoginResponseDto>> login(@RequestBody LoginRequestDto requestDto) {
LoginResponseDto result = authService.login(requestDto);
return ResponseEntity.ok(Response.success("로그인 성공", result));
}
}
91 changes: 53 additions & 38 deletions src/main/java/com/example/FixLog/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -1,38 +1,53 @@
//package com.example.FixLog.controller;
//
//
//import com.example.FixLog.dto.Response;
//import com.example.FixLog.dto.member.SignupRequestDto;
//import com.example.FixLog.dto.member.DuplicateCheckResponseDto;
//import com.example.FixLog.service.MemberService;
//import lombok.RequiredArgsConstructor;
//import org.springframework.http.ResponseEntity;
//import org.springframework.web.bind.annotation.*;
//
//@RestController
//@RequestMapping("/api/members")
//@RequiredArgsConstructor
//public class MemberController {
//
// private final MemberService memberService;
//
// @PostMapping("/signup")
// public ResponseEntity<Response<String>> signup(@RequestBody SignupRequestDto request) {
// memberService.signup(request);
// return ResponseEntity.ok(Response.success("회원가입이 완료되었습니다.", null));
// }
//
// @GetMapping("/check-email")
// public ResponseEntity<Response<DuplicateCheckResponseDto>> checkEmail(@RequestParam String email) {
// boolean exists = memberService.isEmailDuplicated(email);
// String msg = exists ? "이미 사용 중인 이메일입니다." : "사용 가능한 이메일입니다.";
// return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists)));
// }
//
// @GetMapping("/check-nickname")
// public ResponseEntity<Response<DuplicateCheckResponseDto>> checkNickname(@RequestParam String nickname) {
// boolean exists = memberService.isNicknameDuplicated(nickname);
// String msg = exists ? "이미 사용 중인 닉네임입니다." : "사용 가능한 닉네임입니다.";
// return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists)));
// }
//}
package com.example.FixLog.controller;


import com.example.FixLog.domain.member.Member;
import com.example.FixLog.dto.Response;
import com.example.FixLog.dto.member.MemberInfoResponseDto;
import com.example.FixLog.dto.member.SignupRequestDto;
import com.example.FixLog.dto.member.DuplicateCheckResponseDto;
import com.example.FixLog.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/members")
@RequiredArgsConstructor
public class MemberController {

private final MemberService memberService;

@PostMapping("/signup")
public ResponseEntity<Response<String>> signup(@RequestBody SignupRequestDto request) {
memberService.signup(request);
return ResponseEntity.ok(Response.success("회원가입 성공", null));
}

@GetMapping("/check-email")
public ResponseEntity<Response<DuplicateCheckResponseDto>> checkEmail(@RequestParam String email) {
boolean exists = memberService.isEmailDuplicated(email);
String msg = exists ? "이미 사용 중인 이메일입니다." : "사용 가능한 이메일입니다.";
return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists)));
}

@GetMapping("/check-nickname")
public ResponseEntity<Response<DuplicateCheckResponseDto>> checkNickname(@RequestParam String nickname) {
boolean exists = memberService.isNicknameDuplicated(nickname);
String msg = exists ? "이미 사용 중인 닉네임입니다." : "사용 가능한 닉네임입니다.";
return ResponseEntity.ok(Response.success(msg, new DuplicateCheckResponseDto(exists)));
}

@GetMapping("/me")
public ResponseEntity<Response<MemberInfoResponseDto>> getMyInfo(@AuthenticationPrincipal Member member) {
MemberInfoResponseDto responseDto = new MemberInfoResponseDto(
member.getEmail(),
member.getNickname(),
member.getProfileImageUrl(),
member.getBio(),
member.getSocialType()
);
return ResponseEntity.ok(Response.success("회원 정보 조회 성공", responseDto));
}
}
41 changes: 40 additions & 1 deletion src/main/java/com/example/FixLog/domain/member/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,20 @@
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
public class Member {
public class Member implements UserDetails {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down Expand Up @@ -76,6 +80,41 @@ public static Member of(String email, String password, String nickname, SocialTy
member.nickname = nickname;
member.socialType = socialType;
member.isDeleted = false;
member.profileImageUrl = "https://dummyimage.com/200x200/cccccc/ffffff&text=Profile"; // 기본 프로필 이미지(임시)
return member;
}

public void setProfileImageUrl(String profileImageUrl) {
this.profileImageUrl = profileImageUrl;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(new SimpleGrantedAuthority("ROLE_USER")); // 기본 권한
}

@Override
public String getUsername() {
return this.email; // 로그인 시 사용할 사용자 식별자
}

@Override
public boolean isAccountNonExpired() {
return true; // 계정 만료 여부 (true = 사용 가능)
}

@Override
public boolean isAccountNonLocked() {
return true; // 계정 잠금 여부 (true = 잠금 아님)
}

@Override
public boolean isCredentialsNonExpired() {
return true; // 비밀번호 만료 여부
}

@Override
public boolean isEnabled() {
return !this.isDeleted; // 탈퇴 여부 기반 활성 상태
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.FixLog.dto.member;

import lombok.Getter;

@Getter
public class LoginRequestDto {
private String email;
private String password;
}
15 changes: 15 additions & 0 deletions src/main/java/com/example/FixLog/dto/member/LoginResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.FixLog.dto.member;

import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Getter;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Getter
@AllArgsConstructor
public class LoginResponseDto {
private Long userId;
private String accessToken;
private String nickname;
private String profileImageUrl;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.FixLog.dto.member;

import com.example.FixLog.domain.member.SocialType;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class MemberInfoResponseDto {
private String email;
private String nickname;
private String profileImageUrl;
private String bio;
private SocialType socialType;
}
4 changes: 3 additions & 1 deletion src/main/java/com/example/FixLog/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ public enum ErrorCode {
FOLDER_NOT_FOUND(HttpStatus.NOT_FOUND, "폴더를 찾을 수 없습니다."),
ACCESS_DENIED(HttpStatus.FORBIDDEN, "권한이 없습니다."),
TAG_NOT_FOUND(HttpStatus.NOT_FOUND, "없는 태그 번호입니다."),
SORT_NOT_EXIST(HttpStatus.BAD_REQUEST, "사용할 수 없는 정렬입니다.");
SORT_NOT_EXIST(HttpStatus.BAD_REQUEST, "사용할 수 없는 정렬입니다."),
INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "비밀번호가 일치하지 않습니다."),
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 사용자입니다.");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기에 있는 MEMBER NOT FOUND 이미 있는 에러일 것같아요!
USER ID NOT FOUND 나 USER EMAIL NOT FOUND요!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗 그럼 로그인시에 회원 정보 없음도 둘 중에 한 에러로 통일하겠습니다!


private final HttpStatus status;
private final String message;
Expand Down
Loading