인프런 강의 를 참고하여 정리한 글 입니다.
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: cpdm
password:
jpa:
defer-datasource-initialization: true
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
show_sql: true
logging:
level:
me.cpdm: DEBUG
jwt:
header: Authorization
secret: Y2hvcHBhLWRvbnQtYml0ZS1tZS1zcHJpbmctYm9vdC1qd3QtdGVzdC1zZWNyZXQta2V5LWNob3BwYS1kb250LWJpdGUtbWUtc3ByaW5nLWJvb3Qtand0LXRlc3Qtc2VjcmV0LWtleQo=
token-validity-in-seconds: 86400
implementation 'io.jsonwebtoken:jjwt-api:0.11.2'
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2'
public TokenProvider(
@Value("${jwt.secret}") String secret,
@Value("${jwt.token-validity-in-seconds}") long tokenValidityInSeconds) {
this.secret = secret;
this.tokenValidityInMilliseconds = tokenValidityInSeconds * 1000;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
public String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date validity = new Date(now + this.tokenValidityInMilliseconds); // 토큰 만료 시간 설정
// 토큰 생성하여 반환
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public boolean validateToken(String token) {
try {
// 파싱 과정에서 catch
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
logger.info("[ERR] : 잘못된 JWT SIGN");
} catch (ExpiredJwtException e) {
logger.info("[ERR] : 만료된 JWT TOKEN");
} catch (UnsupportedJwtException e) {
logger.info("[ERR] : 미지원 JWT TOKEN");
} catch (IllegalArgumentException e) {
logger.info("[ERR] : 잘못된 JWT TOKEN");
}
return false;
}
public JwtFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest);// request에서 토큰을 받아온다.
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {//유효성읉 통과한다면
Authentication authentication = tokenProvider.getAuthentication(jwt); // 정상적으로 반환된다면
SecurityContextHolder.getContext().setAuthentication(authentication); // 저장해준다.
logger.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
} else {
logger.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI);
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void configure(HttpSecurity http) {
JwtFilter customFilter = new JwtFilter(tokenProvider);
// security 로직에 필터를 등록한다.
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException
) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
@EnableGlobalMethodSecurity(prePostEnabled = true)
public SecurityConfig(
TokenProvider tokenProvider,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.accessDeniedHandler(jwtAccessDeniedHandler)
.and()
.headers()
.frameOptions()
.sameOrigin()
.and()
.sessionManagement() // 세션 미사용
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests() // 토큰이 없는 상태에서 요청이 들어오는 경우 permitAll 해준다.
.antMatchers("/api/cpdm").permitAll()
.antMatchers("/api/authenticate").permitAll() // 로그인
.antMatchers("/api/join").permitAll() // 회원가입
.anyRequest().authenticated()
.and()
.apply(new JwtSecurityConfig(tokenProvider));