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] Spring Security 설정 #29

Merged
merged 9 commits into from
Aug 12, 2024
Merged
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
}