SpringBoot 5. Security JWT - swkim0128/PARA GitHub Wiki


type: Spring archive: false

build.gradle


dependencies {
	implementation("org.springframework.boot:spring-boot-starter-security")
	implementation("com.auth0:java-jwt:3.10.3")
}

SecurityConfig


import com.ssafy.api.service.UserService;
import com.ssafy.common.auth.CustomOAuth2UserService;
import com.ssafy.common.auth.JwtAuthenticationFilter;
import com.ssafy.common.auth.SsafyUserDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * ์ธ์ฆ(authentication) ์™€ ์ธ๊ฐ€(authorization) ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ ์„ค์ • ์ •์˜.
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SsafyUserDetailService ssafyUserDetailService;
    
    @Autowired
    private UserService userService;

    private final CustomOAuth2UserService customOAuth2UserService;
    
    // Password ์ธ์ฝ”๋”ฉ ๋ฐฉ์‹์— BCrypt ์•”ํ˜ธํ™” ๋ฐฉ์‹ ์‚ฌ์šฉ
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // DAO ๊ธฐ๋ฐ˜์œผ๋กœ Authentication Provider๋ฅผ ์ƒ์„ฑ
    // BCrypt Password Encoder์™€ UserDetailService ๊ตฌํ˜„์ฒด๋ฅผ ์„ค์ •
    @Bean
    DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsService(this.ssafyUserDetailService);
        return daoAuthenticationProvider;
    }

    // DAO ๊ธฐ๋ฐ˜์˜ Authentication Provider๊ฐ€ ์ ์šฉ๋˜๋„๋ก ์„ค์ •
    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(authenticationProvider());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .httpBasic().disable()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ์ด๋ฏ€๋กœ ์„ธ์…˜ ์‚ฌ์šฉ ํ•˜์ง€์•Š์Œ
                .and()
                .addFilter(new JwtAuthenticationFilter(authenticationManager(), userService)) //HTTP ์š”์ฒญ์— JWT ํ† ํฐ ์ธ์ฆ ํ•„ํ„ฐ๋ฅผ ๊ฑฐ์น˜๋„๋ก ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€
                .authorizeRequests()
                .antMatchers("/api/v1/users/me").authenticated()       //์ธ์ฆ์ด ํ•„์š”ํ•œ URL๊ณผ ํ•„์š”ํ•˜์ง€ ์•Š์€ URL์— ๋Œ€ํ•˜์—ฌ ์„ค์ •
    	        	    .anyRequest().permitAll()
                .and().cors()
                .and()
                .oauth2Login()
                .userInfoEndpoint()
                .userService(customOAuth2UserService);
    }
}

Jwt filter


/**
 * ์š”์ฒญ ํ—ค๋”์— jwt ํ† ํฐ์ด ์žˆ๋Š” ๊ฒฝ์šฐ, ํ† ํฐ ๊ฒ€์ฆ ๋ฐ ์ธ์ฆ ์ฒ˜๋ฆฌ ๋กœ์ง ์ •์˜.
 */
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
	private UserService userService;
	
	public JwtAuthenticationFilter(AuthenticationManager authenticationManager, UserService userService) {
		super(authenticationManager);
		this.userService = userService;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// Read the Authorization header, where the JWT Token should be
        String header = request.getHeader(JwtTokenUtil.HEADER_STRING);

        // If header does not contain BEARER or is null delegate to Spring impl and exit
        if (header == null || !header.startsWith(JwtTokenUtil.TOKEN_PREFIX)) {
            filterChain.doFilter(request, response);
            return;
        }
        
        try {
            // If header is present, try grab user principal from database and perform authorization
            Authentication authentication = getAuthentication(request);
            // jwt ํ† ํฐ์œผ๋กœ ๋ถ€ํ„ฐ ํš๋“ํ•œ ์ธ์ฆ ์ •๋ณด(authentication) ์„ค์ •.
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception ex) {
            ResponseBodyWriteUtil.sendError(request, response, ex);
            return;
        }
        
        filterChain.doFilter(request, response);
	}
	
	@Transactional(readOnly = true)
    public Authentication getAuthentication(HttpServletRequest request) throws Exception {
        String token = request.getHeader(JwtTokenUtil.HEADER_STRING);
        // ์š”์ฒญ ํ—ค๋”์— Authorization ํ‚ค๊ฐ’์— jwt ํ† ํฐ์ด ํฌํ•จ๋œ ๊ฒฝ์šฐ์—๋งŒ, ํ† ํฐ ๊ฒ€์ฆ ๋ฐ ์ธ์ฆ ์ฒ˜๋ฆฌ ๋กœ์ง ์‹คํ–‰.
        if (token != null) {
            // parse the token and validate it (decode)
            JWTVerifier verifier = JwtTokenUtil.getVerifier();
            JwtTokenUtil.handleError(token);
            DecodedJWT decodedJWT = verifier.verify(token.replace(JwtTokenUtil.TOKEN_PREFIX, ""));
            String email = decodedJWT.getSubject();
            
            // Search in the DB if we find the user by token subject (username)
            // If so, then grab user details and create spring auth token using username, pass, authorities/roles
            if (email != null) {
                    // jwt ํ† ํฐ์— ํฌํ•จ๋œ ๊ณ„์ • ์ •๋ณด(userId) ํ†ตํ•ด ์‹ค์ œ ๋””๋น„์— ํ•ด๋‹น ์ •๋ณด์˜ ๊ณ„์ •์ด ์žˆ๋Š”์ง€ ์กฐํšŒ.
            		User user = userService.getUserByEmail(email);
                if(user != null) {
                        // ์‹๋ณ„๋œ ์ •์ƒ ์œ ์ €์ธ ๊ฒฝ์šฐ, ์š”์ฒญ context ๋‚ด์—์„œ ์ฐธ์กฐ ๊ฐ€๋Šฅํ•œ ์ธ์ฆ ์ •๋ณด(jwtAuthentication) ์ƒ์„ฑ.
                		SsafyUserDetails userDetails = new SsafyUserDetails(user);
                		UsernamePasswordAuthenticationToken jwtAuthentication = new UsernamePasswordAuthenticationToken(email,
                				null, userDetails.getAuthorities());
                		jwtAuthentication.setDetails(userDetails);
                		return jwtAuthentication;
                }
            }
            return null;
        }
        return null;
    }
}

