Skip to content

Commit

Permalink
Merge pull request #29 from Kakaotech-18-Ecommerce/SCRUM-48-Spring-Se…
Browse files Browse the repository at this point in the history
…curity-Configuration

[Feat] Spring Security 설정
  • Loading branch information
yeopyeop-82 authored Aug 12, 2024
2 parents faf5efc + 0be1318 commit 91e4bf7
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.kakaoteck.golagola.config.util;

public class ApplicationConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.kakaoteck.golagola.config.util;

public class SecurityConfig {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.kakaoteck.golagola.security.filter;

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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +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> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}

public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}

public String generateToken(Map<String, Object> 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<String, Object> 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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.kakaoteck.golagola.security.service;

public class LogoutService {
}
44 changes: 44 additions & 0 deletions src/main/java/com/kakaoteck/golagola/security/token/Token.java
Original file line number Diff line number Diff line change
@@ -0,0 +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;

}
Original file line number Diff line number Diff line change
@@ -0,0 +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;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.kakaoteck.golagola.security.token;

import org.springframework.data.jpa.repository.JpaRepository;

public interface TokenBlackListRepository extends JpaRepository<TokenBlackList, Long> {

boolean existsByToken(String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.kakaoteck.golagola.security.token;

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<Token, Long> {

@Query(value = """
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<Token> 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<Token> findAllValidTokenBySeller(@Param("id") Long id);

Optional<Token> findByToken(String token);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.kakaoteck.golagola.security.token.enums;

public enum TokenType {
BEARER
}

0 comments on commit 91e4bf7

Please sign in to comment.