-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: jwt 라이브러리 추가 * fix: merge 과정에서 충돌 이슈 제거 * feat: 토큰 생성, 검증을 하는 TokenProvider 클래스 생성 * feat: SecurityConfig 클래스 추가 * feat: Jwt Filter 적용 * feat: UserDetailService 추가 및 Member에 메서드 추가 * feat: 로그인, 회원가입 시 필요한 dto 클래스 생성 * feat: 유저 회원가입 기능 구현 * feat: 유저 로그인 기능 구현 * feat: 공통 Exception 클래스들 추가 * refactor: LoginSuccessDto 추가 * feat: LoginSuccessDto 추가 * feat: 회원 정보 조회 기능 추가 * feat: 회원 정보 조회 기능 추가 * fix: securityConfig에서 posts url 허용 * feat: 회원 전체 목록 기능 구현 * feat: ErrorCode 적용 * feat: 단위 테스트 작성 * fix: postCount, postLike 값이 null이 들어가는 오류 수정 * refactor: 불필요한 코드 제거 * feat: 회원 로그인, 조회 예외 처리 * fix: signWith() deprecated 수정 * fix: errorCode 중복 네이밍 제거
- Loading branch information
Showing
18 changed files
with
560 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
src/main/java/balancetalk/global/config/MyUserDetailService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package balancetalk.global.config; | ||
|
||
import balancetalk.module.member.domain.MemberRepository; | ||
import lombok.RequiredArgsConstructor; | ||
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 MyUserDetailService implements UserDetailsService { | ||
|
||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { | ||
return memberRepository.findByEmail(username) | ||
.orElseThrow(() -> new UsernameNotFoundException("회원을 찾을 수 없습니다.")); | ||
|
||
} | ||
} |
44 changes: 44 additions & 0 deletions
44
src/main/java/balancetalk/global/config/SecurityConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package balancetalk.global.config; | ||
|
||
import balancetalk.global.jwt.JwtAuthenticationFilter; | ||
import balancetalk.global.jwt.JwtTokenProvider; | ||
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.http.SessionCreationPolicy; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.security.web.SecurityFilterChain; | ||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; | ||
|
||
@Configuration | ||
@RequiredArgsConstructor | ||
@EnableWebSecurity | ||
public class SecurityConfig { | ||
|
||
private final JwtTokenProvider jwtTokenProvider; | ||
|
||
@Bean | ||
public BCryptPasswordEncoder passwordEncoder() { | ||
return new BCryptPasswordEncoder(); | ||
} | ||
|
||
@Bean | ||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { | ||
http | ||
.csrf(csrf -> csrf.disable()) | ||
.cors(cors -> cors.disable()) | ||
// h2 콘솔 사용 | ||
.headers(header -> header.frameOptions(frameOption -> frameOption.disable()).disable()) | ||
// 세션 사용 X (jwt 사용) | ||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) | ||
.authorizeHttpRequests(request -> request | ||
.requestMatchers("/posts/**", "/members/**", "/h2-console/**").permitAll() | ||
.anyRequest().authenticated() | ||
) | ||
// jwtFilter 먼저 적용 | ||
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); | ||
return http.build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
34 changes: 34 additions & 0 deletions
34
src/main/java/balancetalk/global/jwt/JwtAuthenticationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package balancetalk.global.jwt; | ||
|
||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.ServletRequest; | ||
import jakarta.servlet.ServletResponse; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.web.filter.GenericFilterBean; | ||
|
||
import java.io.IOException; | ||
|
||
@RequiredArgsConstructor | ||
public class JwtAuthenticationFilter extends GenericFilterBean { | ||
|
||
private final JwtTokenProvider jwtTokenProvider; | ||
|
||
@Override | ||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | ||
String token = jwtTokenProvider.resolveToken((HttpServletRequest) request); | ||
|
||
// 토큰이 유효할 때 | ||
if (token != null && jwtTokenProvider.validateToken(token)) { | ||
// 토큰으로부터 유저 정보를 받는다 | ||
Authentication authentication = jwtTokenProvider.getAuthentication(token); | ||
// SecurityContext에 객체 저장 | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} | ||
// 다음 필터로 진행 | ||
chain.doFilter(request, response); | ||
} | ||
} |
61 changes: 61 additions & 0 deletions
61
src/main/java/balancetalk/global/jwt/JwtTokenProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package balancetalk.global.jwt; | ||
|
||
import balancetalk.module.member.domain.Role; | ||
import io.jsonwebtoken.*; | ||
import io.jsonwebtoken.security.Keys; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
import org.springframework.security.core.userdetails.UserDetailsService; | ||
import org.springframework.stereotype.Component; | ||
import java.security.Key; | ||
import java.util.Date; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtTokenProvider { | ||
|
||
private Long tokenValidTime = 30 * 60 * 1000L; // 30분 유효 시간 | ||
private final UserDetailsService userDetailsService; | ||
private final Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS512); | ||
|
||
public String createToken(String email, Role role) { | ||
Claims claims = Jwts.claims().setSubject(email); // JWT payload에 저장되는 정보 단위 | ||
claims.put("role" , role); | ||
Date now = new Date(); | ||
return Jwts.builder() | ||
.setClaims(claims) // 정보 저장 | ||
.setIssuedAt(now) // 토큰 발행 시간 | ||
.setExpiration(new Date(now.getTime() + tokenValidTime)) // 30분 유효시간 설정 | ||
.signWith(secretKey) // 암호화 알고리즘과 secretKey | ||
.compact(); | ||
} | ||
|
||
// 인증 정보 조회 | ||
public Authentication getAuthentication(String token) { | ||
UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserEmail(token)); | ||
return new UsernamePasswordAuthenticationToken(userDetails, userDetails.getAuthorities()); | ||
} | ||
|
||
// 토큰에서 회원 정보 추출 | ||
public String getUserEmail(String token) { | ||
return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody().getSubject(); | ||
} | ||
|
||
// 토큰 유효성, 만료일자 확인 | ||
public boolean validateToken(String jwtToken) { | ||
try { | ||
Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(jwtToken); | ||
return !claims.getBody().getExpiration().before(new Date()); // 만료시간 이전이면 true 반환 | ||
} catch (Exception e) { | ||
return false; // 만료시간 이후라면 false 반환 | ||
} | ||
} | ||
|
||
// request Header에서 토큰 값 가져오기 | ||
public String resolveToken(HttpServletRequest request) { | ||
return request.getHeader("X-AUTH-TOKEN"); | ||
} | ||
} |
65 changes: 65 additions & 0 deletions
65
src/main/java/balancetalk/module/member/application/MemberService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
package balancetalk.module.member.application; | ||
|
||
import balancetalk.global.exception.BalanceTalkException; | ||
import balancetalk.global.exception.ErrorCode; | ||
import balancetalk.global.jwt.JwtTokenProvider; | ||
import balancetalk.module.member.domain.Member; | ||
import balancetalk.module.member.domain.MemberRepository; | ||
import balancetalk.module.member.dto.JoinDto; | ||
import balancetalk.module.member.dto.LoginDto; | ||
import balancetalk.module.member.dto.LoginSuccessDto; | ||
import balancetalk.module.member.dto.MemberResponseDto; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
@Service | ||
@RequiredArgsConstructor | ||
public class MemberService { | ||
|
||
private final JwtTokenProvider jwtTokenProvider; | ||
private final MemberRepository memberRepository; | ||
|
||
@Transactional | ||
public Long join(final JoinDto joinDto) { | ||
Member member = joinDto.toEntity(); | ||
return memberRepository.save(member).getId(); | ||
} | ||
|
||
@Transactional | ||
public LoginSuccessDto login(final LoginDto loginDto) { | ||
Member member = memberRepository.findByEmail(loginDto.getEmail()) | ||
.orElseThrow(() -> new BalanceTalkException(ErrorCode.MISMATCHED_EMAIL_OR_PASSWORD)); | ||
if (!member.getPassword().equals(loginDto.getPassword())) { | ||
throw new BalanceTalkException(ErrorCode.MISMATCHED_EMAIL_OR_PASSWORD); | ||
} | ||
String token = jwtTokenProvider.createToken(member.getEmail(), member.getRole()); | ||
|
||
if (token == null) { | ||
throw new BalanceTalkException(ErrorCode.AUTHENTICATION_ERROR); | ||
} | ||
|
||
return LoginSuccessDto.builder() | ||
.email(member.getEmail()) | ||
.password(member.getPassword()) | ||
.role(member.getRole()) | ||
.token(token) | ||
.build(); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public MemberResponseDto findById(Long id) { | ||
Member member = memberRepository.findById(id) | ||
.orElseThrow(() -> new BalanceTalkException(ErrorCode.NOT_FOUND_MEMBER)); | ||
return MemberResponseDto.fromEntity(member); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public List<MemberResponseDto> findAll() { | ||
List<Member> members = memberRepository.findAll(); | ||
return members.stream() | ||
.map(MemberResponseDto::fromEntity) | ||
.collect(Collectors.toList()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 2 additions & 1 deletion
3
src/main/java/balancetalk/module/member/domain/MemberRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package balancetalk.module.member.domain; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.Optional; | ||
public interface MemberRepository extends JpaRepository<Member, Long> { | ||
Optional<Member> findByEmail(String username); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package balancetalk.module.member.dto; | ||
|
||
import balancetalk.module.member.domain.Member; | ||
import balancetalk.module.member.domain.Role; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Data; | ||
import lombok.NoArgsConstructor; | ||
@Data | ||
@Builder | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class JoinDto { | ||
private String nickname; | ||
private String email; | ||
private String password; | ||
private Role role; | ||
private String ip; | ||
// TODO: profilePhoto 추가 | ||
|
||
public Member toEntity() { | ||
return Member.builder() | ||
.nickname(nickname) | ||
.email(email) | ||
.password(password) | ||
.role(role) | ||
.ip(ip) | ||
.build(); | ||
} | ||
} |
Oops, something went wrong.