Skip to content

Commit

Permalink
Merge pull request #35 from Me1tingPot/feature/#29
Browse files Browse the repository at this point in the history
[Feature] νŒŒν‹° λ‚΄μš© 쑰회 κΈ°λŠ₯ κ΅¬ν˜„
  • Loading branch information
runasy-koonta authored May 26, 2024
2 parents 3f21cea + 9e807bd commit c8e7e42
Show file tree
Hide file tree
Showing 49 changed files with 1,151 additions and 52 deletions.
15 changes: 11 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id 'java'
id 'war'
id 'org.springframework.boot' version '3.2.5'
id 'io.spring.dependency-management' version '1.1.4'
}
Expand Down Expand Up @@ -27,17 +28,23 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'

// JWT
//implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'

implementation 'org.springframework.boot:spring-boot-starter-websocket'
// s3 κ΄€λ ¨ μ„€μ •
// implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
// implementation 'com.amazonaws:aws-java-sdk-s3:1.12.265'
// implementation 'software.amazon.ion:ion-java:1.0.3'developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'software.amazon.ion:ion-java:1.0.3'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
}

tasks.named('test') {
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/meltingpot/server/ServerApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,18 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class ServerApplication {
public class ServerApplication extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(ServerApplication.class);
}

public static void main(String[] args) {
SpringApplication.run(ServerApplication.class, args);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package meltingpot.server.auth.controller;

import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import meltingpot.server.auth.controller.dto.SigninRequestDto;
import meltingpot.server.auth.controller.dto.AccountResponseDto;
import meltingpot.server.util.ResponseCode;
import meltingpot.server.util.ResponseData;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.RequiredArgsConstructor;
import meltingpot.server.auth.service.AuthService;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
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;

@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("auth")
public class AuthController {

private final AuthService authService;
private final Logger logger = LoggerFactory.getLogger(this.getClass());

// νšŒμ› κ°€μž…


// 둜그인
@PostMapping("signin")
@Operation(summary="둜그인", description="둜그인 API μž…λ‹ˆλ‹€.")
public ResponseEntity<ResponseData<AccountResponseDto>> signin(
@RequestBody @Valid SigninRequestDto request
){
AccountResponseDto data = authService.signin(request.toServiceDto());
logger.info("SIGNIN_SUCCESS (200 OK) :: userId = {}, userEmail = {}",
data.getId(), data.getEmail());
return ResponseData.toResponseEntity(ResponseCode.SIGNIN_SUCCESS, data);
}


// λ‘œκ·Έμ•„μ›ƒ

// 이메일 인증

// λΉ„λ°€λ²ˆν˜Έ μž¬μ„€μ •

// 토큰 μž¬λ°œκΈ‰


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package meltingpot.server.auth.controller.dto;

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import meltingpot.server.domain.entity.Account;
import meltingpot.server.util.TokenDto;

@Getter
@Setter
@Builder
public class AccountResponseDto {
private final Long id;
private final String email;
private final String name;
private TokenDto tokenDto;

public static AccountResponseDto of(Account account) {
return AccountResponseDto.builder()
.id(account.getId())
.email(account.getUsername())
.name(account.getName())
.build();
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package meltingpot.server.auth.controller.dto;


import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import meltingpot.server.auth.service.dto.SigninServiceDto;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SigninRequestDto {

@NotBlank(message = "email is required")
private String email;

@NotBlank(message = "password is required")
private String password;

public SigninServiceDto toServiceDto() {
return SigninServiceDto.builder()
.username(getEmail())
.password(getPassword())
.build();
}
}
93 changes: 93 additions & 0 deletions src/main/java/meltingpot/server/auth/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package meltingpot.server.auth.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import meltingpot.server.exception.ResourceNotFoundException;
import meltingpot.server.config.TokenProvider;
import meltingpot.server.domain.entity.RefreshToken;
import meltingpot.server.domain.entity.Account;
import meltingpot.server.domain.repository.RefreshTokenRepository;
import meltingpot.server.domain.repository.AccountRepository;
import meltingpot.server.auth.controller.dto.AccountResponseDto;
import meltingpot.server.auth.service.dto.SigninServiceDto;
import meltingpot.server.util.AccountUser;
import meltingpot.server.util.ResponseCode;
import meltingpot.server.util.SecurityUtil;
import meltingpot.server.util.TokenDto;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@RequiredArgsConstructor
@Service
@EnableWebSecurity
public class AuthService implements UserDetailsService {
private final AccountRepository accountRepository;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final TokenProvider tokenProvider;
private final RefreshTokenRepository refreshTokenRepository;
//private final PasswordEncoder passwordEncoder;
private static final String BEARER_HEADER = "Bearer ";

// 둜그인 μœ μ € 정보 λ°˜ν™˜ to @CurrentUser
@Transactional(readOnly = true)
public Account getUserInfo(){
return accountRepository.findByUsernameAndDeletedIsNull(SecurityUtil.getCurrentUserName())
.orElseThrow(() -> new ResourceNotFoundException(ResponseCode.ACCOUNT_NOT_FOUND));
}

// λ‘œκ·ΈμΈμ‹œ μœ μ €μ •λ³΄ μ‘°νšŒν•˜λŠ” λ©”μ„œλ“œ override
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(username));
return new AccountUser(account);
}

// 둜그인
@Transactional(rollbackFor = Exception.class)
public AccountResponseDto signin(SigninServiceDto serviceDto){

// 1. Login ID/PW λ₯Ό 기반으둜 AuthenticationToken 생성 (미인증 토큰)
UsernamePasswordAuthenticationToken authenticationToken = serviceDto.toAuthentication();

// 2. 검증 (μ‚¬μš©μž λΉ„λ°€λ²ˆν˜Έ 체크) 이 μ΄λ£¨μ–΄μ§€λŠ” λΆ€λΆ„
// authenticate λ©”μ„œλ“œκ°€ 싀행이 될 λ•Œ loadUserByUsername λ©”μ„œλ“œκ°€ 싀행됨
Authentication authentication = authenticationManagerBuilder.getObject()
.authenticate(authenticationToken);

// 3. 인증 정보λ₯Ό 기반으둜 JWT 토큰 생성
TokenDto tokenDto = tokenProvider.generateTokenDto(authentication);

// 4. RefreshToken μ €μž₯
Account account = accountRepository.findByUsername(authentication.getName())
.orElseThrow(() -> new ResourceNotFoundException(ResponseCode.ACCOUNT_NOT_FOUND));
RefreshToken refreshToken = RefreshToken.builder()
.account(account)
.tokenValue(tokenDto.getRefreshToken())
.build();

refreshTokenRepository.save(refreshToken);

//인증된 Authenticationλ₯Ό SecurityContext에 μ €μž₯
SecurityContextHolder.getContext().setAuthentication(authentication);

// 5. 토큰 포함 ν˜„μž¬ μœ μ € 정보 λ°˜ν™˜
AccountResponseDto accountResponseDto = AccountResponseDto.of(getUserInfo());
accountResponseDto.setTokenDto(tokenDto);

return accountResponseDto;

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package meltingpot.server.auth.service.dto;

import lombok.Builder;
import lombok.Getter;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

@Getter
@Builder
public class SigninServiceDto {

private final String username;
private final String password;

// 미인증 토큰 생성
public UsernamePasswordAuthenticationToken toAuthentication() {
return new UsernamePasswordAuthenticationToken(getUsername(), getPassword());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class ChatRoomService {
private final ChatRoomUserRepository chatRoomUserRepository;

public void updateAlarmStatus(Long userId, Long chatRoomId) {
ChatRoomUser chatRoomUser = chatRoomUserRepository.findChatRoomUserByUserIdAndChatRoomId(userId, chatRoomId)
ChatRoomUser chatRoomUser = chatRoomUserRepository.findChatRoomUserByAccountIdAndChatRoomId(userId, chatRoomId)
.orElseThrow(() -> new IllegalArgumentException("ChatRoomUser not found"));
chatRoomUser.toggleAlarm();
chatRoomUserRepository.save(chatRoomUser);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package meltingpot.server.config;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class HttpLogoutSuccessHandler implements LogoutSuccessHandler {

@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
if (authentication == null) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
} else {
response.setStatus(HttpServletResponse.SC_OK);
}
}
}
21 changes: 21 additions & 0 deletions src/main/java/meltingpot/server/config/JwtAccessDeniedHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package meltingpot.server.config;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
// ν•„μš”ν•œ κΆŒν•œμ΄ 없이 μ ‘κ·Όν•˜λ € ν• λ•Œ 401
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package meltingpot.server.config;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import meltingpot.server.exception.UnknownAuthenticationException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
// μœ νš¨ν•œ 자격증λͺ…을 μ œκ³΅ν•˜μ§€ μ•Šκ³  μ ‘κ·Όν•˜λ € ν• λ•Œ 401
if (authException instanceof BadCredentialsException
|| authException instanceof InternalAuthenticationServiceException) {
throw new BadCredentialsException("μ΄λ©”μΌμ΄λ‚˜ λΉ„λ°€λ²ˆν˜Έκ°€ λ§žμ§€ μ•ŠμŠ΅λ‹ˆλ‹€");
} else if (authException instanceof DisabledException) {
throw new DisabledException("계정이 λΉ„ν™œμ„±ν™” λ˜μ—ˆμŠ΅λ‹ˆλ‹€");
} else if (authException instanceof CredentialsExpiredException) {
throw new CredentialsExpiredException("λΉ„λ°€λ²ˆν˜Έ μœ νš¨κΈ°κ°„μ΄ λ§Œλ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€");
} else {
throw new UnknownAuthenticationException("μ•Œ 수 μ—†λŠ” 이유둜 λ‘œκ·ΈμΈμ— μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€");
}
}
}
Loading

0 comments on commit c8e7e42

Please sign in to comment.