Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[๐Ÿ‡ Assignment/#6] Week6_Seminar_์‹ค์Šต๊ณผ์ œ ๊ตฌํ˜„ #22

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
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
1 change: 1 addition & 0 deletions practice/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
38 changes: 38 additions & 0 deletions practice/src/main/java/org/sopt/practice/auth/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.sopt.practice.auth;

import lombok.RequiredArgsConstructor;
import org.sopt.practice.dto.request.LoginRequest;
import org.sopt.practice.dto.request.RefreshTokenRequest;
import org.sopt.practice.dto.response.LoginResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

private final AuthService authService;

@PostMapping("/login")
public ResponseEntity<LoginResponse> login(
@RequestBody LoginRequest loginRequest
){
LoginResponse loginResponse = authService.login(loginRequest);
return ResponseEntity.status(HttpStatus.OK)
.body(loginResponse);
}

@PostMapping("/refresh")
public ResponseEntity<LoginResponse> refresh(
@RequestBody RefreshTokenRequest refreshTokenRequest
){
LoginResponse loginResponse = authService.refreshAccessToken(refreshTokenRequest);
return ResponseEntity.status(HttpStatus.OK)
.body(loginResponse);
}
}
62 changes: 62 additions & 0 deletions practice/src/main/java/org/sopt/practice/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.sopt.practice.auth;

import lombok.RequiredArgsConstructor;
import org.sopt.practice.auth.redis.domain.Token;
import org.sopt.practice.auth.redis.repository.RedisTokenRepository;
import org.sopt.practice.common.jwt.JwtTokenProvider;
import org.sopt.practice.domain.Member;
import org.sopt.practice.dto.request.LoginRequest;
import org.sopt.practice.dto.request.RefreshTokenRequest;
import org.sopt.practice.dto.response.LoginResponse;
import org.sopt.practice.exception.NotFoundException;
import org.sopt.practice.exception.enums.ErrorMessage;
import org.sopt.practice.repository.MemberRepository;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class AuthService {

private final JwtTokenProvider jwtTokenProvider;
private final RedisTokenRepository redisTokenRepository;
private final MemberRepository memberRepository;

public LoginResponse login(LoginRequest loginRequest){
// ์‚ฌ์šฉ์ž์˜ ์ž๊ฒฉ ์ฆ๋ช…์„ ํ™•์ธํ•˜๋Š” ๋กœ์ง
Member member = memberRepository.findByUsername(loginRequest.username())
.orElseThrow(() -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND_BY_ID_EXCEPTION));

// ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋กœ์ง
if(!member.getPassword().equals(loginRequest.password())){
throw new NotFoundException(ErrorMessage.INVALID_MEMBER_PASSWORD_EXCEPTION);
}

Long memberId = member.getId();

String accessToken = jwtTokenProvider.issueAccessToken(
UserAuthentication.createUserAuthentication(memberId)
);

String refreshToken = jwtTokenProvider.issueRefreshToken(
UserAuthentication.createUserAuthentication(memberId)
);

//๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์„ Redis์— ์ €์žฅ
redisTokenRepository.save(Token.of(memberId, refreshToken));

return LoginResponse.of(accessToken, refreshToken);
}

