<aside>
☝🏻 Jwt 설정 및 SecurityConfig 설정
</aside>
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";
}
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();
}
}
}
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();
}
}