Skip to content

Commit

Permalink
Merge pull request #6 from oven-2023/feature/auth
Browse files Browse the repository at this point in the history
Feature/auth
  • Loading branch information
haen-su authored Oct 13, 2023
2 parents f9ea98c + fd87723 commit ff1b4fd
Show file tree
Hide file tree
Showing 22 changed files with 691 additions and 7 deletions.
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ dependencies {
// spring security
implementation 'org.springframework.boot:spring-boot-starter-security'

// jwt
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'javax.xml.bind:jaxb-api:2.4.0-b180830.0359'

// websocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package com.oven.server.api.user.controller;

import com.oven.server.api.user.domain.User;
import com.oven.server.api.user.dto.request.IdCheckRequest;
import com.oven.server.api.user.dto.request.JoinRequest;
import com.oven.server.api.user.dto.request.LoginRequest;
import com.oven.server.api.user.dto.request.RefreshTokenRequest;
import com.oven.server.api.user.dto.response.AccessTokenResponse;
import com.oven.server.api.user.dto.response.IdCheckResponse;
import com.oven.server.api.user.dto.response.JwtTokenResponse;
import com.oven.server.api.user.service.AuthService;
import com.oven.server.common.response.Response;
import com.oven.server.common.response.ResponseCode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -30,4 +37,32 @@ public Response<IdCheckResponse> idDuplicateCheck(@RequestBody IdCheckRequest id
return Response.success(ResponseCode.SUCCESS_OK, idCheckResponse);
}

@Operation(summary = "회원가입")
@PostMapping(value = "/join")
public Response<Void> join(@RequestBody JoinRequest joinRequest) {
authService.join(joinRequest);
return Response.success(ResponseCode.SUCCESS_CREATED);
}

@Operation(summary = "로그인")
@PostMapping(value = "/login")
public Response<JwtTokenResponse> login(@RequestBody LoginRequest loginRequest) {
JwtTokenResponse jwtTokenResponse = authService.login(loginRequest);
return Response.success(ResponseCode.SUCCESS_OK, jwtTokenResponse);
}

@Operation(summary = "로그아웃")
@PostMapping(value = "/logout")
public Response<Void> signOut(@AuthenticationPrincipal User user, @RequestBody RefreshTokenRequest refreshTokenRequest) {
authService.logout(user, refreshTokenRequest);
return Response.success(ResponseCode.SUCCESS_OK);
}

@Operation(summary = "리프레쉬 토큰으로 액세스 토큰 재발급")
@PostMapping(value = "/reissuance")
public Response<AccessTokenResponse> reissueAccessToken(@RequestBody RefreshTokenRequest refreshTokenRequest) {
AccessTokenResponse accessTokenDto = authService.reissueAccessToken(refreshTokenRequest);
return Response.success(ResponseCode.SUCCESS_CREATED, accessTokenDto);
}

}
25 changes: 25 additions & 0 deletions src/main/java/com/oven/server/api/user/domain/RefreshToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.oven.server.api.user.domain;

import com.oven.server.common.BaseEntity;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

@Getter
@RedisHash(value = "refreshToken", timeToLive = 60 * 60 * 24 * 14) // 단위는 초
public class RefreshToken extends BaseEntity {

@Id
@Indexed
private String refreshToken;
private String username;

@Builder
public RefreshToken(String refreshToken, String username) {
this.refreshToken = refreshToken;
this.username = username;
}

}
42 changes: 38 additions & 4 deletions src/main/java/com/oven/server/api/user/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

