Skip to content

Commit

Permalink
시큐리티 설정 (#67)
Browse files Browse the repository at this point in the history
스프링 시큐리티 등 인가에 필요한 라이브러리 설정 #27

* Feat : 스프링 시큐리티 등 인가에 필요한 라이브러리 설정(#27)

- controller를  제외한 로그인 기능 구현

* Feat : 스프링 시큐리티 등 인가에 필요한 라이브러리 설정(#27)

- 시큐리티 컨픽 설정

스프링 시큐리티 등 인가에 필요한 라이브러리 설정 #27

* Feat : 스프링 시큐리티 등 인가에 필요한 라이브러리 설정(#27)

- 인메모리 유저 구현, 기타 무시 패턴 적용

스프링 시큐리티 등 인가에 필요한 라이브러리 설정 #27

* Fix : 스프링 시큐리티 등 인가에 필요한 라이브러리 설정(#27)

- 코드 리뷰 피드백 적용

스프링 시큐리티 등 인가에 필요한 라이브러리 설정 #27

---------

Co-authored-by: Dongseok Kang <[email protected]>
  • Loading branch information
KimuYounguWoo and wcorn authored Aug 2, 2024
1 parent ddd0b0e commit f35aaa8
Show file tree
Hide file tree
Showing 21 changed files with 597 additions and 9 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ out/
/nbdist/
/.nb-gradle/

### QODANA ###
qodana.yaml

### VS Code ###
.vscode/

Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ dependencies {
// JWT
implementation 'io.jsonwebtoken:jjwt:0.12.6'

// ModelMapper
implementation 'org.modelmapper:modelmapper:3.2.1'

//vault
implementation 'org.springframework.cloud:spring-cloud-starter-vault-config'

Expand Down
31 changes: 31 additions & 0 deletions qodana.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#-------------------------------------------------------------------------------#
# Qodana analysis is configured by qodana.yaml file #
# https://www.jetbrains.com/help/qodana/qodana-yaml.html #
#-------------------------------------------------------------------------------#
version: "1.0"

#Specify inspection profile for code analysis
profile:
name: qodana.starter

#Enable inspections
#include:
# - name: <SomeEnabledInspectionId>

#Disable inspections
#exclude:
# - name: <SomeDisabledInspectionId>
# paths:
# - <path/where/not/run/inspection>

projectJDK: 17 #(Applied in CI/CD pipeline)

#Execute shell command before Qodana execution (Applied in CI/CD pipeline)
#bootstrap: sh ./prepare-qodana.sh

#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline)
#plugins:
# - id: <plugin.id> #(plugin id can be found at https://plugins.jetbrains.com)

#Specify Qodana linter for analysis (Applied in CI/CD pipeline)
linter: jetbrains/qodana-jvm:latest
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package kea.enter.enterbe.api.auth.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import kea.enter.enterbe.api.auth.dto.LoginRequestDto;
import kea.enter.enterbe.api.auth.dto.TokenDao;
import kea.enter.enterbe.api.auth.service.AuthService;
import kea.enter.enterbe.global.common.exception.CustomException;
import kea.enter.enterbe.global.common.exception.ResponseCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.parser.Authorization;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "인증 및 인가 컨트롤러", description = "인증 및 인가 컨트롤러")
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
@Slf4j
public class AuthController {

private final AuthService authService;

@PostMapping("/login")
public ResponseEntity<TokenDao> login(LoginRequestDto loginRequestDto, Authorization authorization) {
if (authorization != null) {
// 이미 로그인한 상태
throw new CustomException(ResponseCode.ALREADY_LOGGED_IN);
}
return ResponseEntity.ok(authService.login(loginRequestDto));
}

}
29 changes: 29 additions & 0 deletions src/main/java/kea/enter/enterbe/api/auth/dto/LoginRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kea.enter.enterbe.api.auth.dto;


import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Builder;
import lombok.Getter;

@Getter
public class LoginRequestDto {
@NotBlank
@Email
private final String email;

@NotBlank
private final String password;

@Builder
public LoginRequestDto(
String email, String password
) {
this.email = email;
this.password = password;
}

public static LoginRequestDto of(String email, String password) {
return LoginRequestDto.builder().email(email).password(password).build();
}
}
32 changes: 32 additions & 0 deletions src/main/java/kea/enter/enterbe/api/auth/dto/MemberInfoDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package kea.enter.enterbe.api.auth.dto;

import kea.enter.enterbe.domain.member.entity.MemberRole;
import lombok.Builder;
import lombok.Getter;

@Getter
public class MemberInfoDto {
private Long memberId;
private String email;
private String name;
private String password;
private MemberRole role;


@Builder
public MemberInfoDto(
Long memberId, String email, String name, String password, MemberRole role
) {
this.memberId = memberId;
this.email = email;
this.name = name;
this.password = password;
this.role = role;
}

public static MemberInfoDto of(
Long memberId, String email, String name, String password, MemberRole role
) {
return MemberInfoDto.builder().memberId(memberId).email(email).name(name).password(password).role(role).build();
}
}
29 changes: 29 additions & 0 deletions src/main/java/kea/enter/enterbe/api/auth/dto/TokenDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package kea.enter.enterbe.api.auth.dto;

import jakarta.validation.constraints.NotNull;
import lombok.Builder;
import lombok.Getter;

@Getter
public class TokenDao {
@NotNull
String accessToken;

@NotNull
String refreshToken;

@Builder
public TokenDao(
String accessToken, String refreshToken
) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}

public static TokenDao of(
String accessToken, String refreshToken
) {
return TokenDao.builder().accessToken(accessToken).refreshToken(refreshToken).build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package kea.enter.enterbe.api.auth.service;

import kea.enter.enterbe.api.auth.dto.LoginRequestDto;
import kea.enter.enterbe.api.auth.dto.TokenDao;

public interface AuthService {
TokenDao login(LoginRequestDto loginRequestDto);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package kea.enter.enterbe.api.auth.service;

import kea.enter.enterbe.api.auth.dto.LoginRequestDto;
import kea.enter.enterbe.api.auth.dto.MemberInfoDto;
import kea.enter.enterbe.api.auth.dto.TokenDao;
import kea.enter.enterbe.domain.member.entity.Member;
import kea.enter.enterbe.domain.member.repository.MemberRepository;
import kea.enter.enterbe.global.common.exception.CustomException;
import kea.enter.enterbe.global.common.exception.ResponseCode;
import kea.enter.enterbe.global.security.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthServiceImpl implements AuthService {

private final JwtUtil jwtUtil;
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final ModelMapper modelMapper;

@Override
public TokenDao login(LoginRequestDto loginRequestDto) {
String email = loginRequestDto.getEmail();
String password = loginRequestDto.getPassword();
Member member = memberRepository.findMemberByEmail(email).orElseThrow(() -> new CustomException(
ResponseCode.MEMBER_NOT_FOUND));

if (!passwordEncoder.matches(password, member.getPassword())) {
throw new CustomException(ResponseCode.PASSWORD_INCORRECT);
}

return TokenDao.of(
jwtUtil.createAccessToken(modelMapper.map(member, MemberInfoDto.class)),
jwtUtil.createRefreshToken(modelMapper.map(member, MemberInfoDto.class))
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package kea.enter.enterbe.api.auth.service;


import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import kea.enter.enterbe.api.auth.dto.MemberInfoDto;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Getter
@RequiredArgsConstructor
public class CustomUserDetails implements UserDetails {

private final MemberInfoDto member;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<String> roles = new ArrayList<>();
roles.add("ROLE_" + member.getRole().toString());

return roles.stream()
.map(SimpleGrantedAuthority::new)
.toList();
}

@Override
public String getPassword() {
return member.getPassword();
}

@Override
public String getUsername() {
return member.getMemberId().toString();
}


@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,29 @@
package kea.enter.enterbe.api.auth.service;

import kea.enter.enterbe.api.auth.dto.MemberInfoDto;
import kea.enter.enterbe.domain.member.entity.Member;
import kea.enter.enterbe.domain.member.entity.MemberState;
import kea.enter.enterbe.domain.member.repository.MemberRepository;
import kea.enter.enterbe.global.common.exception.CustomException;
import kea.enter.enterbe.global.common.exception.ResponseCode;
import lombok.RequiredArgsConstructor;
import org.modelmapper.ModelMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final MemberRepository memberRepository;
private final ModelMapper mapper = new ModelMapper();

@Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
Member member = memberRepository.findByIdAndState(Long.parseLong(id), MemberState.ACTIVE)
.orElseThrow(() -> new CustomException(ResponseCode.MEMBER_NOT_FOUND));

return new CustomUserDetails(mapper.map(member, MemberInfoDto.class));
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package kea.enter.enterbe.domain.member.repository;

import java.util.List;
import java.util.Optional;
import kea.enter.enterbe.domain.member.entity.Member;
import kea.enter.enterbe.domain.member.entity.MemberState;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;

@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByIdAndState(Long id, MemberState state);
Optional<Member> findMemberByEmail(String email);
Optional<List<Member>> findAllByState(MemberState state);
}

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ public enum ResponseCode {
// PENALTY
PENALTY_NOT_FOUND("PEN-ERR-001", HttpStatus.NOT_FOUND, "페널티를 찾을 수 없습니다."),

// AUTh
PASSWORD_INCORRECT("AUT-ERR-001", HttpStatus.UNAUTHORIZED, "비밀번호가 틀렸습니다"),
ALREADY_LOGGED_IN("AUT-ERR-002", HttpStatus.BAD_REQUEST, "이미 로그인 되어있습니다."),
NOT_FOUND_USER("AUT-ERR-003", HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다."),

//GLOBAL
BAD_REQUEST("GLB-ERR-001", HttpStatus.NOT_FOUND, "잘못된 요청입니다."),
METHOD_NOT_ALLOWED("GLB-ERR-002", HttpStatus.METHOD_NOT_ALLOWED, "허용되지 않은 메서드입니다."),
Expand Down
Loading

0 comments on commit f35aaa8

Please sign in to comment.