diff --git a/src/main/java/com/example/springsecuritystudy/config/SecurityConfig.java b/src/main/java/com/example/springsecuritystudy/config/SecurityConfig.java index 2f8079e..16a56b9 100644 --- a/src/main/java/com/example/springsecuritystudy/config/SecurityConfig.java +++ b/src/main/java/com/example/springsecuritystudy/config/SecurityConfig.java @@ -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 diff --git a/src/main/java/com/example/springsecuritystudy/jwt/JwtAuthenticationFilter.java b/src/main/java/com/example/springsecuritystudy/jwt/JwtAuthenticationFilter.java index 5563a06..0e14653 100644 --- a/src/main/java/com/example/springsecuritystudy/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/springsecuritystudy/jwt/JwtAuthenticationFilter.java @@ -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); // 쿠키 만료 시간 diff --git a/src/main/java/com/example/springsecuritystudy/jwt/JwtAuthorizationFilter.java b/src/main/java/com/example/springsecuritystudy/jwt/JwtAuthorizationFilter.java index 60af9d5..4f7755c 100644 --- a/src/main/java/com/example/springsecuritystudy/jwt/JwtAuthorizationFilter.java +++ b/src/main/java/com/example/springsecuritystudy/jwt/JwtAuthorizationFilter.java @@ -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( diff --git a/src/main/java/com/example/springsecuritystudy/jwt/JwtKey.java b/src/main/java/com/example/springsecuritystudy/jwt/JwtKey.java index 8017137..8fd8ae6 100644 --- a/src/main/java/com/example/springsecuritystudy/jwt/JwtKey.java +++ b/src/main/java/com/example/springsecuritystudy/jwt/JwtKey.java @@ -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 SECRET_KEY_SET = new HashMap() { - { - 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 SECRET_KEY_SET; + private String[] KID_SET; + private Random randomIndex; + + + @PostConstruct + public void init() { + SECRET_KEY_SET = new HashMap() { + { + 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 getRandomKey() { + public Pair 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; diff --git a/src/main/java/com/example/springsecuritystudy/jwt/JwtUtils.java b/src/main/java/com/example/springsecuritystudy/jwt/JwtUtils.java index ab2839c..63e58be 100644 --- a/src/main/java/com/example/springsecuritystudy/jwt/JwtUtils.java +++ b/src/main/java/com/example/springsecuritystudy/jwt/JwtUtils.java @@ -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 key = JwtKey.getRandomKey(); + Pair key = jwtKey.getRandomKey(); return Jwts.builder() .setClaims(claims) // 토큰에 담을 정보 설정 .setIssuedAt(now) // 토큰 발행 시간 설정 diff --git a/src/main/java/com/example/springsecuritystudy/jwt/SigningKeyResolver.java b/src/main/java/com/example/springsecuritystudy/jwt/SigningKeyResolver.java index e0f510c..6195eec 100644 --- a/src/main/java/com/example/springsecuritystudy/jwt/SigningKeyResolver.java +++ b/src/main/java/com/example/springsecuritystudy/jwt/SigningKeyResolver.java @@ -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); } } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index e52a8f2..d387a1e 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -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