보라코딩
JWT(JSON Web Token), Filter, Config 본문
JWT (JSON Web Token)
클레임이라고 불리는 정보를 JSON 형태로 안전하게 전송하기 위한 토큰
- 인증과 정보 교환에 사용, 서명이 되어 있어서 신뢰성 확보가 가능
1. Header : 토큰의 타입과 사용된 알고리즘 정보를 담고 있음, Base64Url로 인코딩
2. Payload : 클레임 정보, 대상, 발행자, 만료 시간 등 다양한 정보가 포함됨, Base64Url로 인코딩
3. Signature : Header과 Payload, Secrey Key를 사용하여 생성된 서명
[ 장점 ]
- 상태 유지 X
- 간단하고 자기 포함적
- 확장성
[ 단점 ]
- 크기 : 클레임이 많을수록 토큰 크기가 커짐
- 보안 : 서명은 되어 있지만 암호화는 되어있지 않음. 중요 정보 JWT에 포함하면 안됨
- 토큰 관리 : 만료 시간, 갱신 필요
JWT.IO
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
jwt.io
build.gradle
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
provider 패키지에 JwtProvider.java 생성
jwt 생성과 검증
( 참고로 secret key는 application.properties에 입력 )
package com.react.spring.provider;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
@Component
public class JwtProvider {
@Value("${secret-key}")
private String secretKey;
// jwt 생성
public String create(String email){
Date expiredDate = Date.from(Instant.now().plus(1, ChronoUnit.HOURS)); // 1시간
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.ES256, secretKey)
.setSubject(email) // email을 (id를 email로 설정함)
.setIssuedAt(new Date()) // 생성시간
.setExpiration(expiredDate) // 만료시간
.compact(); //압축
return jwt;
}
// jwt 검증 (반환되는 것은 email)
public String validate(String jwt){
Claims claims = null;
try {
claims = Jwts.parser().setSigningKey(secretKey)
.parseClaimsJws(jwt).getBody();
} catch (Exception exception){
exception.printStackTrace();
return null;
}
return claims.getSubject();
}
}
filter 패키지에 JwtAuthenticationFilter.java
package com.react.spring.filter;
import com.react.spring.provider.JwtProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
@RequiredArgsConstructor // 필수 생성자
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
String token = parseBearerToken(request);
if (token == null){
// 현재 필터가 아무 작업을 하지 않고 요청을 다음 필터로 전달하는 역할 (요청 계속 처리할 수 있도록)
filterChain.doFilter(request, response);
return;
}
String email = jwtProvider.validate(token);
if (email == null){
filterChain.doFilter(request, response);
return;
}
AbstractAuthenticationToken abstractAuthenticationToken =
// (아이디, 비밀번호, 권한) 입력
new UsernamePasswordAuthenticationToken(email, null, AuthorityUtils.NO_AUTHORITIES);
// 인증 요청에 대한 사용자 인증 요청에 대한 세부정보 설정
abstractAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// context에 등록 (사용자의 인증 정보 관리 목적, 인증된 사용자의 컨텍스트 유지)
SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); // 비어있는 context 만들고
securityContext.setAuthentication(abstractAuthenticationToken); // 현재 사용자의 인증정보 등록
// 사용자의 인증 정보 유지, 인증된 사용자로서 역할 수행
SecurityContextHolder.setContext(securityContext);
} catch (Exception exception){
exception.printStackTrace();
}
// 다음 필터로 넘기기
filterChain.doFilter(request, response);
}
private String parseBearerToken(HttpServletRequest request){
String authorization = request.getHeader("Authorization");
// hasText : null 이거나 길이 0 이거나 공백으로만 채워져 있으면 false
boolean hasAuthorization = StringUtils.hasText(authorization);
if(!hasAuthorization) return null;
boolean isBearer = authorization.startsWith("Bearer ");
if (!isBearer) return null;
// Bearer 토큰인 경우에만
String token = authorization.substring(7);
return token;
}
}
config 패키지에 WebSecurityConfig.java
package com.react.spring.config;
import com.react.spring.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Configurable
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
protected SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception{
httpSecurity
.cors().and() // 기본 cors 정책
.csrf().disable() // csrf 공격 비활성화
.httpBasic().disable() // http basic 인증 비활성화
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() // 세션 생성 정책 stateless : 세션 사용하지 않고 상태 보존하지 않음
.authorizeRequests() // 요청의 인증과 권한 부여 설정
.antMatchers("/", "/api/v1/auth/**", "/api/v1/search/**", "/file/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/v1/board/**", "/api/v1/user/*").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(new FailedAuthenticationEntryPoint());
httpSecurity.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
}
class FailedAuthenticationEntryPoint implements AuthenticationEntryPoint{
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("{\"code:\" : \"AF\", \"message\",\"Authorization Failed.\", \"}\"");
}
}
config 패키지에 CorsConfig.java
package com.react.spring.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings (CorsRegistry corsRegistry){
corsRegistry.addMapping("/**")
.allowedMethods("*")
.allowedOrigins("*");
}
}
참고한 강의!
최신 강의라 따라하기 좋음 ㅎ.ㅎ
'코딩 > Spring' 카테고리의 다른 글
자바 테스트코드 강의 추천! (0) | 2024.02.14 |
---|---|
SpringBoot + React 채팅 구현 (웹소켓, stomp, redis) (0) | 2023.11.20 |
jwt를 이용한 회원가입 (리액트 + JPA) (0) | 2023.08.13 |
Spring Boot JPA CRUD 정리 (0) | 2023.08.08 |
Spring Boot 와 React.js 연동하기! (0) | 2023.08.05 |