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

feat : OAuth2.0 카카오 로그인 & JWT 인증 구현 #18

Merged
merged 23 commits into from
Jan 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
25e4eee
chore: OAuth2.0 의존성 추가
kkangh00n Dec 26, 2023
8281ed2
feat : Member 도메인 email 필드 추가 & 알림 여부 기본값 true로 초기화
kkangh00n Dec 26, 2023
81722d6
feat : 소셜 로그인 사용자 정보 조회
kkangh00n Dec 26, 2023
ab72dac
feat : 소셜 로그인 사용자 가입 여부 확인 후 저장
kkangh00n Dec 26, 2023
55a743c
feat : OAuth2.0 커스텀 로직 적용
kkangh00n Dec 26, 2023
099c460
chore : JWT 의존성 추가
kkangh00n Dec 26, 2023
ca90a5f
feat : JWT properties 객체
kkangh00n Dec 26, 2023
708f602
feat : 회원 로그인 시 JWT 토큰 발급
kkangh00n Dec 26, 2023
c72c081
feat : 로그인 완료 후 발급된 JWT 토큰을 JSON으로 전달
kkangh00n Dec 26, 2023
6843ab0
feat : RefreshToken 저장을 위한 테이블 생성
kkangh00n Dec 26, 2023
c7d91a4
feat : 로그인 시, RefreshToken 테이블에 저장
kkangh00n Dec 26, 2023
459dac8
feat : 인가를 위해 Member Role 필드 추가
kkangh00n Dec 26, 2023
efc32e1
feat : 요청 시, JWT 토큰을 이용한 사용자 인증 필터 구현
kkangh00n Dec 26, 2023
a97bccc
feat : JwtAuthenticationFilter 적용
kkangh00n Dec 27, 2023
63b6a88
fix : 누락 메서드 추가
kkangh00n Dec 27, 2023
d2e967f
style : 인덴트 컨벤션 수정
kkangh00n Dec 27, 2023
86ea017
refactor : 누락 접근 제어자 추가
kkangh00n Jan 1, 2024
e177ee5
chore : conflict resolve
kkangh00n Jan 1, 2024
8db399f
chore : conflict resolve
kkangh00n Jan 1, 2024
4b3136b
chore : conflict resolve
kkangh00n Jan 1, 2024
c00daa5
chore : conflict resolve
kkangh00n Jan 1, 2024
0495100
chore : conflict resolve
kkangh00n Jan 1, 2024
f7799fe
chore : conflict resolve
kkangh00n Jan 1, 2024
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
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ dependencies {
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc'

testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
}

