
대칭키를 사용한대 ..!!
알바 --(20분)-- 학교 --(20분)-- 집 --(20분)-- 체육관
알바하는 곳부터 체육관까지 일직선으로 지하철역 5정거장 차이난다.
신기한데예
혹시 cors안했으면 BE목록 가서 해야함
Header.Payload.Signature
HeaderPayload.Signaturespring:
jwt:
secret: 원하는 키
.parseSignedClaims(token)이 검증을 해준다는 걸 다뤘다.package com.jsh.oauth_jwt_study.jwt;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JWTUtil {
private SecretKey secretKey;
public JWTUtil(@Value("${spring.jwt.secret}") String secret) {
this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
//시크릿키 만들기 *두근두근*
}
public String getUsername(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload()
.get("username", String.class);
}
public String getRole(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload()
.get("role", String.class);
}
public Boolean isExpired(String token) {
return Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token)
.getPayload()
.getExpiration()
.before(new Date());
}
public String createJwt(String username, String role, Long expiredMs) {
return Jwts.builder()
.claim("username", username)
.claim("role", role)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expiredMs))
.signWith(secretKey)
.compact();
}
}
바로 쿠키를 가지고 만드는게 좋다.
자세한 내용은 아래서 설명하겠다 😅
@Component
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JWTUtil jwtUtil;
public CustomSuccessHandler(JWTUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//OAuth2User
CustomOAuth2User customUserDetails = (CustomOAuth2User) authentication.getPrincipal();
//토큰을 만드는 코드
String username = customUserDetails.getUsername();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
Iterator<? extends GrantedAuthority> iterator = authorities.iterator();
GrantedAuthority auth = iterator.next();
String role = auth.getAuthority();
String token = jwtUtil.createJwt(username, role, 60*60*60L);
//쿠키를 구워요~! 🍪🍪
response.addCookie(createCookie("Authorization", token));
response.sendRedirect("http://localhost:3000/");
}
private Cookie createCookie(String authorization, String token) {
//클래스 이름이 어떻게 쿠키? 너무 귀여워... 🥲
Cookie cooKie = new Cookie(authorization,token);
cooKie.setMaxAge(60*60*60); //쿠키가 적용될 유효 시간을 설정한다. (여기서는 60시간..ㄷㄷ)
cooKie.setPath("/"); //쿠키가 유효한 경로를 설정한다. "/"는 루트 경로로 설정해 모든 경로에서 쿠키가 유효하도록 설정한다.
cooKie.setHttpOnly(true); // 쿠키를 HTTP(S)에서만 접근 가능하도록 한다.
//cooKie.setSecure(true); //쿠키를 HTTPS으로만 전송되도록 설정한다. (그런데 나는 돈없어서 HTTP임 그래서 주석할거임ㅋ)
return cooKie;
}
}
만약 요청에 토큰이 있는 경우 토큰을 검증하고, 강제로 SecurityContextHolder에 세션을 생성하자.
물론 이 애플리케이션은 STATLESS 상태이기 때문에 소멸된다.
따라서 이렇게 검증을 하고 세션을 생성할 필터를 만들어야한다.
유저가 보낸 쿠키가 제대로 된 토큰이라면 (임시)세션에 권한을 넣어줘서 다양한 리소스에 접근할 수 있도록 하는 것이다.
java는 null을 다루면 바로 예외를 터트리기 때문에^^사실 요청에 어떤 값을 추가하거나 어떤 메서드를 호출하는 것은 아니다.
흐름 자체는 잘못된 토큰이거나 올바른 토큰이어도 똑같다!
다만, 강제로 세션에 유저를 넣어줘서 이후 권한이 필요한 리소스에 정상적으로 접근할 수 있도록 하는 것이다.
로직이 복잡해서... 좋은 코드는 아니라고 생각한다.
일단 이 코드가 담당하고 있는 역할이 아주 많다 ^🥕^
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestUri = request.getRequestURI();
if (requestUri.matches("^\\/login(?:\\/.*)?$")) {
filterChain.doFilter(request, response);
return;
}
if (requestUri.matches("^\\/oauth2(?:\\/.*)?$")) {
filterChain.doFilter(request, response);
return;
}
String authorization = null;
Cookie[] cookies = request.getCookies();
if (cookies == null) {
System.out.println("cookie null");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
for (Cookie cookie : cookies) {
System.out.println(cookie.getName());
if (cookie.getName().equals("Authorization")) {
authorization = cookie.getValue();
}
}
//Authorization 헤더 검증
if (authorization == null) {
System.out.println("token null");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
//토큰
String token = authorization;
//토큰 소멸 시간 검증
if (jwtUtil.isExpired(token)) {
System.out.println("token expired");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
세션에 들어가는 건? Authentication타입
Authentication타입에 들어가는 건 ~~~ OAuth2User
String username = jwtUtil.getUsername(token);
String role = jwtUtil.getRole(token);
//userDTO를 생성하여 값 set
UserDTO userDTO = new UserDTO();
userDTO.setUsername(username);
userDTO.setRole(role);
//UserDetails에 회원 정보 객체 담기
CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO);
//스프링 시큐리티 인증 토큰 생성
Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
//세션에 사용자 등록
SecurityContextHolder.getContext().
setAuthentication(authToken);
package com.jsh.oauth_jwt_study.jwt;
import com.jsh.oauth_jwt_study.dto.CustomOAuth2User;
import com.jsh.oauth_jwt_study.dto.UserDTO;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
public class JWTFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil;
public JWTFilter(JWTUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String requestUri = request.getRequestURI();
if (requestUri.matches("^\\/login(?:\\/.*)?$")) {
filterChain.doFilter(request, response);
return;
}
if (requestUri.matches("^\\/oauth2(?:\\/.*)?$")) {
filterChain.doFilter(request, response);
return;
}
//cookie들을 불러온 뒤 Authorization Key에 담긴 쿠키를 찾음
String authorization = null;
Cookie[] cookies = request.getCookies();
if (cookies == null) {
System.out.println("cookie null");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
for (Cookie cookie : cookies) {
System.out.println(cookie.getName());
if (cookie.getName().equals("Authorization")) {
authorization = cookie.getValue();
}
}
//Authorization 헤더 검증
if (authorization == null) {
System.out.println("token null");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
//토큰
String token = authorization;
//토큰 소멸 시간 검증
if (jwtUtil.isExpired(token)) {
System.out.println("token expired");
filterChain.doFilter(request, response);
//조건이 해당되면 메소드 종료 (필수)
return;
}
//토큰에서 username과 role 획득
String username = jwtUtil.getUsername(token);
String role = jwtUtil.getRole(token);
//userDTO를 생성하여 값 set
UserDTO userDTO = new UserDTO();
userDTO.setUsername(username);
userDTO.setRole(role);
//UserDetails에 회원 정보 객체 담기
CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO);
//스프링 시큐리티 인증 토큰 생성
Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
//세션에 사용자 등록
SecurityContextHolder.getContext().
setAuthentication(authToken);
filterChain.doFilter(request, response);
}
}
package com.jsh.oauth_jwt_study.config;
import com.jsh.oauth_jwt_study.jwt.JWTFilter;
import com.jsh.oauth_jwt_study.jwt.JWTUtil;
import com.jsh.oauth_jwt_study.oauth2.CustomSuccessHandler;
import com.jsh.oauth_jwt_study.service.CustomOAuth2UserService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.oauth2.client.web.OAuth2LoginAuthenticationFilter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import java.util.Collections;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final CustomOAuth2UserService customOAuth2UserService;
private final CustomSuccessHandler customSuccessHandler;
private final JWTUtil jwtUtil;
public SecurityConfig(CustomOAuth2UserService customOAuth2UserService, CustomSuccessHandler customSuccessHandler, JWTUtil jwtUtil) {
this.customOAuth2UserService = customOAuth2UserService;
this.customSuccessHandler = customSuccessHandler;
this.jwtUtil = jwtUtil;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//csrf disable
http
.csrf((auth) -> auth.disable());
//From 로그인 방식 disable
http
.formLogin((auth) -> auth.disable());
//HTTP Basic 인증 방식 disable
http
.httpBasic((auth) -> auth.disable());
//JWTFilter 추가
http
.addFilterBefore(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
//oauth2
http
.oauth2Login((oauth2) -> oauth2
.userInfoEndpoint((userInfoEndpointConfig) -> userInfoEndpointConfig
.userService(customOAuth2UserService))
.successHandler(customSuccessHandler)
);
//경로별 인가 작업
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers("/").permitAll()
.anyRequest().authenticated());
//세션 설정 : STATELESS
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
CustomSuccessHandler는 무엇일까?public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
SimpleUrlAuthenticationSuccessHandler를 상속받았다.SimpleUrlAuthenticationSuccessHandler는 : 인증이 성공적으로 이루어졌을 때 실행되는 클래스다. onAuthenticationSuccess 메서드는 무엇일까?onAuthenticationSuccess 메서드가 하는 일1. 리다이렉션 처리
2. 세션관리
3. 쿠키 설정
4. 로그 기록
5. 추가적인 비즈니스 로직 수행
onAuthenticationSuccess의 파라미터 정보HttpServletRequest request
HttpServletResponse response
Authentication authentication
이 외에도 자주 사용하는 쿠키 클래스 관련 코드를 알아보자.
<쿠키를 조회하는 코드>
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("name".equals(cookie.getName())) {
String value = cookie.getValue();
// 쿠키 값 사용
}
}
}
<쿠키를 삭제하는 방법>
cookie.setMaxAge(0); // 유효 기간을 0으로 설정하여 쿠키 삭제
- 세션리스지만 일시적으로 세션을 넣어서 권한 검사를 통과할 수 있도록 도와준다.
- 자바에서 NULL은 재앙이다.
오우 힘들어요~!