From cd34b3d5415a30848c189551aa87a4c7ff277e84 Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:27:07 +0900 Subject: [PATCH 1/9] SCRUM-48 feat: Spring Security init --- .../com/kakaoteck/golagola/config/util/ApplicationConfig.java | 4 ++++ .../com/kakaoteck/golagola/config/util/SecurityConfig.java | 4 ++++ .../golagola/security/filter/JwtAuthenticationFilter.java | 4 ++++ .../com/kakaoteck/golagola/security/service/JwtService.java | 4 ++++ .../kakaoteck/golagola/security/service/LogoutService.java | 4 ++++ .../java/com/kakaoteck/golagola/security/token/Token.java | 4 ++++ .../com/kakaoteck/golagola/security/token/TokenBlackList.java | 4 ++++ .../golagola/security/token/TokenBlackListRepository.java | 4 ++++ .../kakaoteck/golagola/security/token/TokenRepository.java | 4 ++++ .../kakaoteck/golagola/security/token/enums/TokenType.java | 4 ++++ 10 files changed, 40 insertions(+) create mode 100644 src/main/java/com/kakaoteck/golagola/config/util/ApplicationConfig.java create mode 100644 src/main/java/com/kakaoteck/golagola/config/util/SecurityConfig.java create mode 100644 src/main/java/com/kakaoteck/golagola/security/filter/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/kakaoteck/golagola/security/service/JwtService.java create mode 100644 src/main/java/com/kakaoteck/golagola/security/service/LogoutService.java create mode 100644 src/main/java/com/kakaoteck/golagola/security/token/Token.java create mode 100644 src/main/java/com/kakaoteck/golagola/security/token/TokenBlackList.java create mode 100644 src/main/java/com/kakaoteck/golagola/security/token/TokenBlackListRepository.java create mode 100644 src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java create mode 100644 src/main/java/com/kakaoteck/golagola/security/token/enums/TokenType.java diff --git a/src/main/java/com/kakaoteck/golagola/config/util/ApplicationConfig.java b/src/main/java/com/kakaoteck/golagola/config/util/ApplicationConfig.java new file mode 100644 index 0000000..3d3be1c --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/config/util/ApplicationConfig.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.config.util; + +public class ApplicationConfig { +} diff --git a/src/main/java/com/kakaoteck/golagola/config/util/SecurityConfig.java b/src/main/java/com/kakaoteck/golagola/config/util/SecurityConfig.java new file mode 100644 index 0000000..7353458 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/config/util/SecurityConfig.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.config.util; + +public class SecurityConfig { +} diff --git a/src/main/java/com/kakaoteck/golagola/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/kakaoteck/golagola/security/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..b8b880a --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/security/filter/JwtAuthenticationFilter.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.security.filter; + +public class JwtAuthenticationFilter { +} diff --git a/src/main/java/com/kakaoteck/golagola/security/service/JwtService.java b/src/main/java/com/kakaoteck/golagola/security/service/JwtService.java new file mode 100644 index 0000000..fce3914 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/security/service/JwtService.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.security.service; + +public class JwtService { +} diff --git a/src/main/java/com/kakaoteck/golagola/security/service/LogoutService.java b/src/main/java/com/kakaoteck/golagola/security/service/LogoutService.java new file mode 100644 index 0000000..1a4baa0 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/security/service/LogoutService.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.security.service; + +public class LogoutService { +} diff --git a/src/main/java/com/kakaoteck/golagola/security/token/Token.java b/src/main/java/com/kakaoteck/golagola/security/token/Token.java new file mode 100644 index 0000000..eb72be8 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/security/token/Token.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.security.token; + +public class Token { +} diff --git a/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackList.java b/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackList.java new file mode 100644 index 0000000..667281d --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackList.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.security.token; + +public class TokenBlackList { +} diff --git a/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackListRepository.java b/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackListRepository.java new file mode 100644 index 0000000..b6732f3 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackListRepository.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.security.token; + +public interface TokenBlackListRepository { +} diff --git a/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java b/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java new file mode 100644 index 0000000..f3f5ac2 --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.security.token; + +public interface TokenRepository { +} diff --git a/src/main/java/com/kakaoteck/golagola/security/token/enums/TokenType.java b/src/main/java/com/kakaoteck/golagola/security/token/enums/TokenType.java new file mode 100644 index 0000000..ed36d5b --- /dev/null +++ b/src/main/java/com/kakaoteck/golagola/security/token/enums/TokenType.java @@ -0,0 +1,4 @@ +package com.kakaoteck.golagola.security.token.enums; + +public enum TokenType { +} From 896f5dad214fd3367592a1bd18a77a13998c9408 Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:28:13 +0900 Subject: [PATCH 2/9] SCRUM-48 feat: JwtService --- .../golagola/security/service/JwtService.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/src/main/java/com/kakaoteck/golagola/security/service/JwtService.java b/src/main/java/com/kakaoteck/golagola/security/service/JwtService.java index fce3914..304af1d 100644 --- a/src/main/java/com/kakaoteck/golagola/security/service/JwtService.java +++ b/src/main/java/com/kakaoteck/golagola/security/service/JwtService.java @@ -1,4 +1,91 @@ package com.kakaoteck.golagola.security.service; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.cglib.core.internal.Function; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Service; + +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +@Service public class JwtService { + + @Value("${spring.jwt.secret}") + private String secretKey; + @Value("${spring.jwt.token.access-expiration-time}") + private long jwtExpiration; + @Value("${spring.jwt.token.refresh-expiration-time}") + private long refreshExpiration; + + public String extractUserName(String token) { + return extractClaim(token, Claims::getSubject); + } + + public T extractClaim(String token, Function claimsResolver) { + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + public String generateToken(UserDetails userDetails) { + return generateToken(new HashMap<>(), userDetails); + } + + public String generateToken(Map extraClaims, UserDetails userDetails) { + return buildToken(extraClaims, userDetails, jwtExpiration); + } + + public boolean isTokenValid(String token, UserDetails userDetails) { + final String username = extractUserName(token); + return (username.equals(userDetails.getUsername())) && !isTokenExpired(token); + } + + private boolean isTokenExpired(String token) { + return extractExpiration(token).before(new Date()); + } + + public String generateRefreshToken(UserDetails userDetails) { + return buildToken(new HashMap<>(), userDetails, refreshExpiration); + } + + private String buildToken( + Map extraClaims, + UserDetails userDetails, + long expiration + ) { + return Jwts + .builder() + .setClaims(extraClaims) + .setSubject(userDetails.getUsername()) + .setIssuedAt(new Date(System.currentTimeMillis())) + .setExpiration(new Date(System.currentTimeMillis() + expiration)) + .signWith(getSignInKey(), SignatureAlgorithm.HS256) + .compact(); + } + + private Date extractExpiration(String token) { + return extractClaim(token, Claims::getExpiration); + } + + private Claims extractAllClaims(String token) { + return Jwts + .parserBuilder() + .setSigningKey(getSignInKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } + + private Key getSignInKey() { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + return Keys.hmacShaKeyFor(keyBytes); + } + } From 8d481c3b3f0ccadf11df369ece6783fde7b32ef8 Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:29:16 +0900 Subject: [PATCH 3/9] SCRUM-48 feat: TokenRepository --- .../security/token/TokenRepository.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java b/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java index f3f5ac2..b2a6094 100644 --- a/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java +++ b/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java @@ -1,4 +1,19 @@ package com.kakaoteck.golagola.security.token; -public interface TokenRepository { +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface TokenRepository extends JpaRepository { + + @Query(value = """ + select t from Token t inner join User u on t.user.userId = u.userId + where u.userId = :id and (t.expired = false or t.revoked = false) + """) + List findAllValidTokenByUser(@Param("id") Long id); + + Optional findByToken(String token); } From 75693fbf547ae5685bea5be1802092c6d2d2123a Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:29:35 +0900 Subject: [PATCH 4/9] SCRUM-48 feat: TokenType --- .../com/kakaoteck/golagola/security/token/enums/TokenType.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/kakaoteck/golagola/security/token/enums/TokenType.java b/src/main/java/com/kakaoteck/golagola/security/token/enums/TokenType.java index ed36d5b..1cfccda 100644 --- a/src/main/java/com/kakaoteck/golagola/security/token/enums/TokenType.java +++ b/src/main/java/com/kakaoteck/golagola/security/token/enums/TokenType.java @@ -1,4 +1,5 @@ package com.kakaoteck.golagola.security.token.enums; public enum TokenType { + BEARER } From 44cba9d44cc5c46a4279aa5eca7b154eeb1b04fc Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:30:48 +0900 Subject: [PATCH 5/9] =?UTF-8?q?SCRUM-48=20feat:=20Token=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../golagola/security/token/Token.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/main/java/com/kakaoteck/golagola/security/token/Token.java b/src/main/java/com/kakaoteck/golagola/security/token/Token.java index eb72be8..95a817a 100644 --- a/src/main/java/com/kakaoteck/golagola/security/token/Token.java +++ b/src/main/java/com/kakaoteck/golagola/security/token/Token.java @@ -1,4 +1,44 @@ package com.kakaoteck.golagola.security.token; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kakaoteck.golagola.domain.buyer.entity.Buyer; +import com.kakaoteck.golagola.domain.seller.entity.Seller; +import com.kakaoteck.golagola.security.token.enums.TokenType; +import jakarta.persistence.*; +import lombok.*; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "token") public class Token { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true) + private String token; + + @Enumerated(EnumType.STRING) + private TokenType tokenType = TokenType.BEARER; + + private boolean revoked; + + private boolean expired; + + @JsonIgnore + @ToString.Exclude + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "buyer_id") + private Buyer buyer; + + @JsonIgnore + @ToString.Exclude + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "seller_id") + private Seller seller; + } From ee57f646ff9de7e03c9feeeb3ad458a458f6679a Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:31:28 +0900 Subject: [PATCH 6/9] SCRUM-48 feat: TokenBlackListRepository --- .../golagola/security/token/TokenBlackListRepository.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackListRepository.java b/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackListRepository.java index b6732f3..474449b 100644 --- a/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackListRepository.java +++ b/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackListRepository.java @@ -1,4 +1,8 @@ package com.kakaoteck.golagola.security.token; -public interface TokenBlackListRepository { +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TokenBlackListRepository extends JpaRepository { + + boolean existsByToken(String token); } From bec4068fddcbb79c3cb84bc4dce58a8a0aa6af69 Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:31:57 +0900 Subject: [PATCH 7/9] =?UTF-8?q?SCRUM-48=20feat:=20TokenBlackList=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../security/token/TokenBlackList.java | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackList.java b/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackList.java index 667281d..e1aac0a 100644 --- a/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackList.java +++ b/src/main/java/com/kakaoteck/golagola/security/token/TokenBlackList.java @@ -1,4 +1,21 @@ package com.kakaoteck.golagola.security.token; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Entity +@Table(name = "token_blacklist") public class TokenBlackList { -} + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true) + private String token; + +} \ No newline at end of file From e017a9c088ac038df06f18852104539657f8ef9f Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:33:32 +0900 Subject: [PATCH 8/9] SCRUM-48 fix: TokenRepository --- .../golagola/security/token/TokenRepository.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java b/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java index b2a6094..6e9ad38 100644 --- a/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java +++ b/src/main/java/com/kakaoteck/golagola/security/token/TokenRepository.java @@ -10,10 +10,16 @@ public interface TokenRepository extends JpaRepository { @Query(value = """ - select t from Token t inner join User u on t.user.userId = u.userId - where u.userId = :id and (t.expired = false or t.revoked = false) + select t from Token t inner join Buyer u on t.buyer.buyerId = u.buyerId + where u.buyerId = :id and (t.expired = false or t.revoked = false) """) - List findAllValidTokenByUser(@Param("id") Long id); + List findAllValidTokenByBuyer(@Param("id") Long id); + + @Query(value = """ + select t from Token t inner join Seller u on t.seller.sellerId = u.sellerId + where u.sellerId = :id and (t.expired = false or t.revoked = false) + """) + List findAllValidTokenBySeller(@Param("id") Long id); Optional findByToken(String token); } From 0be13189833bf947330dbd29e63b67115367aa52 Mon Sep 17 00:00:00 2001 From: Kim Seung-yeop Date: Mon, 12 Aug 2024 18:36:16 +0900 Subject: [PATCH 9/9] =?UTF-8?q?SCRUM-48=20feat:=20filter=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../filter/JwtAuthenticationFilter.java | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/kakaoteck/golagola/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/kakaoteck/golagola/security/filter/JwtAuthenticationFilter.java index b8b880a..e155841 100644 --- a/src/main/java/com/kakaoteck/golagola/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/kakaoteck/golagola/security/filter/JwtAuthenticationFilter.java @@ -1,4 +1,69 @@ package com.kakaoteck.golagola.security.filter; -public class JwtAuthenticationFilter { +import com.kakaoteck.golagola.security.service.JwtService; +import com.kakaoteck.golagola.security.token.TokenRepository; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +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.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + private final JwtService jwtService; + private final UserDetailsService userDetailsService; + private final TokenRepository tokenRepository; + + @Override + protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { + + final String authHeader = request.getHeader("Authorization"); + final String jwt; + final String userEmail; + + if (request.getServletPath().contains("/api/v1/auth")) { + filterChain.doFilter(request, response); + return; + } + + if (authHeader == null || !authHeader.startsWith("Bearer")) { + filterChain.doFilter(request, response); + return; + } + + jwt = authHeader.substring(7); + userEmail = jwtService.extractUserName(jwt); + + if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail); + + var isTokenValid = tokenRepository.findByToken(jwt) + .map(t -> !t.isExpired() && !t.isRevoked()) + .orElse(false); + + if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) { + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + authToken.setDetails( + new WebAuthenticationDetailsSource().buildDetails(request) + ); + SecurityContextHolder.getContext().setAuthentication(authToken); + } + } + filterChain.doFilter(request, response); + } }