Framework/Spring Framework

Spring Boot 3.X migration ์ด์Šˆ (feat. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ, JWT ๋ณ€๊ฒฝ์ )

ํ”„๋กœ๊ทธ๋ž˜๋จธ ์˜ค์›” 2023. 11. 9.

์Šคํ”„๋ง ๋ถ€ํŠธ 2  ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€ 3๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•˜๊ฒŒ ๋˜๋ฉด ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ด์Šˆ๊ฐ€ ์ƒ๊ธฐ๊ฒŒ ๋œ๋‹ค. ํŠนํžˆ๋‚˜ JDK17 ๊นŒ์ง€ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด์„œ ๊ธฐ์กด์— ์žˆ๋˜ ํŒจํ‚ค์ง€ ๊ฒฝ๋กœ๋ฅผ ๋ชป ์“ฐ๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ๋„ ๋งŽ๋‹ค.
์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฌธ์ œ์ ์— ๋Œ€ํ•ด์„œ ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ–ˆ๋Š”์ง€ ๊ธฐ๋ก์œผ๋กœ ๋‚จ๊ฒจ ๋†“์•„์•ผ๊ฒ ๋‹ค.

 

  ๊ธฐ์กด  ๋ณ€๊ฒฝ ํ›„
์Šคํ”„๋ง ๋ถ€ํŠธ ๋ฒ„์ „ 2.7.17 3.0.11
์ž๋ฐ” ๋ฒ„์ „ 8 17
JWT ๋ฒ„์ „ 0.9.1 0.11.5

 

Spring Security ๋‘˜ ๋ชจ๋‘ 6๋ฒ„์ „(6:3.1.1.RELEASE) ์„ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

thymeleaf์—์„œ Spring Security๋ฅผ ์“ฐ๋ ค๋ฉด springsecurity6๋ฅผ ์จ์•ผ ํ•œ๋‹ค.

 

 

๐Ÿ˜ build.gradle

๊ธฐ์กด ์˜์กด์„ฑ :

 

 

๋ณ€๊ฒฝ ํ›„

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

	implementation 'org.springframework.boot:spring-boot-starter-security'
//  Temporary explicit version to fix Thymeleaf bug
	implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:3.1.1.RELEASE'

	implementation 'io.jsonwebtoken:jjwt:0.9.1'

	implementation 'org.springframework.security:spring-security-test'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

 

 

 

WebSecurityConfig

 

๋ณ€๊ฒฝ ์ „

 

	@Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http)
        throws Exception {
        http.authorizeHttpRequests()
            .requestMatchers("/", "/home", "/join", "/login")
            .permitAll()
            .anyRequest().authenticated()
            .and()
            .formLogin().disable()
            .csrf().disable()
            .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider()),
                UsernamePasswordAuthenticationFilter.class);

        return http.getOrBuild();
    }

 

๋ณ€๊ฒฝ ํ›„ .authorizeRequests๊ฐ€ .authorizeHttpRequests๋กœ ๋ฐ”๋€Œ์—ˆ๊ณ   .antMatchers() ๋ฉ”์†Œ๋“œ๊ฐ€ ์‚ฌ๋ผ์ง€๊ณ  .requestMatchers() ๋กœ ๋ณ€๊ฒฝ ๋˜์—ˆ๋‹ค.

.build()๋Œ€์‹  .getOrBuild() ๋ฅผ ์‚ฌ์šฉ

 

 

 

javax ํŒจํ‚ค์ง€

JDK17๋กœ ๋ณ€๊ฒฝํ•˜์˜€๊ธฐ ๋•Œ๋ฌธ์— javax๊ฐ€ ์‚ฌ๋ผ์ง€๊ณ  ๊ธฐ๋ณธ์œผ๋กœ jakarta ํŒจํ‚ค์ง€๋กœ ๋ณ€๊ฒฝํ•ด์ฃผ์–ด์•ผํ•ฉ๋‹ˆ๋‹ค.

๋ณ€๊ฒฝ ์ „

 

๋ณ€๊ฒฝ ํ›„

 

 

JWT ๋ฐœ๊ธ‰

 

๋ณ€๊ฒฝ ์ „

 

 

public class JwtTokenProvider {

    private String secretKey = "sleep+mbti";
    private long tokenValidTime = 30* 60 * 1000L; // 30min

    private Key key;

    @Autowired
    private UserDetailsService userDetailsService;

//    @PostConstruct
//    public void init() {
//        secretKey = Base64.getEncoder()
//            .encodeToString(secretKey.getBytes());
//    }
    @PostConstruct
    public void init() {
        String base64EncodedSecretKey = Base64.getEncoder().encodeToString(secretKey.getBytes());
        byte[] keyBytes = Decoders.BASE64.decode(base64EncodedSecretKey);
        this.key = Keys.hmacShaKeyFor(keyBytes);
    }

    public String createToken(String username, Collection<? extends GrantedAuthority> roles) {
        Claims claims = Jwts.claims().setSubject(username);
        claims.put("roles", roles);

        Date now = new Date();

        return Jwts.builder()
            .setClaims(claims)
            .setIssuedAt(now)
            .setExpiration(new Date(now.getTime() + tokenValidTime))
            .signWith(key, SignatureAlgorithm.HS256)
            //.signWith(secretKey, SignatureAlgorithm.HS256)
            .compact();
    }
}

 

 