JwtToken util


import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;

import static com.google.common.collect.Lists.newArrayList;

/**
 * jwt ํ† ํฐ ์œ ํ‹ธ ์ •์˜.
 */
@Component
public class JwtTokenUtil {
    private static String secretKey;
    private static Integer expirationTime;

    public static final String TOKEN_PREFIX = "Bearer ";
    public static final String HEADER_STRING = "Authorization";
    public static final String ISSUER = "ssafy.com";
    
    @Autowired
	public JwtTokenUtil(@Value("${jwt.secret}") String secretKey, @Value("${jwt.expiration}") Integer expirationTime) {
		this.secretKey = secretKey;
		this.expirationTime = expirationTime;
	}
    
	public void setExpirationTime() {
    		//JwtTokenUtil.expirationTime = Integer.parseInt(expirationTime);
    		JwtTokenUtil.expirationTime = expirationTime;
	}

	public static JWTVerifier getVerifier() {
        return JWT
                .require(Algorithm.HMAC512(secretKey.getBytes()))
                .withIssuer(ISSUER)
                .build();
    }
    
    public static String getToken(String userId) {
    		Date expires = JwtTokenUtil.getTokenExpiration(expirationTime);
        return JWT.create()
                .withSubject(userId)
                .withExpiresAt(expires)
                .withIssuer(ISSUER)
                .withIssuedAt(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()))
                .sign(Algorithm.HMAC512(secretKey.getBytes()));
    }

    public static String getToken(Instant expires, String userId) {
        return JWT.create()
                .withSubject(userId)
                .withExpiresAt(Date.from(expires))
                .withIssuer(ISSUER)
                .withIssuedAt(Date.from(LocalDateTime.now().atZone(ZoneId.systemDefault()).toInstant()))
                .sign(Algorithm.HMAC512(secretKey.getBytes()));
    }
    
    public static Date getTokenExpiration(int expirationTime) {
    		Date now = new Date();
    		return new Date(now.getTime() + expirationTime);
    }

    public static void handleError(String token) {
        JWTVerifier verifier = JWT
                .require(Algorithm.HMAC512(secretKey.getBytes()))
                .withIssuer(ISSUER)
                .build();

        try {
            verifier.verify(token.replace(TOKEN_PREFIX, ""));
        } catch (AlgorithmMismatchException ex) {
            throw ex;
        } catch (InvalidClaimException ex) {
            throw ex;
        } catch (SignatureGenerationException ex) {
            throw ex;
        } catch (SignatureVerificationException ex) {
            throw ex;
        } catch (TokenExpiredException ex) {
            throw ex;
        } catch (JWTCreationException ex) {
            throw ex;
        } catch (JWTDecodeException ex) {
            throw ex;
        } catch (JWTVerificationException ex) {
            throw ex;
        } catch (Exception ex) {
            throw ex;
        }
    }

    public static void handleError(JWTVerifier verifier, String token) {
        try {
            verifier.verify(token.replace(TOKEN_PREFIX, ""));
        } catch (AlgorithmMismatchException ex) {
            throw ex;
        } catch (InvalidClaimException ex) {
            throw ex;
        } catch (SignatureGenerationException ex) {
            throw ex;
        } catch (SignatureVerificationException ex) {
            throw ex;
        } catch (TokenExpiredException ex) {
            throw ex;
        } catch (JWTCreationException ex) {
            throw ex;
        } catch (JWTDecodeException ex) {
            throw ex;
        } catch (JWTVerificationException ex) {
            throw ex;
        } catch (Exception ex) {
            throw ex;
        }
    }
}
โš ๏ธ **GitHub.com Fallback** โš ๏ธ