<aside> ☝🏻 Jwt 설정 및 SecurityConfig 설정

</aside>


1. jwt비밀키가 필요하다.

Properties.java 클레스를 만들어 준다.

// 이전에는 @EnableConfigurationProperties 썻지만 이거쓰면 간편하고 유연하게 가능
// v.2.2~ 부터 Config... 생김

// 외부 속성에서 "jwt"로 시작하는 yml,(Properties,JSON )를 매핑
@ConfigurationProperties(prefix = "jwt")
// 애노테이션이 지정된 클래스를 자동으로 스캔하여 "jwt"를 스캔  (v2.2~)
@ConfigurationPropertiesScan
// 사용자 정의 데이터 형식을 사용
@ConfigurationPropertiesBinding
public record Properties(
		// jwt:
	//		password: << 를 매핑시켜줌
		String password
) {
	// ioC 컨테이너한테 설정 파일이라 알려주고 Bean객체로 만들어짐
	// 그 안에서 쓰는건 @Bean 해줘야됨 
	// POJO(Plain Old Java Object) 설정관련 클래스
	@Configuration
	// @ConfigurationProperties 어노테이션이 적용된 
	// 클래스를 사용할 수 있도록 해주는 어노테이션
	@EnableConfigurationProperties(Properties.class)
	public static class ScanningConfig {}
	
	public Properties {
		// 매핑이 안되거나 값이 없을때를 대비해서 넣어줌
		if (password == null) password = "77d1b474a0920ab13e44a05170117cf0e809bad5c554d19020a95b45e9e2fb95893b8b149382e294d78fdb8e5aa2ae266b5797d985f5dc127366d2a50ec3938e";
	}

2. TokenProvider 클래스를 만든다.

@Slf4j
@Component
public class TokenProvider {
	
	// 키 선언
	private final Key key;
	
	// properties에서 비밀키를 넣어주면 HMAC알고리즘화된 키를 반환 그게 위에키에 대입됨
	public TokenProvider(Properties properties) {
		// Proerties에서 받은 비밀키를 64바이트로 디코드
		byte[] secretByteKey = Decoders.BASE64.decode(properties.password());
		// HMAC 알고리즘에서 사용할 수 있는 비밀 키를 생성
		this.key = Keys.hmacShaKeyFor(secretByteKey);
	}
	
	// 검증된 authentication를 넣어 토큰생성 설정등을 요기서함
	public String generateToken(Authentication authentication) {
		
		// header 설정
//		Map<String, Object> header = new HashMap<>();
//		header.put("alg", "HS256");
//		header.put("typ", "JWT");
		
		//payload 설정
		String authorities = authentication.getAuthorities().stream()
				.map(GrantedAuthority ::getAuthority)
				.collect(Collectors.joining(","));
		
		//Access Token 생성
		return Jwts.builder()
				// JWT를 소유하고 있는 사용자의 식별자 정보를 저장하는 데 사용
				.setSubject(authentication.getName())
				// payload 넣어주는곳
				.claim("auth", authorities)
				//토큰 기한
				.setExpiration(new Date(System.currentTimeMillis()+ 1000 * 60 * 30))
				// Signature
				.signWith(key, SignatureAlgorithm.HS256)
				.compact();
	}
	
	// 로그인할때 시큐리티에서 유저인증정보와 엑세스토큰을 대조해서 시큐리티에서 작업할수있게 만들어주는애
	public Authentication getAuthentication(String accessToken) {
		
		//액세스 토큰의 페이로드를 claims객체로 파싱
		Claims claims = parseClaims(accessToken);
		
		// 페이로드에 저장된 auth를 확인
		if (claims.get("auth") == null) {
			throw new RuntimeException("권한 정보가 없는 토큰입니다.");
		}
		
		// JWT 토큰에서 권한 정보를 추출
		Collection<? extends GrantedAuthority> authorities =
				Arrays.stream(claims.get("auth").toString().split(","))
						.map(SimpleGrantedAuthority::new)
						.collect(Collectors.toList());
		//User 객체는 Spring Security에서 제공하는 인증 정보 객체로 만드는작업
		UserDetails principal = new User(claims.getSubject(), "", authorities);
		//인증된 사용자를 나타내는 Spring Security에서 제공하는 객체
		return new UsernamePasswordAuthenticationToken(principal, "", authorities);
	}
	
	
	// JWT 토큰이 유효한지 검증하는 기능
	public boolean validateToken(String token) {
		try {
			Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
			return true;
		}catch (io.jsonwebtoken.security.SecurityException |
		        MalformedJwtException e) {
			log.info("Invalid JWT Token", e);
		} catch (ExpiredJwtException e) {
			log.info("Expired JWT Token", e);
		} catch (UnsupportedJwtException e) {
			log.info("Unsupported JWT Token", e);
		} catch (IllegalArgumentException e) {
			log.info("JWT claims string is empty.", e);
		}
		return false;
	}
	
	//  JWT 토큰에서 Claims 객체를 파싱하여 반환하는 역할
	private Claims parseClaims(String accessToken) {
		try {
			return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody();
		} catch (ExpiredJwtException e) {
			return e.getClaims();
		}
	}
}

3. SecurityConfig.java 클래스 만들기

@Configuration
@EnableWebSecurity(debug = true)
@RequiredArgsConstructor
public class SecurityConfig {
	
	private final DataSource dataSource;	// 임포트 패키지 주의
	private final EncoderFactory encoderFactory;
	private final TokenProvider tokenProvider;
	
	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http
				// csrf 비활성화
				.csrf().disable()
				// 세션을 무상태(stateless)로 관리하겠다는 의미
				.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
				
				.and()
				//fromLogin 비활성화
				.formLogin().disable()
				// HTTP 기본 인증 기능을 비활성화
				.httpBasic().disable()
				
				.authorizeRequests()
				// /api/hello에 대한 요청은 인증이 필요 없다
				.antMatchers("/login","/signup").permitAll()
				// 나머지 요청은 인증이 필요하다
				.anyRequest().authenticated()
				
				.and()
				//인증때 토큰과, 유저가 맞는지 확인하는 설정
				.addFilterBefore(new AuthenticationFilter(tokenProvider),UsernamePasswordAuthenticationFilter.class);
		return http.build();
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		// return PasswordEncoderFactories.createDelegatingPasswordEncoder();
		// return encoderFactory.createEncoder(EncoderType.BCRYPT);
		return encoderFactory.defaultEncoder();
	}
	
//	@Bean
//	public JwtParser jwtParser() {
//		// Parsing -> g
//		byte[] keyBytes = Decoders.BASE64.decode(secretKeyAsString);
//		Key secretKey = Keys.hmacShaKeyFor(keyBytes);
//
//		return Jwts.parserBuilder()
//				.setSigningKey(secretKey)
//				.build();
//	}
	
	// 1) jwt token -> create(: provider)
	// 2) jwt parser
	// 3) authentication manager bean
	// TODO below codes
	// UsernamePasswordAuthenticationToken authenticationToken =
	// new UsernamePasswordAuthenticationToken(email, password);
		    // Auth -> throws AuthenticationException
	//	    authenticationManager.authenticate(authenticationToken);
	// String token = jwtProvider.createToken(...);
	// Cookie, Header, ... -> check. (HTTPS: SSL/TLS Certification)
	
	@Bean
	public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
		return authenticationConfiguration.getAuthenticationManager();
	}
}