public LoginResponse refreshAccessToken(RefreshTokenRequest refreshTokenRequest){
String refreshToken = refreshTokenRequest.refreshToken();
Token token = redisTokenRepository.findByRefreshToken(refreshToken)
.orElseThrow(() -> new NotFoundException(ErrorMessage.INVALID_REFRESH_TOKEN_EXCEPTION));

Long userId = token.getId();
String newAccessToken = jwtTokenProvider.issueRefreshToken(
UserAuthentication.createUserAuthentication(userId)
);

return LoginResponse.of(newAccessToken, refreshToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ public class SecurityConfig {
private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint;
private final CustomAccessDeniedHandler customAccessDeniedHandler;

private static final String[] AUTH_WHITE_LIST = {"/api/v1/member"};
private static final String[] AUTH_WHITE_LIST = {
"/swagger-ui/**",
"/swagger-ui.html",
"/v3/api-docs/**",
"/api/v1/member",
"/api/v1/auth/login",
"/api/v1/auth/refresh",
"/api/v1/member/find-all",
};

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class JwtTokenProvider {
private static final String USER_ID = "userId";

private static final Long ACCESS_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L * 14;
private static final Long REFRESH_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L * 14;

@Value("${jwt.secret}")
private String JWT_SECRET;
Expand All @@ -27,6 +28,10 @@ public String issueAccessToken(final Authentication authentication){
return generateToken(authentication, ACCESS_TOKEN_EXPIRATION_TIME);
}

public String issueRefreshToken(final Authentication authentication){
return generateToken(authentication, REFRESH_TOKEN_EXPIRATION_TIME);
}

public String generateToken(Authentication authentication, Long tokenExpirationTime){
final Date now = new Date();
final Claims claims = Jwts.claims()
Expand Down Expand Up @@ -74,5 +79,4 @@ public Long getUserFromJwt(String token){
Claims claims = getBody(token);
return Long.valueOf(claims.get(USER_ID).toString());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.net.URI;
import java.util.List;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -47,7 +46,7 @@ public ResponseEntity deleteMemberById(
return ResponseEntity.noContent().build();
}

@GetMapping("find-all")
@GetMapping("/find-all")
public ResponseEntity<List<MemberFindAllDto>> findAllMembers(){
List<Member> members = memberService.findAllMembers();
List<MemberFindAllDto> memberFindAllDtoList = members.stream()
Expand Down
14 changes: 9 additions & 5 deletions practice/src/main/java/org/sopt/practice/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,30 @@ public class Member {
@GeneratedValue(strategy = GenerationType.IDENTITY) //๊ธฐ๋ณธํ‚ค ์ƒ์„ฑ์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์œ„์ž„
private Long id;

private String name;
private String username; // ์œ ์ € ์ด๋ฆ„

private String password; // ์œ ์ € ๋น„๋ฐ€๋ฒˆํ˜ธ

@Enumerated(EnumType.STRING) //Enum์˜ ์ด๋ฆ„์„ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ
private Part part;

private int age;

// ์ •์  ํŒฉํ† ๋ฆฌ ๋ฉ”์„œ๋“œ๋กœ ๊ฐ์ฒด ์ƒ์„ฑ
public static Member create(String name, Part part, int age){
public static Member create(String name, String password, Part part, int age){
return Member.builder()
.name(name)
.username(name)
.password(password)
.part(part)
.age(age)
.build();
}

// ๋นŒ๋”ํŒจํ„ด์œผ๋กœ ๊ฐ์ฒด ์ƒ์„ฑ
@Builder
private Member(final String name, final Part part, final int age){
this.name = name;
private Member(final String username, String password,final Part part, final int age){
this.username = username;
this.password = password;
this.part = part;
this.age = age;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.practice.dto.request;

public record LoginRequest(
String username,
String password
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
import org.sopt.practice.domain.enums.Part;

public record MemberCreateRequest(
String name,
String username,
String password,
Part part,
int age
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.sopt.practice.dto.request;

public record RefreshTokenRequest(
String refreshToken
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.sopt.practice.dto.response;

public record LoginResponse(
String accessToken,
String refreshToken
) {
public static LoginResponse of(
String accessToken,
String refreshToken
){
return new LoginResponse(accessToken, refreshToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public record MemberFindAllDto(
public static MemberFindAllDto of(
Member member
){
return new MemberFindAllDto(member.getName(), member.getPart(), member.getAge());
return new MemberFindAllDto(member.getUsername(), member.getPart(), member.getAge());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ public record MemberFindDto(
public static MemberFindDto of(
Member member
){
return new MemberFindDto(member.getName(), member.getPart(), member.getAge());
return new MemberFindDto(member.getUsername(), member.getPart(), member.getAge());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

public record UserJoinResponse(
String accessToken,
String refreshToken,
String userId
) {
public static UserJoinResponse of(
String accessToken,
String refreshToken,
String userId
){
return new UserJoinResponse(accessToken, userId);
return new UserJoinResponse(accessToken, refreshToken, userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public enum ErrorMessage {
MEMBER_NOT_OWNED_BLOG_EXCEPTION(HttpStatus.NOT_FOUND.value(), "๋ธ”๋กœ๊ทธ ์†Œ์œ ์ž๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค."),
POST_NOT_FOUND_BY_ID_EXCEPTION(HttpStatus.NOT_FOUND.value(), "ID์— ํ•ด๋‹นํ•˜๋Š” ๊ฒŒ์‹œ๊ธ€์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."),
JWT_UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "์‚ฌ์šฉ์ž์˜ ๋กœ๊ทธ์ธ ๊ฒ€์ฆ์„ ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค."),
INVALID_MEMBER_PASSWORD_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."),
INVALID_REFRESH_TOKEN_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "Refresh ํ† ํฐ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."),
;

private final int status;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.sopt.practice.external;

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SwaggerConfig {

@Bean
public OpenAPI openAPI(){
Info info = new Info()
.title("NOW SOPT PRACTICE SWAGGER")
.description("NOW SOPT ์„ธ๋ฏธ๋‚˜ & ์‹ค์Šต API ๊ตฌํ˜„")
.version("v1");

return new OpenAPI()
.info(info);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ public interface MemberRepository extends Repository<Member, Long> {
Member save(Member member);
Optional<Member> findById(Long memberId);
void delete(Member member);

List<Member> findAll();
Optional<Member> findByUsername(String username);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import jakarta.persistence.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.sopt.practice.auth.UserAuthentication;
import org.sopt.practice.auth.redis.domain.Token;
import org.sopt.practice.auth.redis.repository.RedisTokenRepository;
import org.sopt.practice.common.jwt.JwtTokenProvider;
import org.sopt.practice.domain.Member;
import org.sopt.practice.dto.request.MemberCreateRequest;
Expand All @@ -22,19 +24,30 @@ public class MemberService {

private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
private final RedisTokenRepository redisTokenRepository;

@Transactional //๋ณ€๊ฒฝ์‚ฌํ•ญ์„ DB์— ๋ฐ˜์˜
public UserJoinResponse createMember(
MemberCreateRequest memberCreateRequest
){
Member member = memberRepository.save(
Member.create(memberCreateRequest.name(), memberCreateRequest.part(), memberCreateRequest.age())
Member.create(memberCreateRequest.username(), memberCreateRequest.password() ,memberCreateRequest.part(), memberCreateRequest.age())
);

Long memberId = member.getId();

String accessToken = jwtTokenProvider.issueAccessToken(
UserAuthentication.createUserAuthentication(memberId)
);
return UserJoinResponse.of(accessToken, memberId.toString());

String refreshToken = jwtTokenProvider.issueRefreshToken(
UserAuthentication.createUserAuthentication(memberId)
);

// RefreshToken์„ Redis์— ์ €์žฅ
redisTokenRepository.save(Token.of(memberId, refreshToken));

return UserJoinResponse.of(accessToken, refreshToken, memberId.toString());
}

public MemberFindDto findMemberById(
Expand Down