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
12 changes: 12 additions & 0 deletions 제리/workbook03/jerry/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ dependencies {

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

// 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
@@ -0,0 +1,62 @@
package com.example.jerry.domain.config;

import com.example.jerry.global.auth.JwtAuthFilter;
import com.example.jerry.global.auth.JwtUtil;
import com.example.jerry.global.auth.enums.CustomUserDetailsService;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
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 = {
"/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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.example.jerry.domain.converter;

import com.example.jerry.domain.dto.request.MemberSignupReqDto;
import com.example.jerry.domain.dto.response.MemberResDto;
import com.example.jerry.domain.entity.Member;
import com.example.jerry.global.auth.enums.Role;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class MemberConverter {

public static Member toMember(MemberSignupReqDto reqDto,
String encodedPassword,
Role role) {
if (reqDto == null) {
return null;
}

return Member.builder()
.email(reqDto.getEmail())
.password(encodedPassword)
.name(reqDto.getName())
.phone(reqDto.getPhone())
.role(role)
.build();
}

public static MemberResDto toMemberResDto(Member member) {
return MemberResDto.builder()
.memberId(member.getMemberId())
.email(member.getEmail())
.name(member.getName())
.phone(member.getPhone())
.points(member.getPoints())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.example.jerry.domain.dto.request;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter @Setter
@Getter
@NoArgsConstructor
public class MemberLoginReqDto {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.example.jerry.domain.dto.response;

import com.example.jerry.domain.entity.Member;
import lombok.Builder;
import lombok.Getter;

Expand All @@ -13,14 +12,4 @@ public class MemberResDto {
private String name;
private String phone;
private Long points;

public static MemberResDto from(Member member) {
return MemberResDto.builder()
.memberId(member.getMemberId())
.email(member.getEmail())
.name(member.getName())
.phone(member.getPhone())
.points(member.getPoints())
.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.jerry.domain.entity;

import com.example.jerry.global.auth.enums.Role;
import jakarta.persistence.*;
import lombok.*;

Expand All @@ -24,6 +25,9 @@ public class Member {
@Column(length = 200, nullable = false)
private String password;

@Enumerated(EnumType.STRING)
private Role role;

@Column(length = 30, nullable = false)
private String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.util.List;
import java.util.ArrayList;

import com.example.jerry.domain.converter.MemberConverter;
import com.example.jerry.domain.entity.Food;
import com.example.jerry.domain.entity.Member;
import com.example.jerry.domain.entity.MemberPreference;
Expand All @@ -14,7 +16,10 @@
import com.example.jerry.domain.repository.FoodRepository;
import com.example.jerry.domain.repository.MemberRepository;
import com.example.jerry.domain.repository.MemberPreferenceRepository;
import com.example.jerry.global.auth.JwtUtil;
import com.example.jerry.global.auth.enums.Role;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -27,6 +32,8 @@ public class MemberService {
private final MemberRepository memberRepository;
private final FoodRepository foodRepository;
private final MemberPreferenceRepository memberPreferenceRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;

// 회원가입

Expand All @@ -40,13 +47,13 @@ public MemberResDto signup(MemberSignupReqDto req) {
throw new TestException(MemberErrorCode.DUPLICATE_PHONE);
}

// Member 생성 및 저장
Member member = Member.builder()
.email(req.getEmail())
.password(req.getPassword())
.name(req.getName())
.phone(req.getPhone())
.build();
String encodedPassword = passwordEncoder.encode(req.getPassword());

Member member = MemberConverter.toMember(
req,
encodedPassword,
Role.ROLE_USER
);

memberRepository.save(member);

Expand All @@ -71,7 +78,7 @@ public MemberResDto signup(MemberSignupReqDto req) {
memberPreferenceRepository.saveAll(preferences);
}

return MemberResDto.from(member);
return MemberConverter.toMemberResDto(member);
}


Expand All @@ -81,11 +88,11 @@ public MemberResDto login(MemberLoginReqDto req) {
Member member = memberRepository.findByEmail(req.getEmail())
.orElseThrow(() -> new TestException(MemberErrorCode.INVALID_LOGIN));

if (!member.getPassword().equals(req.getPassword())) {
if (!passwordEncoder.matches(req.getPassword(), member.getPassword())) {
throw new TestException(MemberErrorCode.INVALID_LOGIN);
}

return MemberResDto.from(member);
return MemberConverter.toMemberResDto(member);
}

// 멤버 단일 조회
Expand All @@ -94,6 +101,6 @@ public MemberResDto getMember(Long memberId) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new TestException(MemberErrorCode.MEMBER_NOT_FOUND));

return MemberResDto.from(member);
return MemberConverter.toMemberResDto(member);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.example.jerry.global.auth;

import com.example.jerry.domain.exception.TestException;
import com.example.jerry.global.apiPayload.ApiResponse;
import com.example.jerry.global.apiPayload.code.GeneralErrorCode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
private final ObjectMapper objectMapper = new ObjectMapper();


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

com.example.jerry.global.apiPayload.ApiResponse<Void> errorResponse = ApiResponse.onFailure(
GeneralErrorCode.UNAUTHORIZED,
null
);

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

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws java.io.IOException, ServletException {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.example.jerry.global.auth;

import com.example.jerry.global.apiPayload.ApiResponse;
import com.example.jerry.global.apiPayload.code.GeneralErrorCode;
import com.example.jerry.global.auth.enums.CustomUserDetailsService;
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.RequiredArgsConstructor;
import org.springframework.lang.NonNull;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
@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()
);

// SecurityContext에 인증 정보 저장
SecurityContextHolder.getContext().setAuthentication(auth);
}

filterChain.doFilter(request, response);

} catch (Exception e) {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

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

ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.getOutputStream(), errorResponse);
}
}
}
Loading