jjwt 0.11.5๋กœ ๋ฐ”๋€Œ๋ฉด์„œ .signWitH() ๋ฉ”์†Œ๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค. ๊ธฐ์กด์—๋Š” ์•Œ๊ณ ๋ฆฌ์ฆ˜๊ณผ ์ธ์ฝ”๋”ฉ๋˜์–ด ๋ฌธ์ž์—ดํ™” ์‹œํ‚จ, ๋น„๋ฐ€ํ‚ค ๋ฌธ์ž์—ด์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋„ฃ์–ด์ฃผ์—ˆ๋Š”๋ฐ, 0.11.5 ๋ฒ„์ „์—์„  ์ธ์ฝ”๋”ฉ๋œ Key ๊ฐ์ฒด์™€ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ˆœ์„œ๋กœ ๋„ฃ์–ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

Key๊ฐ์ฒด์™€ ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆœ์„œ ๋ณ€๊ฒฝ์ ์ด ์ƒ๊ฒผ๋‹ค.

 

ํ•˜์ง€๋งŒ ๋ณ€๊ฒฝ ํ›„์—๋„ ์‹คํ–‰ํ•˜๋ฉด ์ด๋Ÿฐ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์˜ฌ ๊ฒƒ์ด๋‹ค.

 

Caused by: io.jsonwebtoken.security.WeakKeyException: The specified key byte array is 128 bits which is not secure enough for any JWT HMAC - SHA algorithm.  The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size).  Consider using the io.jsonwebtoken.security.Keys#secretKeyFor(SignatureAlgorithm) method to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm.  See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.

 

์ด ์˜ค๋ฅ˜๋Š” ๋ฐ”๋กœ ์ „๊ณผ๋Š” ๋‹ค๋ฅด๊ฒŒ ์ด์   ๋น„๋ฐ€ํ‚ค์˜ ๋ฐ”์ดํŠธ ํฌ๊ธฐ ์ œํ•œ์ด ์ƒ๊ฒจ์„œ ๋‚˜ํƒ€๋‚˜๋Š” ์˜ค๋ฅ˜์ด๋‹ค. ๊ธฐ์กด์—๋Š” ์งง์€ ๋ฌธ์ž์—ด์˜ ์‹œํฌ๋ฆฟ ํ‚ค์—ฌ๋„ ์ž‘๋™ํ–ˆ์ง€๋งŒ, ์ด์   ๋„ˆ๋ฌด ์งง์œผ๋ฉด ์•ฝํ•œ ํ‚ค๋ผ๋Š” ์˜ค๋ฅ˜๊ฐ€ ํ„ฐ์ ธ์„œ ์‹คํ–‰์ด ์•ˆ๋œ๋‹ค.

์‰ฝ๊ฒŒ ๋งํ•ด ์šฐ๋ฆฌ๊ฐ€ ์–ด๋–ค ์›น์‚ฌ์ดํŠธ ํšŒ์›๊ฐ€์ž… ํ•˜๋ คํ• ๋•Œ ๋น„๋ฐ€๋ฒˆํ˜ธ 6๊ธ€์ž๋ฅผ ์น˜๋ฉด ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” 10๊ธ€์ž ์ด์ƒ์ด์—ฌ์•ผํ•ฉ๋‹ˆ๋‹ค. ๋ผ๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋‚˜์˜ค๋ฉด์„œ ํšŒ์›๊ฐ€์ž…์„ ๋ชปํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™๋‹ค. 

 

์˜์–ด๋Š” 1๊ธ€์ž๋‹น 1๋ฐ”์ดํŠธ์ด๊ณ , ํ•œ๊ธ€์€ 1๊ธ€์ž๋‹น 3๋ฐ”์ดํŠธ ๋‹ˆ๊น ํ•œ๊ธ€๋กœ ํ•ด์„œ ๋ฐ”์ดํŠธ ์ˆ˜๋ฅผ ๋Š˜๋ ค๋ณด์•˜๋‹ค.

private String secretKey = "ใ…ใ…ฃ๋จธ๋ž˜ใ…‘์ ˆ ใ…“ใ…ˆ๋ฐ”ใ…ฃ๋Ÿฌใ…๋จธใ…ใ…“ใ…ฃใ…๋„ˆ๋ฆฌใ…๋„;ใ…๋จธ๋Ÿฌใ…ฃ๋‚˜๋Ÿฌใ…ฃ๋Ÿฌ์žฌ๋Ÿฌ์žฌ๋Ÿฌ๋‚˜ใ…“๋ฆฌ๋„ใ…ˆใ„ท๋žดใ…“ํ˜ธ๋งˆใ…ฃใ…—ํ•˜ใ…ฃ;ํ™€์žดใ…ˆ";

 

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋น„๋ฐ€ํ‚ค๋ฅผ ๊ธธ๊ฒŒ ํ•˜์—ฌ ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋‹ˆ ์ •์ƒ ์ž‘๋™ํ•˜์˜€๋‹ค.

 

๋กœ๊ทธ์ธ์‹œ ์ •์ƒ์ ์œผ๋กœ JWT  ํ† ํฐ์ด ๋งŒ๋“ค์–ด์ง€๋Š” ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

JWT ํ† ํฐ์„ Decodeํ•ด๋ณด๋‹ˆ ๋‚ด๊ฐ€ ์ค€ ๋ฐ์ดํ„ฐ ๋“ค์ด ์ •์ƒ์ ์œผ๋กœ ๋“ค์–ด์žˆ๋Š” ๊ฑธ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋Œ“๊ธ€