Modify 비밀키 환경변수로 숨김 처리

This commit is contained in:
Daeil Choi
2023-02-08 12:20:14 +09:00
parent 09f1e3d07f
commit eb7f9974a2
7 changed files with 68 additions and 28 deletions

View File

@@ -19,6 +19,7 @@ import com.example.springsecuritystudy.filter.StopwatchFilter;
import com.example.springsecuritystudy.jwt.JwtAuthenticationFilter; import com.example.springsecuritystudy.jwt.JwtAuthenticationFilter;
import com.example.springsecuritystudy.jwt.JwtAuthorizationFilter; import com.example.springsecuritystudy.jwt.JwtAuthorizationFilter;
import com.example.springsecuritystudy.jwt.JwtProperties; import com.example.springsecuritystudy.jwt.JwtProperties;
import com.example.springsecuritystudy.jwt.JwtUtils;
import com.example.springsecuritystudy.user.UserRepository; import com.example.springsecuritystudy.user.UserRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -31,6 +32,7 @@ import lombok.RequiredArgsConstructor;
public class SecurityConfig { public class SecurityConfig {
private final UserRepository userRepository; private final UserRepository userRepository;
private final JwtUtils jwtUtils;
@Bean @Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
@@ -47,10 +49,11 @@ public class SecurityConfig {
); );
// JWT filter // JWT filter
http.addFilterBefore( http.addFilterBefore(
new JwtAuthenticationFilter(authenticationManager(http.getSharedObject(AuthenticationConfiguration.class))), new JwtAuthenticationFilter(authenticationManager(http.getSharedObject(AuthenticationConfiguration.class))
, jwtUtils),
UsernamePasswordAuthenticationFilter.class UsernamePasswordAuthenticationFilter.class
).addFilterBefore( ).addFilterBefore(
new JwtAuthorizationFilter(userRepository), new JwtAuthorizationFilter(userRepository, jwtUtils),
BasicAuthenticationFilter.class BasicAuthenticationFilter.class
); );
http http

View File

@@ -19,9 +19,12 @@ import com.example.springsecuritystudy.user.User;
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final JwtUtils jwtUtils;
public JwtAuthenticationFilter( public JwtAuthenticationFilter(
AuthenticationManager authenticationManager) { AuthenticationManager authenticationManager, JwtUtils jwtUtils) {
super(authenticationManager); super(authenticationManager);
this.jwtUtils = jwtUtils;
} }
/** /**
@@ -46,7 +49,7 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException { Authentication authResult) throws IOException, ServletException {
User user = (User)authResult.getPrincipal(); User user = (User)authResult.getPrincipal();
String token = JwtUtils.createToken(user); String token = jwtUtils.createToken(user);
// 쿠키 생성 // 쿠키 생성
Cookie cookie = new Cookie(JwtProperties.COOKIE_NAME, token); Cookie cookie = new Cookie(JwtProperties.COOKIE_NAME, token);
cookie.setMaxAge(JwtProperties.EXPIRATION_TIME); // 쿠키 만료 시간 cookie.setMaxAge(JwtProperties.EXPIRATION_TIME); // 쿠키 만료 시간

View File

@@ -18,16 +18,17 @@ import com.example.springsecuritystudy.common.UserNotFoundException;
import com.example.springsecuritystudy.user.User; import com.example.springsecuritystudy.user.User;
import com.example.springsecuritystudy.user.UserRepository; import com.example.springsecuritystudy.user.UserRepository;
import lombok.RequiredArgsConstructor;
/** /**
* JWT를 이용한 인증 * JWT를 이용한 인증
*/ */
@RequiredArgsConstructor
public class JwtAuthorizationFilter extends OncePerRequestFilter { public class JwtAuthorizationFilter extends OncePerRequestFilter {
private final UserRepository userRepository; private final UserRepository userRepository;
private final JwtUtils jwtUtils;
public JwtAuthorizationFilter(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
@@ -55,7 +56,7 @@ public class JwtAuthorizationFilter extends OncePerRequestFilter {
} }
private Authentication getUsernamePasswordAuthenticationToken(String token) { private Authentication getUsernamePasswordAuthenticationToken(String token) {
String username = JwtUtils.getUsername(token); String username = jwtUtils.getUsername(token);
if (username != null) { if (username != null) {
User user = userRepository.findByUsername(username).orElseThrow(UserNotFoundException::new); User user = userRepository.findByUsername(username).orElseThrow(UserNotFoundException::new);
return new UsernamePasswordAuthenticationToken( return new UsernamePasswordAuthenticationToken(

View File

@@ -6,6 +6,11 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import javax.annotation.PostConstruct;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.Keys;
import javafx.util.Pair; import javafx.util.Pair;
@@ -13,27 +18,39 @@ import javafx.util.Pair;
* JWT Key를 제공하고 조회한다. * JWT Key를 제공하고 조회한다.
* Key Rolling을 지원한다. * Key Rolling을 지원한다.
*/ */
@Component
public class JwtKey { public class JwtKey {
/**
* Kid-Key List 외부로 절대 유출되어서는 안된다.
*/
private static final Map<String, String> SECRET_KEY_SET = new HashMap<String, String>() {
{
put("key1", "SpringSecurityJWTPracticeProjectIsSoGoodAndThisProjectIsSoFunSpringSecurityJWTPracticeProjectIsSoGoodAndThisProjectIsSoFun");
put("key2", "GoodSpringSecurityNiceSpringSecurityGoodSpringSecurityNiceSpringSecurityGoodSpringSecurityNiceSpringSecurityGoodSpringSecurityNiceSpringSecurity");
put("key3", "HelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurity");
}
};
private static final String[] KID_SET = SECRET_KEY_SET.keySet().toArray(new String[0]); private final Environment env;
private static Random randomIndex = new Random();
public JwtKey(Environment env) {
this.env = env;
}
private Map<String, String> SECRET_KEY_SET;
private String[] KID_SET;
private Random randomIndex;
@PostConstruct
public void init() {
SECRET_KEY_SET = new HashMap<String, String>() {
{
put("key1", env.getProperty("jwt.secret-key1"));
put("key2", env.getProperty("jwt.secret-key2"));
put("key3", env.getProperty("jwt.secret-key3"));
}
};
KID_SET = SECRET_KEY_SET.keySet().toArray(new String[0]);
randomIndex = new Random();
}
/** /**
* SECRET_KEY_SET 에서 랜덤한 KEY 가져오기 * SECRET_KEY_SET 에서 랜덤한 KEY 가져오기
* *
* @return kid와 key Pair * @return kid와 key Pair
*/ */
public static Pair<String, Key> getRandomKey() { public Pair<String, Key> getRandomKey() {
String kid = KID_SET[randomIndex.nextInt(KID_SET.length)]; String kid = KID_SET[randomIndex.nextInt(KID_SET.length)];
String secretKey = SECRET_KEY_SET.get(kid); String secretKey = SECRET_KEY_SET.get(kid);
return new Pair<>(kid, Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8))); return new Pair<>(kid, Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)));
@@ -45,7 +62,7 @@ public class JwtKey {
* @param kid kid * @param kid kid
* @return Key * @return Key
*/ */
public static Key getKey(String kid) { public Key getKey(String kid) {
String key = SECRET_KEY_SET.getOrDefault(kid, null); String key = SECRET_KEY_SET.getOrDefault(kid, null);
if (key == null) { if (key == null) {
return null; return null;

View File

@@ -3,24 +3,31 @@ package com.example.springsecuritystudy.jwt;
import java.security.Key; import java.security.Key;
import java.util.Date; import java.util.Date;
import org.springframework.stereotype.Component;
import com.example.springsecuritystudy.user.User; import com.example.springsecuritystudy.user.User;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import javafx.util.Pair; import javafx.util.Pair;
import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor
public class JwtUtils { public class JwtUtils {
private final JwtKey jwtKey;
/** /**
* 토큰에서 username 찾기 * 토큰에서 username 찾기
* *
* @param token 토큰 * @param token 토큰
* @return username * @return username
*/ */
public static String getUsername(String token) { public String getUsername(String token) {
// jwtToken에서 username을 찾는다. // jwtToken에서 username을 찾는다.
return Jwts.parserBuilder() return Jwts.parserBuilder()
.setSigningKeyResolver(SigningKeyResolver.instance) .setSigningKeyResolver(new SigningKeyResolver(jwtKey))
.build() .build()
.parseClaimsJws(token) .parseClaimsJws(token)
.getBody() .getBody()
@@ -36,10 +43,10 @@ public class JwtUtils {
* @param user 유저 * @param user 유저
* @return jwt token * @return jwt token
*/ */
public static String createToken(User user) { public String createToken(User user) {
Claims claims = Jwts.claims().setSubject(user.getUsername()); Claims claims = Jwts.claims().setSubject(user.getUsername());
Date now = new Date(); Date now = new Date();
Pair<String, Key> key = JwtKey.getRandomKey(); Pair<String, Key> key = jwtKey.getRandomKey();
return Jwts.builder() return Jwts.builder()
.setClaims(claims) // 토큰에 담을 정보 설정 .setClaims(claims) // 토큰에 담을 정보 설정
.setIssuedAt(now) // 토큰 발행 시간 설정 .setIssuedAt(now) // 토큰 발행 시간 설정

View File

@@ -5,18 +5,20 @@ import java.security.Key;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader; import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolverAdapter; import io.jsonwebtoken.SigningKeyResolverAdapter;
import lombok.RequiredArgsConstructor;
/** /**
* JwsHeader를 통해 Signature 검증에 필요한 Key를 가져오는 코드를 구현합니다. * JwsHeader를 통해 Signature 검증에 필요한 Key를 가져오는 코드를 구현합니다.
*/ */
@RequiredArgsConstructor
public class SigningKeyResolver extends SigningKeyResolverAdapter { public class SigningKeyResolver extends SigningKeyResolverAdapter {
public static SigningKeyResolver instance = new SigningKeyResolver(); private final JwtKey jwtKey;
@Override @Override
public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) { public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
String kid = jwsHeader.getKeyId(); String kid = jwsHeader.getKeyId();
if (kid == null) if (kid == null)
return null; return null;
return JwtKey.getKey(kid); return jwtKey.getKey(kid);
} }
} }

View File

@@ -13,3 +13,10 @@ spring:
jpa: jpa:
database-platform: org.hibernate.dialect.H2Dialect database-platform: org.hibernate.dialect.H2Dialect
show-sql: true show-sql: true
jwt:
secret-key1: SpringSecurityJWTPracticeProjectIsSoGoodAndThisProjectIsSoFunSpringSecurityJWTPracticeProjectIsSoGoodAndThisProjectIsSoFun
secret-key2: GoodSpringSecurityNiceSpringSecurityGoodSpringSecurityNiceSpringSecurityGoodSpringSecurityNiceSpringSecurityGoodSpringSecurityNiceSpringSecurity
secret-key3: HelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurityHelloSpringSecurity
expiration-time: 86400 #60 * 60 * 24
remember-me-expiration-time: 2592000 #60 * 60 * 24 * 30