Modify 비밀키 환경변수로 숨김 처리
This commit is contained in:
@@ -19,6 +19,7 @@ import com.example.springsecuritystudy.filter.StopwatchFilter;
|
||||
import com.example.springsecuritystudy.jwt.JwtAuthenticationFilter;
|
||||
import com.example.springsecuritystudy.jwt.JwtAuthorizationFilter;
|
||||
import com.example.springsecuritystudy.jwt.JwtProperties;
|
||||
import com.example.springsecuritystudy.jwt.JwtUtils;
|
||||
import com.example.springsecuritystudy.user.UserRepository;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -31,6 +32,7 @@ import lombok.RequiredArgsConstructor;
|
||||
public class SecurityConfig {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final JwtUtils jwtUtils;
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
|
||||
@@ -47,10 +49,11 @@ public class SecurityConfig {
|
||||
);
|
||||
// JWT filter
|
||||
http.addFilterBefore(
|
||||
new JwtAuthenticationFilter(authenticationManager(http.getSharedObject(AuthenticationConfiguration.class))),
|
||||
new JwtAuthenticationFilter(authenticationManager(http.getSharedObject(AuthenticationConfiguration.class))
|
||||
, jwtUtils),
|
||||
UsernamePasswordAuthenticationFilter.class
|
||||
).addFilterBefore(
|
||||
new JwtAuthorizationFilter(userRepository),
|
||||
new JwtAuthorizationFilter(userRepository, jwtUtils),
|
||||
BasicAuthenticationFilter.class
|
||||
);
|
||||
http
|
||||
|
||||
@@ -19,9 +19,12 @@ import com.example.springsecuritystudy.user.User;
|
||||
|
||||
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
|
||||
|
||||
private final JwtUtils jwtUtils;
|
||||
|
||||
public JwtAuthenticationFilter(
|
||||
AuthenticationManager authenticationManager) {
|
||||
AuthenticationManager authenticationManager, JwtUtils jwtUtils) {
|
||||
super(authenticationManager);
|
||||
this.jwtUtils = jwtUtils;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -46,7 +49,7 @@ public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilte
|
||||
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
|
||||
Authentication authResult) throws IOException, ServletException {
|
||||
User user = (User)authResult.getPrincipal();
|
||||
String token = JwtUtils.createToken(user);
|
||||
String token = jwtUtils.createToken(user);
|
||||
// 쿠키 생성
|
||||
Cookie cookie = new Cookie(JwtProperties.COOKIE_NAME, token);
|
||||
cookie.setMaxAge(JwtProperties.EXPIRATION_TIME); // 쿠키 만료 시간
|
||||
|
||||
@@ -18,16 +18,17 @@ import com.example.springsecuritystudy.common.UserNotFoundException;
|
||||
import com.example.springsecuritystudy.user.User;
|
||||
import com.example.springsecuritystudy.user.UserRepository;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* JWT를 이용한 인증
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthorizationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final JwtUtils jwtUtils;
|
||||
|
||||
public JwtAuthorizationFilter(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||
@@ -55,7 +56,7 @@ public class JwtAuthorizationFilter extends OncePerRequestFilter {
|
||||
}
|
||||
|
||||
private Authentication getUsernamePasswordAuthenticationToken(String token) {
|
||||
String username = JwtUtils.getUsername(token);
|
||||
String username = jwtUtils.getUsername(token);
|
||||
if (username != null) {
|
||||
User user = userRepository.findByUsername(username).orElseThrow(UserNotFoundException::new);
|
||||
return new UsernamePasswordAuthenticationToken(
|
||||
|
||||
@@ -6,6 +6,11 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
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 javafx.util.Pair;
|
||||
|
||||
@@ -13,27 +18,39 @@ import javafx.util.Pair;
|
||||
* JWT Key를 제공하고 조회한다.
|
||||
* Key Rolling을 지원한다.
|
||||
*/
|
||||
@Component
|
||||
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 static Random randomIndex = new Random();
|
||||
private final Environment env;
|
||||
|
||||
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 가져오기
|
||||
*
|
||||
* @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 secretKey = SECRET_KEY_SET.get(kid);
|
||||
return new Pair<>(kid, Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)));
|
||||
@@ -45,7 +62,7 @@ public class JwtKey {
|
||||
* @param kid kid
|
||||
* @return Key
|
||||
*/
|
||||
public static Key getKey(String kid) {
|
||||
public Key getKey(String kid) {
|
||||
String key = SECRET_KEY_SET.getOrDefault(kid, null);
|
||||
if (key == null) {
|
||||
return null;
|
||||
|
||||
@@ -3,24 +3,31 @@ package com.example.springsecuritystudy.jwt;
|
||||
import java.security.Key;
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.example.springsecuritystudy.user.User;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import javafx.util.Pair;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtUtils {
|
||||
private final JwtKey jwtKey;
|
||||
|
||||
/**
|
||||
* 토큰에서 username 찾기
|
||||
*
|
||||
* @param token 토큰
|
||||
* @return username
|
||||
*/
|
||||
public static String getUsername(String token) {
|
||||
public String getUsername(String token) {
|
||||
// jwtToken에서 username을 찾는다.
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKeyResolver(SigningKeyResolver.instance)
|
||||
.setSigningKeyResolver(new SigningKeyResolver(jwtKey))
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
@@ -36,10 +43,10 @@ public class JwtUtils {
|
||||
* @param user 유저
|
||||
* @return jwt token
|
||||
*/
|
||||
public static String createToken(User user) {
|
||||
public String createToken(User user) {
|
||||
Claims claims = Jwts.claims().setSubject(user.getUsername());
|
||||
Date now = new Date();
|
||||
Pair<String, Key> key = JwtKey.getRandomKey();
|
||||
Pair<String, Key> key = jwtKey.getRandomKey();
|
||||
return Jwts.builder()
|
||||
.setClaims(claims) // 토큰에 담을 정보 설정
|
||||
.setIssuedAt(now) // 토큰 발행 시간 설정
|
||||
|
||||
@@ -5,18 +5,20 @@ import java.security.Key;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwsHeader;
|
||||
import io.jsonwebtoken.SigningKeyResolverAdapter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* JwsHeader를 통해 Signature 검증에 필요한 Key를 가져오는 코드를 구현합니다.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class SigningKeyResolver extends SigningKeyResolverAdapter {
|
||||
public static SigningKeyResolver instance = new SigningKeyResolver();
|
||||
private final JwtKey jwtKey;
|
||||
|
||||
@Override
|
||||
public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
|
||||
String kid = jwsHeader.getKeyId();
|
||||
if (kid == null)
|
||||
return null;
|
||||
return JwtKey.getKey(kid);
|
||||
return jwtKey.getKey(kid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,3 +13,10 @@ spring:
|
||||
jpa:
|
||||
database-platform: org.hibernate.dialect.H2Dialect
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user