Skip to content
Open
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
10 changes: 10 additions & 0 deletions junjunseo/워크북실습/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ dependencies {

// Validation
implementation 'org.springframework.boot:spring-boot-starter-validation'

// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// Jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
implementation 'org.springframework.boot:spring-boot-configuration-processor'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.umc9th.domain.member.dto.MemberResDto;
import com.example.umc9th.domain.member.exception.code.MemberSuccessCode;
import com.example.umc9th.domain.member.service.command.MemberCommandService;
import com.example.umc9th.domain.member.service.query.MemberQueryService;
import com.example.umc9th.global.apiPayload.ApiResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
Expand All @@ -16,6 +17,7 @@
public class MemberController {

private final MemberCommandService memberCommandService;
private final MemberQueryService memberQueryService;

// 회원가입
@PostMapping("/sign-up")
Expand All @@ -25,5 +27,11 @@ public ApiResponse<MemberResDto.JoinDto> signUp(
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberCommandService.signup(dto));
}


// 로그인
@PostMapping("/login")
public ApiResponse<MemberResDto.LoginDto> login(
@RequestBody @Valid MemberReqDto.LoginDto dto
){
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberQueryService.login(dto));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.umc9th.domain.member.dto.MemberReqDto;
import com.example.umc9th.domain.member.dto.MemberResDto;
import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.domain.member.enums.Role;

public class MemberConverter {
// Entity -> DTO
Expand All @@ -14,13 +15,23 @@ public static MemberResDto.JoinDto toJoinDTO(Member member) {
}

// DTO -> Entity
public static Member toMember(MemberReqDto.JoinDto dto) {
public static Member toMember(MemberReqDto.JoinDto dto, String password, Role role){
return Member.builder()
.name(dto.name())
.email(dto.email())
.password(password)
.role(role)
.birth(dto.birth())
.address(dto.address())
.detailAddress(dto.specAddress())
.gender(dto.gender())
.build();
}

public static MemberResDto.LoginDto toLoginDto(Member member, String accessToken) {
return MemberResDto.LoginDto.builder()
.memberId(member.getId())
.accessToken(accessToken)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,39 @@
import com.example.umc9th.domain.member.enums.Address;
import com.example.umc9th.domain.member.enums.Gender;
import com.example.umc9th.global.annotation.ExistFoods;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDate;
import java.util.List;

public class MemberReqDto {

public record JoinDto(
@NotBlank
String name,
@Email
String email, // 추가된 속성
@NotBlank
String password, // 추가된 속성
@NotNull
Gender gender,
@NotNull
LocalDate birth,
@NotNull
Address address,
@NotNull
String specAddress,
@ExistFoods
List<Long> preferCategory
){}

// 로그인
public record LoginDto(
@NotBlank
String email,
@NotBlank
String password
){}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ public record JoinDto(
LocalDateTime createAt
){}

// 로그인
@Builder
public record LoginDto(
Long memberId,
String accessToken
){}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.umc9th.domain.member.entity.mapping.MemberTerm;
import com.example.umc9th.domain.member.enums.Address;
import com.example.umc9th.domain.member.enums.Gender;
import com.example.umc9th.domain.member.enums.Role;
import com.example.umc9th.domain.member.enums.SocialType;
import com.example.umc9th.domain.mission.entity.mapping.MemberMission;
import com.example.umc9th.domain.review.entity.Review;
Expand Down Expand Up @@ -60,6 +61,12 @@ public class Member extends BaseEntity {
@Column(name = "email")
private String email;

@Column(nullable = false)
private String password;

@Enumerated(EnumType.STRING)
private Role role;

@Column(name = "phone_number")
private String phoneNumber;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.umc9th.domain.member.enums;

public enum Role {
ROLE_ADMIN,
ROLE_USER
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public enum MemberErrorCode implements BaseErrorCode {
NOT_FOUND(HttpStatus.NOT_FOUND,
"MEMBER404_1",
"해당 사용자를 찾지 못했습니다."),
INVALID(HttpStatus.BAD_REQUEST,
"MEMBER400_2",
"잘못된 회원 정보 요청입니다.")
;

private final HttpStatus status;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@
import com.example.umc9th.domain.member.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface MemberRepository extends JpaRepository<Member,Long> {

Optional<Member> findByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import com.example.umc9th.domain.member.entity.Food;
import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.domain.member.entity.mapping.MemberFood;
import com.example.umc9th.domain.member.enums.Role;
import com.example.umc9th.domain.member.exception.FoodException;
import com.example.umc9th.domain.member.exception.code.FoodErrorCode;
import com.example.umc9th.domain.member.repository.FoodRepository;
import com.example.umc9th.domain.member.repository.MemberFoodRepository;
import com.example.umc9th.domain.member.repository.MemberRepository;
import com.example.umc9th.global.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
Expand All @@ -24,14 +27,23 @@ public class MemberCommandServiceImpl implements MemberCommandService {
private final MemberRepository memberRepository;
private final MemberFoodRepository memberFoodRepository;
private final FoodRepository foodRepository;
private final JwtUtil jwtUtil;

// Password Encoder
private final PasswordEncoder passwordEncoder;

// 회원가입
@Override
public MemberResDto.JoinDto signup(
MemberReqDto.JoinDto dto
) {
// 사용자 생성
Member member = MemberConverter.toMember(dto);

// 솔트된 비밀번호 생성
String salt = passwordEncoder.encode(dto.password());

// 사용자 생성: 유저 / 관리자는 따로 API 만들어서 관리
Member member = MemberConverter.toMember(dto, salt, Role.ROLE_USER);

// DB 적용
memberRepository.save(member);

Expand Down Expand Up @@ -64,4 +76,6 @@ public MemberResDto.JoinDto signup(
// 응답 DTO 생성
return MemberConverter.toJoinDTO(member);
}


}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
package com.example.umc9th.domain.member.service.query;

import com.example.umc9th.domain.member.dto.MemberReqDto;
import com.example.umc9th.domain.member.dto.MemberResDto;
import jakarta.validation.Valid;

public interface MemberQueryService {
MemberResDto.LoginDto login(MemberReqDto.@Valid LoginDto dto);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
package com.example.umc9th.domain.member.service.query;

public class MemberQueryServiceImpl {
import com.example.umc9th.domain.member.converter.MemberConverter;
import com.example.umc9th.domain.member.dto.MemberReqDto;
import com.example.umc9th.domain.member.dto.MemberResDto;
import com.example.umc9th.domain.member.entity.Member;
import com.example.umc9th.domain.member.exception.MemberException;
import com.example.umc9th.domain.member.exception.code.MemberErrorCode;
import com.example.umc9th.domain.member.repository.MemberRepository;
import com.example.umc9th.global.entity.CustomUserDetails;
import com.example.umc9th.global.util.JwtUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MemberQueryServiceImpl implements MemberQueryService {

private final MemberRepository memberRepository;
private final JwtUtil jwtUtil;
private final PasswordEncoder encoder;

@Override
public MemberResDto.LoginDto login(
MemberReqDto.@Valid LoginDto dto
) {

// Member 조회
Member member = memberRepository.findByEmail(dto.email())
.orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND));

// 비밀번호 검증
if (!encoder.matches(dto.password(), member.getPassword())){
throw new MemberException(MemberErrorCode.INVALID);
}

// JWT 토큰 발급용 UserDetails
CustomUserDetails userDetails = new CustomUserDetails(member);

// 엑세스 토큰 발급
String accessToken = jwtUtil.createAccessToken(userDetails);

// DTO 조립
return MemberConverter.toLoginDto(member, accessToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.example.umc9th.global.config;

import com.example.umc9th.global.entity.AuthenticationEntryPointImpl;
import com.example.umc9th.global.entity.CustomUserDetailsService;
import com.example.umc9th.global.util.JwtAuthFilter;
import com.example.umc9th.global.util.JwtUtil;
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.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


@EnableWebSecurity
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

private final JwtUtil jwtUtil;
private final CustomUserDetailsService customUserDetailsService;

private final String[] allowUris = {
"/login",
"/sign-up",
// Swagger 허용
"/swagger-ui/**",
"/swagger-resources/**",
"/v3/api-docs/**",
};

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(requests -> requests
.requestMatchers(allowUris).permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
// 폼로그인 비활성화
.formLogin(AbstractHttpConfigurer::disable)
// JwtAuthFilter를 UsernamePasswordAuthenticationFilter 앞에 추가
.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf(AbstractHttpConfigurer::disable)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);

return http.build();
}

@Bean
public JwtAuthFilter jwtAuthFilter() {
return new JwtAuthFilter(jwtUtil, customUserDetailsService);
}

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

@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPointImpl();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.umc9th.global.entity;

import com.example.umc9th.global.apiPayload.ApiResponse;
import com.example.umc9th.global.apiPayload.code.GeneralErrorCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import java.io.IOException;

public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {

private final ObjectMapper objectMapper = new ObjectMapper();

@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException
) throws IOException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

ApiResponse<Void> errorResponse = ApiResponse.onFailure(
GeneralErrorCode.UNAUTHORIZED,
null
);

objectMapper.writeValue(response.getOutputStream(), errorResponse);
}
}
Loading