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.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
|
||||||
|
|||||||
@@ -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); // 쿠키 만료 시간
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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) // 토큰 발행 시간 설정
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user