import com.oven.server.common.BaseEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User extends BaseEntity {
public class User extends BaseEntity implements UserDetails {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -25,4 +26,37 @@ public class User extends BaseEntity {

private String password;

@Builder
public User(Long id, String username, String nickname, String password) {
this.id = id;
this.username = username;
this.nickname = nickname;
this.password = password;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.oven.server.api.user.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;

@Getter
public class LoginRequest {

@Schema(description = "아이디", example = "id2023")
private String username;

@Schema(description = "패스워드", example = "password123@")
private String password;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.oven.server.api.user.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;

@Getter
public class RefreshTokenRequest {

@Schema(description = "리프레쉬 토큰", example = "asdlkjfb01sadf03918da23;091")
private String refreshToken;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.oven.server.api.user.dto.response;

import lombok.Builder;
import lombok.Getter;

@Getter
public class AccessTokenResponse {

private String accessToken;

@Builder
public AccessTokenResponse(String accessToken) {
this.accessToken = accessToken;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.oven.server.api.user.repository;

import com.oven.server.api.user.domain.RefreshToken;
import org.springframework.data.repository.CrudRepository;

public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ public interface UserRepository extends JpaRepository<User, Long> {

boolean existsByUsername(String username);

Optional<User> findByUsername(String username);

}
77 changes: 77 additions & 0 deletions src/main/java/com/oven/server/api/user/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
package com.oven.server.api.user.service;

import com.oven.server.api.user.domain.RefreshToken;
import com.oven.server.api.user.domain.User;
import com.oven.server.api.user.dto.request.IdCheckRequest;
import com.oven.server.api.user.dto.request.JoinRequest;
import com.oven.server.api.user.dto.request.LoginRequest;
import com.oven.server.api.user.dto.request.RefreshTokenRequest;
import com.oven.server.api.user.dto.response.AccessTokenResponse;
import com.oven.server.api.user.dto.response.IdCheckResponse;
import com.oven.server.api.user.dto.response.JwtTokenResponse;
import com.oven.server.api.user.repository.RefreshTokenRepository;
import com.oven.server.api.user.repository.UserRepository;
import com.oven.server.common.exception.BaseException;
import com.oven.server.common.response.ResponseCode;
import com.oven.server.config.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;

@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class AuthService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenRepository refreshTokenRepository;

public IdCheckResponse idDuplicateCheck(IdCheckRequest idCheckRequest) throws BaseException {

Expand All @@ -28,4 +42,67 @@ public IdCheckResponse idDuplicateCheck(IdCheckRequest idCheckRequest) throws Ba

}

public void join(JoinRequest joinRequest) throws BaseException {

User newUser = User.builder()
.username(joinRequest.getUsername())
.nickname(joinRequest.getNickname())
.password(passwordEncoder.encode(joinRequest.getPassword()))
.build();

userRepository.save(newUser);

}

public JwtTokenResponse login(LoginRequest loginRequest) {

User user = userRepository.findByUsername(loginRequest.getUsername())
.orElseThrow(() -> new BaseException(ResponseCode.ID_NOT_FOUND));

if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
throw new BaseException(ResponseCode.PASSWORD_NOT_MATCH);
}

return jwtTokenProvider.createJwtToken(user.getUsername());
}

public void logout(User user, RefreshTokenRequest refreshTokenRequest) {
RefreshToken refreshToken = refreshTokenRepository.findById(refreshTokenRequest.getRefreshToken()).orElseThrow(
() -> new BaseException(ResponseCode.REFRESH_TOKEN_NOT_FOUND)
);

refreshTokenRepository.delete(refreshToken);
SecurityContextHolder.clearContext();

}

public AccessTokenResponse reissueAccessToken(RefreshTokenRequest refreshTokenRequest) {

if(!jwtTokenProvider.validateToken(refreshTokenRequest.getRefreshToken())) {
throw new BaseException(ResponseCode.TOKEN_NOT_VALID);
}
if(!refreshTokenRepository.existsById(refreshTokenRequest.getRefreshToken())) {
throw new BaseException(ResponseCode.REFRESH_TOKEN_NOT_FOUND);
}

log.info("[reissueAccessToken] 액세스 토큰 재발급 시작");

RefreshToken refreshToken = refreshTokenRepository.findById(refreshTokenRequest.getRefreshToken()).orElseThrow(
() -> new BaseException(ResponseCode.REFRESH_TOKEN_NOT_FOUND)
);

User user = userRepository.findByUsername(refreshToken.getUsername()).orElseThrow(
() -> new BaseException(ResponseCode.USER_NOT_FOUND)
);

AccessTokenResponse accessTokenResponse = AccessTokenResponse.builder()
.accessToken(jwtTokenProvider.createAccessToken(user.getUsername(), new Date()))
.build();

log.info("[reissueAccessToken] 액세스 토큰 재발급 완료: {}", accessTokenResponse.getAccessToken());

return accessTokenResponse;

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.oven.server.api.user.service;

import com.oven.server.api.user.domain.RefreshToken;
import com.oven.server.api.user.repository.RefreshTokenRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
@RequiredArgsConstructor
@Slf4j
public class RefreshTokenService {

private final RefreshTokenRepository refreshTokenRepository;

public void saveRefreshToken(String token, String username) {
RefreshToken refreshToken = RefreshToken.builder()
.refreshToken(token)
.username(username)
.build();

refreshTokenRepository.save(refreshToken);

log.info("[로그인 후 Refresh Token 저장]");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.oven.server.api.user.service;

import com.oven.server.api.user.domain.User;
import com.oven.server.api.user.repository.UserRepository;
import com.oven.server.common.exception.BaseException;
import com.oven.server.common.response.ResponseCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {

private final UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws BaseException {
log.info("[UserDetailsServiceImpl] loadUserByUsername -> username: {}", username);

return userRepository.findByUsername(username)
.orElseThrow(() -> new BaseException(ResponseCode.USER_NOT_FOUND));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class PostRatingDto {

@Schema(description = "평점", example = "3")
private int rating;
private Integer rating;

}
Loading

0 comments on commit ff1b4fd

Please sign in to comment.