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
13 changes: 13 additions & 0 deletions 따밥/server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,19 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'

<<<<<<< Updated upstream

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

요거 제거 해주세요

=======
// 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'

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

>>>>>>> Stashed changes
// QueryDSL : OpenFeign
implementation 'io.github.openfeign.querydsl:querydsl-jpa:7.0'
implementation 'io.github.openfeign.querydsl:querydsl-core:7.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ public class AuthController {

@GetMapping("/login")
public ApiResponse<MemberResDTO.LoginDTO> login(
@RequestParam Long memberId
MemberReqDTO.@Valid LoginDTO request
){
Member member = memberService.findByUsername(memberId);
GeneralSuccessCode code = GeneralSuccessCode._OK;
return ApiResponse.success(
code, MemberConverter.toLoginDTO(member)
MemberSuccessCode.MEMBER_FOUND,
memberService.login(request)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ public static Member toMember(MemberReqDTO.JoinDTO dto){
}

// Optional<Member> 객체 -> LoginDTO
public static MemberResDTO.LoginDTO toLoginDTO(Member member){
public static MemberResDTO.LoginDTO toLoginDTO(Member member, String accessToken){
return MemberResDTO.LoginDTO.builder()
.memberId(member.getId())
.memberNickname(member.getNickname())
.accessToken(accessToken)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ public record JoinDTO(
String addr4
){}

// 로그인
public record LoginDTO(
@NotBlank String email,
@NotBlank String password
){}

@Getter
public static class HomeDTO{
private String region;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public record ChallengeMissionDTO(
@Builder
public static class LoginDTO{
private Long memberId;
private String memberNickname;
private String accessToken;
}

@Getter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ public enum MemberErrorCode implements BaseErrorCode {
"이미 사용중인 전화번호입니다."),
MEMBER_MISSION_NOT_FOUND(HttpStatus.NOT_FOUND,
"MEMBER404_2",
"해당 회원에게 요청한 미션이 할당된 적 없습니다.")
"해당 회원에게 요청한 미션이 할당된 적 없습니다."),
PASSWORD_INVALID(HttpStatus.BAD_REQUEST,
"비밀번호가 잘못되었습니다.",
"MEMBER400_3"),
;


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
public interface MemberService {
Member findByUsername(Long memberId);
MemberResDTO.JoinDTO join(MemberReqDTO.JoinDTO request);
MemberResDTO.LoginDTO login(MemberReqDTO.LoginDTO request);
MemberResDTO.HomeTopDTO getHomeTop(Long memberId, String region, Long cursor, int size);
void missionSuccess(Long memberId, Long missionId);
MemberResDTO.MyPageDTO getMyPage(Long memberId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package umc.server.domain.member.service;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
Expand Down Expand Up @@ -34,6 +35,12 @@
import umc.server.domain.review.dto.res.ReviewResDTO;
import umc.server.domain.review.entity.Review;
import umc.server.domain.review.repository.ReviewRepository;
<<<<<<< Updated upstream

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

이거 다 제거해주세요~!

=======
import umc.server.global.auth.CustomUserDetails;
import umc.server.global.auth.JwtUtil;
import umc.server.global.auth.enums.Role;
>>>>>>> Stashed changes

import java.util.ArrayList;
import java.util.List;
Expand All @@ -52,6 +59,12 @@ public class MemberServiceImpl implements MemberService{
private final MemberFoodRepository memberFoodRepository;
private final MissionRepository missionRepository;

<<<<<<< Updated upstream
=======
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;

>>>>>>> Stashed changes
@Override
public Member findByUsername(Long memberId) {
return memberRepository.findById(memberId)
Expand Down Expand Up @@ -88,6 +101,22 @@ public MemberResDTO.JoinDTO join(MemberReqDTO.JoinDTO request) {
return MemberConverter.toJoinDTO(member);
}

@Override
public MemberResDTO.LoginDTO login(MemberReqDTO.@Valid LoginDTO request) {
Member member = memberRepository.findByEmail(request.email())
.orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));

if (!passwordEncoder.matches(request.password(), member.getPassword())) {
throw new MemberException(MemberErrorCode.PASSWORD_INVALID);
}

CustomUserDetails userDetails = new CustomUserDetails(member);

String accessToken = jwtUtil.createAccessToken(userDetails);

return MemberConverter.toLoginDTO(member, accessToken);
}

@Override
public MemberResDTO.HomeTopDTO getHomeTop(Long memberId, String region, Long cursor, int size) {
Member member = findByUsername(memberId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package umc.server.global.auth;

import com.fasterxml.jackson.databind.ObjectMapper;
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;
import umc.server.global.apiPayload.ApiResponse;
import umc.server.global.apiPayload.code.GeneralErrorCode;

import java.io.IOException;

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

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

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

mapper.writeValue(response.getOutputStream(), errorResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package umc.server.global.auth;

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.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
import umc.server.global.apiPayload.ApiResponse;
import umc.server.global.apiPayload.code.GeneralErrorCode;

import java.io.IOException;

@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final CustomUserDetailsService userDetailsService;

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

String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
token = token.replace("Bearer ", "");

if (jwtUtil.isValid(token)) {
String email = jwtUtil.getEmail(token);

UserDetails user = userDetailsService.loadUserByUsername(email);
Authentication auth = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
// 이 부분이 일정동안 Authentication을 유지하고 있다고 하는 부분인 것 같다.
}
filterChain.doFilter(request, response);
}
}
82 changes: 82 additions & 0 deletions 따밥/server/src/main/java/umc/server/global/auth/JwtUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package umc.server.global.auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.stream.Collectors;

@Component
public class JwtUtil {
private final SecretKey secretKey;
private final Duration accessExpiration;

public JwtUtil(
@Value("${jwt.token.secretKey}") String secret,
@Value("${jwt.token.expiration}") Long expiration
){
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
this.accessExpiration = Duration.ofMillis(expiration);
}

// AccessToken 생성
public String createAccessToken(CustomUserDetails user){
return createToken(user, accessExpiration);
}

// 토큰에서 이메일 가져오기
public String getEmail(String token){
try{
return getClaims(token).getPayload().getSubject();
} catch (JwtException e){
return null;
}
}

public boolean isValid(String token){
try {
getClaims(token);
return true;
} catch (JwtException e) {
return false;
}
}


private Jws<Claims> getClaims(String token) throws JwtException{
return Jwts.parser()
.verifyWith(secretKey)
.clockSkewSeconds(60)
.build()
.parseSignedClaims(token);
}

private String createToken(CustomUserDetails user, Duration expiration){
Instant now = Instant.now();

// 인가 정보
String authorities = user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));

return Jwts.builder()
.subject(user.getUsername()) // 여기서 username은 email
.claim("role", authorities)
.claim("email", user.getUsername())
.issuedAt(Date.from(now))
.expiration(Date.from(now.plus(expiration)))
.signWith(secretKey)
.compact();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package umc.server.global.config;

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;
import umc.server.global.auth.AuthenticationEntryPointImpl;
import umc.server.global.auth.CustomUserDetailsService;
import umc.server.global.auth.JwtAuthFilter;
import umc.server.global.auth.JwtUtil;

@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/**",
"/auth/**"
};
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}

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

@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPointImpl();
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http
.csrf(AbstractHttpConfigurer::disable);
http
.formLogin(AbstractHttpConfigurer::disable);

http
.authorizeHttpRequests(requests -> requests
.requestMatchers(allowUris).permitAll()
.requestMatchers("/swagger-ui/index.html#").hasRole("ADMIN")
.anyRequest().authenticated()
);

http
.exceptionHandling(exception -> exception.authenticationEntryPoint(authenticationEntryPoint()));



http
.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);

http
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.permitAll()
);

return http.build();
}
}
Loading