tasks.named('test') {
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/com/prgrms/catchtable/common/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.prgrms.catchtable.common;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum Role {

MEMBER("ROLE_MEMBER"),
OWNER("ROLE_OWNER");

private final String role;
}
16 changes: 16 additions & 0 deletions src/main/java/com/prgrms/catchtable/jwt/config/JwtConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.prgrms.catchtable.jwt.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@Data
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {

private String clientSecret;
private int expiryMinute;
private int expiryMinuteRefresh;

}
35 changes: 35 additions & 0 deletions src/main/java/com/prgrms/catchtable/jwt/domain/RefreshToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.prgrms.catchtable.jwt.domain;

import static lombok.AccessLevel.PROTECTED;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = PROTECTED)
@Entity
public class RefreshToken {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(name = "token")
private String token;

@Column(name = "email")
private String email;

@Builder
public RefreshToken(String token, String email) {
this.token = token;
this.email = email;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.prgrms.catchtable.jwt.filter;

import com.prgrms.catchtable.jwt.domain.RefreshToken;
import com.prgrms.catchtable.jwt.provider.JwtTokenProvider;
import com.prgrms.catchtable.jwt.service.RefreshTokenService;
import com.prgrms.catchtable.jwt.token.Token;
import jakarta.servlet.FilterChain;
import jakarta.servlet.GenericFilter;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends GenericFilter {

private final JwtTokenProvider jwtTokenProvider;

private final RefreshTokenService refreshTokenService;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

String accessToken = ((HttpServletRequest) request).getHeader("AccessToken");
String refreshToken = ((HttpServletRequest) request).getHeader("RefreshToken");

if (accessToken != null) {
//AccessToken 유효
if (jwtTokenProvider.validateToken(accessToken)) {
setAuthentication(accessToken);
}
//RefreshToken 유효
else {
if (jwtTokenProvider.validateToken(refreshToken)) {
RefreshToken refreshTokenEntity = refreshTokenService.getRefreshTokenByToken(
refreshToken);
String email = refreshTokenEntity.getEmail();
Token newToken = jwtTokenProvider.createToken(email);

((HttpServletResponse) response).setHeader("AccessToken",
newToken.getAccessToken());
setAuthentication(newToken.getAccessToken());
} else {
throw new UsernameNotFoundException("Please Login again");
Comment on lines +52 to +53
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security 필터내에서 예외를 던지면 이 예외가 인식이 되나요??

Copy link
Collaborator Author

@kkangh00n kkangh00n Jan 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

예외 필터를 따로 구현 후, 앞단에 두어 처리해야합니다..!! 추가 후 리뷰요청 날리겠습니다~!

}
}
}
chain.doFilter(request, response);
}

private void setAuthentication(String accessToken) {
Authentication authentication = jwtTokenProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.prgrms.catchtable.jwt.provider;


import com.prgrms.catchtable.jwt.config.JwtConfig;
import com.prgrms.catchtable.jwt.service.JwtUserDetailsService;
import com.prgrms.catchtable.jwt.token.Token;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
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.stereotype.Component;

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

private final JwtConfig jwtConfig;

private final JwtUserDetailsService jwtUserDetailsService;

public Token createToken(String email) {

Claims claims = Jwts.claims().setSubject(email);
Date now = new Date();

String accessToken = createAccessToken(claims, now);
String refreshToken = createRefreshToken(claims, now);

return new Token(accessToken, refreshToken, email);
}

private String createAccessToken(Claims claims, Date now) {
long expiryMinute = jwtConfig.getExpiryMinute() * 1000L * 60;

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + expiryMinute))
.signWith(SignatureAlgorithm.HS256, jwtConfig.getClientSecret())
.compact();
}

private String createRefreshToken(Claims claims, Date now) {

long expiryMinuteRefresh = jwtConfig.getExpiryMinuteRefresh() * 1000L * 60;

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + expiryMinuteRefresh))
.signWith(SignatureAlgorithm.HS256, jwtConfig.getClientSecret())
.compact();
}

public boolean validateToken(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(jwtConfig.getClientSecret())
.build()
.parseClaimsJws(token)
.getBody();

return claims.getExpiration().after(new Date());
} catch (JwtException je) {
return false;
}
}

public Authentication getAuthentication(String token) {
String email = getEmail(token);
UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(email);
return new UsernamePasswordAuthenticationToken(userDetails, "",
userDetails.getAuthorities());
}

private String getEmail(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(jwtConfig.getClientSecret())
.build()
.parseClaimsJws(token)
.getBody();

return claims.getSubject();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.prgrms.catchtable.jwt.repository;

import com.prgrms.catchtable.jwt.domain.RefreshToken;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {

boolean existsRefreshTokenByEmail(String email);

void deleteRefreshTokenByEmail(String email);

Optional<RefreshToken> findRefreshTokenByToken(String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.prgrms.catchtable.jwt.service;

import com.prgrms.catchtable.member.repository.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 JwtUserDetailsService implements UserDetailsService {

private final MemberRepository memberRepository;

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return memberRepository.findMemberByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("Not Found Member"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.prgrms.catchtable.jwt.service;

import com.prgrms.catchtable.jwt.domain.RefreshToken;
import com.prgrms.catchtable.jwt.repository.RefreshTokenRepository;
import com.prgrms.catchtable.jwt.token.Token;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class RefreshTokenService {

private final RefreshTokenRepository refreshTokenRepository;

@Transactional
public void saveRefreshToken(Token totalToken) {
String email = totalToken.getEmail();

if (refreshTokenRepository.existsRefreshTokenByEmail(email)) {
refreshTokenRepository.deleteRefreshTokenByEmail(email);
}

RefreshToken newRefreshToken = RefreshToken.builder()
.token(totalToken.getRefreshToken())
.email(email)
.build();

refreshTokenRepository.save(newRefreshToken);
}

@Transactional(readOnly = true)
public RefreshToken getRefreshTokenByToken(String refreshToken) {
return refreshTokenRepository.findRefreshTokenByToken(refreshToken)
.orElseThrow(() -> new UsernameNotFoundException("Not Found RefreshToken"));
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/prgrms/catchtable/jwt/token/Token.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.prgrms.catchtable.jwt.token;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class Token {

private String accessToken;

private String refreshToken;

private String email;
}
Loading
Loading