스프링 시큐리티 OAuth2.0 JWT로 구현하기

조대훈·2024년 4월 2일
0

스프링 시큐리티

목록 보기
4/4
post-thumbnail

실습 목표

OAuth2.0 클라이언트와 스프링 시큐리티 6 프레임 워크를 활용하여 신뢰할 수 있는 외부 사이트 ( 구글, 네이버)로 부터 인증을 받고 전달 받은 유저 데이터를 활용하여 JWT를 발급 하고 인가를 진행하는 방법
인증 받은 데이터는 MariaDB를 활용하고 저장

구현

  • API 서버 형태
  • 인증: 네이버 / 구글 소셜 로그인 ( 코드 방식 ) 후 JWT 발급
  • 인가 : JWT를 통한 경로별 접근 권한
  • 인증 정보 DB 저장 후 추가 정보 기입

동작 원리, 프론트 / 백 책임 분배

OAuth2 Code Grant 방식의 동작 순서

  1. 로그인 요청시 로그인 페이지 팝업
  2. 로그인 성공시 인증 서버 측에서 코드 발급
  3. 코드 경로는 우리가 미리 설정한 리다이렉트 URL로 리다이렉팅
  4. 쿼리 파라미터로 코드가 발급
  5. 우리쪽 스프링 서버에서 토큰을 요청
  6. 받은 토큰을 통해서 Acess 토큰을 통해서 User 정보 획득

세션 방식에서 OAuth2 클라이언트 동작 원리

  1. 로그인 창을 요청하는 우리 측 서버 경로로 들어가면 자동으로 리다이렉트
  2. 미리 설정해둔 인증 서버 쪽으로 이동
  3. 인증 서버는 로그인 페이지로 응답
  4. 로그인 성공시 리다이렉트 주소로 code를 준다.
  5. 해당 code로 -> Access 토큰 발급
  6. 해당 Acess 토큰으로 리소스 서버로부터 User정보 획득
  7. User 정보를 받아서 세션 생성
  8. 로그인, 세션 생성 완료

세션 방식의 모식도


JWT 방식에서 OAuth2 클라이언트 구성시 고민점

JWT 방식에서는 로그인(인증)이 성공 하면 JWT 발급 문제와 웹. 하이브리드. 네이티브앱 별 특징에 의해 OAuth2 Code Grant 방식 동작의 책임을 프론트엔드 측에 둘 것인지 백엔드 측에 둘 것인지 많은 고민을 한다

로그인 (인증) 이 성공하면 JWT를 발급해야 하는 문제

  • 프론트단에서 로그인 경로에 대한 하이퍼링크를 실행하면 소셜 로그인 창이 등장하고 로그인 로직이 수행 된다.
    - 하이퍼링크로 실행했기 때문에 JWT를 받을 로직이 없다.
  • 로그인이 성공되면 JWT가 발급 되는데 하이퍼링크로 실행 했기 때문에 JWT를 받을 로직이 없다. 해당 부분에 대한 redirect_url 설정에 따라 많은 고민이 필요하다.
  • API ( axios, fetch )로 요청을 보내면 백엔드 측으로 요청이 전송 되지만 외부 서비스 로그인 페이지를 확인할 수가 없다.
    - 결국엔 하이퍼링크로 보내야 한다.

웹 / 하이브리드 / 네이티브앱 별 특징

  • 웹에서 편하게 사용할 수 있는 웹페이지가 앱에서는 웹뷰로 보이기 때문에 UX적으로 안좋은 경험을 가질 수 있다.
  • 앱 환경에서 쿠키 소멸 현상

프론트 / 백 책임 분배

모든 책임을 프론트가 맡을 시

이 경우 편하게 구성할 수는 있지만, 프론트 측에서 보낸 유저 정보의 진위를 따지는 게 중요하다.
추가적인 보안로직 구성이 까다로워질 수 있다.

책임을 프론트/백에 분배해서 할시

대부분의 웹 블로그가 구현한 방식이지만 코드/ Acess 토큰을 전송하는 방법을 지양한다. (보안 규격 등의 이유)


모든 책임을 백엔드가 맡을 경우

하이퍼링크로 요청 했기 때문에 백엔드 측에서 JWT를 획득하기 매우 까다롭다.


구현할 방식

로그인 페이지 요청 - > 코드 발급 -> Access 토큰 -> 유저 정보 획득 -> JWT 발급 모두 Spring 에서 처리할 계획이다.

참고

앱 개발시 프론트 방식 권장
웹 개발시 백엔드 방식 권장
액세스 토큰, 코드 API 전송 지양


동작 원리

모식도

각각의 필터가 동작하는 주소

JWTFilter
우리가 직접 커스텀해서 등록 해야 한다

모든 주소에서 동작

OAuth2AuthorizationRequestRedirectFilter

/oauth2/authorization/서비스명
/oauth2/authoriziation/naver
/oauth2/auothorziation/google

OAuth2LoginAuthenticationFilter 외부 인증 서버에 설정할 redirect_url

/login/oauth2/code/서비스명
/login/oauth2/code/naver
/login/oauth2/code/google


OAuth2클라이언트에서 우리가 구현해야 할 부분

Filter 들은 자동으로 설정이 된다

  • OAuth2UserDetailService
  • OAuth2UserDetails
  • LoginSuccessHandler

DB에 저장할 로직과 로그인 성공시 실행될 로직

JWT에서 우리가 구현해야 할 부분

  • JWTFilter
  • JWTUtils

토큰을 검증할 필터, 토큰을 발급하고 검증할 로직이 담겨있는 필터


SecurityConfig 기본 세팅

@Configuration  
@EnableWebSecurity  
public class SecurityConfig {  
  
    @Bean  
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {  
  
  
        httpSecurity.csrf((auth) -> auth.disable())  
                .formLogin((auth) -> auth.disable())  
                .httpBasic((auth) -> auth.disable())  
                .oauth2Login(Customizer.withDefaults())  
                .authorizeHttpRequests((auth) -> auth  
                        .requestMatchers("/").permitAll()  
                        .anyRequest().authenticated()  
                )  
                .sessionManagement((session) -> session  
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS));  
  
        return httpSecurity.build();  
  
  
          
    }  
}

진행하는 예제에 필요에 따라 각 옵션 disable 과 SessionManagement를 통해 SessionCreationPolicy.STATELESS로 설정 하였다.

기본적인 SecurityConfig 세팅 완료
추후에 인증, 인가를 추가 하거나 JWT를 발급하고, JWT를 검증할 필터를 추가하고, OAuth2 로그인 값도 커스텀하는 등의 추가 과정이 필요하다.

csrf().disable()

세션 기반의 웹 사이트의 경우,CRSF 공격 방지차 서버에서 생성한 CSRF토큰을 쿠키나 세션에 저장하는데, JWT방식의 경우 이를 저장할 세션이 존재하기 때문에 다른 방식의 보안 절차를 수행한다.

httpBasic().disable()

사용자명 비밀번호를 텍스트로 전송하는 가장 기본적인 인증 방식으로 보안에 취약해 JWT와 같은 암호화 토큰 기반의 인증 방식을 사용할 때엔 disable() 한다

sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)

JWT는 상태정보를 저장하지 않는 statless 한 특징을 가지고 있다.
스프링 시큐리티는 기본적으로 인증에 필요한 요청에 대해 세션을 생성하고 관리하는 기능을 제공하는데, JWT인증 방식에서는 세션 생성이 필요 없기 때문에 disable() 한다.


JWT 발급 및 검증 클래스

JWT 발급과 검증

  • 로그인시-> 성공 ->JWT 발급
  • 접근시 ->JWT 검증

JWT에 관해 발급과 검증을 담당할 클래스가 필요하다. 따라서 JWTUtil 이라는 클래스를 생성하여 JWT 발급, 검증 메소드를 작성한다.

JWT

  • Header
    - JWT임을 명시
    - 사용한 알고리즘
  • PlayLoad
    - 해당 정보
  • Signature
    - 암호화 알고리즘+ 암호화 키

JWT의 특징은 내부 정보를 단순 BASE64 방식으로 인코딩 하기 때문에 외부에서 쉽게 디코딩 할수 있다.
외부에서 열람해도 되는 정보를 담아야 하며, 토큰 자체의 발급처를 확인하기 위해서 사용한다.
(지폐와 같이 외부에서 그 금액을 확인하고 금방 외형을 따라 만들 수 있지만 발급처에 대한 보장 및 검증은 확실하게 해야하는 경우에 사용 한다. 따라서 토큰 내부에 비밀번호와 같은 값 입력을 금지 한다.)


암호화 키 저장

  • application.properties이나 custom.properties에 저장 뒤 git.ignore 처리해준다.
@Component
public class JWTUtil {

    private SecretKey secretKey;

    public JWTUtil(@Value("${spring.jwt.secret}")String secret) {


        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();
    }
}

getUsername 메서드 내부:

  • Jwts.parser(): JWT 토큰을 파싱하기 위한 Parser 생성
  • verifyWith(): 생성한 Parser에 검증에 필요한 Key 제공
  • build(): Parser 구성 완료
  • parseClaims(): 주어진 토큰 파싱

getRole 메서드 내부:

  • 동일하게 Jwts.parser()부터 parseClaims()까지 수행

isExpired 메서드 내부:

  • parser() 부터 parseClaims()까지 동일
  • getPayload(): 토큰 Payload 영역 접근
  • getExpiration(): 토큰의 Expiration Claim 값 얻기
  • before(): Expiration 값이 현재 시간 이전인지 확인

즉, 주요 로직은 Jwts의 parser()를 시작으로 parseClaims() 까지입니다. 그 이후 각 메서드의 목적에 맞게 토큰 정보를 추출하고 검증하는 과정을 거치게 됩니다.


코드 구현부

SecurityConfig


@EnabaleWebSecurity
@Configuration
@AllArgsConstuctor
public class SecurityConfig{

	private final CustomOAuth2UserService cutomOAuth2UserService;
	private final CustomSuccessHandler customSuccessHandler;

	@Bean
	public SecurityFilterChain securityFilterChain (HttpSecurity httpSecurity) throws Exception{

		httpSecurity.
		crsf(AbstractHttpConfigurer::disable)
		.fromLogin(AbstractConfigurer::disable)
		.httpBasic(AbstractConfigurer::disalbe)
		
		.oauth2Login((oauth)->oauth.userInfoEndPoint(
			(userInfoEndPointCoinfg
			.userService(customOAuth2UserService))
			.successHandler(customSuccessHandler))
		
		.authorizationHttpRequest((auth)->auth
			.requestMathcer("/").pertmiaAll()
			.anyRequest().authenticated())
			)
			.sessionManagement((session)->session.sessionCreatePolicy(SessionCreatePolicy.STATELESS));

		return httpSecurity.build();
	}
}

userInfoEndPoint() : OAuth2 로그인 성공 후 사용자 정보를 가져오는 endpoint 설정
userService(custimOAuth2UserService) : 커스텀 UserService 빈을 지정

OAuth2Provider 에서 로그인이 성공하면 스프링 시큐리티가 /oauth2/authorization/google,naver
엔드포인트로 사용자 정보를 요청한다. 여기서 얻은 사용자 정보를 처리할 customOAuthUserService 빈을 통해 후처리를 진행한다. 이를 통해 획득한 정보를 바탕으로 애플리케이션 레벨의 인증 및 권한처리 로직을 적용할 수 있다. 주로 DB연동, 가입/비가입 처리, 획득 정보 업데이트 등을 이 서비스에서 하게 된다.

CustomOAuth2UserService
1. 엔드 포인트를 설정,
2. 서비스별로 받을 Response 객체 생성
3. OAuth2User 구현 ->UserDTO-> CustomOAuth2User 반환

OAuth2User : 서버를 통해 얻어온 사용자 정보 자체

역할
소셜 로그인 성공 후 반환된 OAuth2User 객체에 필요한 사용자 정보 추출
최초가입시 DB 저장

public Class CustomOAuth2UserService with DefaultOAuth2UserService{
	@Override
	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException{
		OAuth2User oAuth2User = super.loadUser(userReqeust);

		String registrationId = userRequest.getClientRegistration().getRegistrationId();
		OAuth2Response oAuth2Response=null;
		if(registrationId.equals("naver")){
			oAuth2Response = new NaverResponse(oAuth2User.getAttributes());
		}
		else if(registrationId.equals("google")){
			oAuth2Response= new GoogleResponse(oAuth2User.getAttribute());
			
		}else{
		null;
		}

	String username= oAuth2Response.getProvider()+ " " + oAuth2Response.getProviderId();

	UserDTO userDTo = new UserDTO;


	userDto.setUsername(username);
	userDto.setName(oAuth2Response.getName());
	userDTO.setRole("ROLE_USER");

	return new CustomOAuth2User(userDTO);
	
	
	}
}

OAuth2Response.interface

public interface OAuth2Response{

	String getProvider();
	String getProviderId();
	String getEmail();
	String getName();
}

GoogleResponse

@RequiredArgsConstructor
public GoogleResponse implements OAuth2Response{

	Map<String,Object> attribute;

	@Override
	String getProvider(){
		return "google";
		
	}

	@Override
	String getProviderId(){
		retunr attribute.get("sub").toString();
	}

	@Override
	String getEmail(){
		return attribute.get("email").toString();
	}

	@Override
	String getName(){
		return attribute.get("name").toString();
	}
	

}

NaverResponse


ass NaverResponse implements OAuth2Response {  
  
    private final Map<String, Object> attribute;  
  
    public NaverResponse(Map<String, Object> attribute) {  
        this.attribute = (Map<String,Object>) attribute.get("response");  
    }  
  
    @Override  
    public String getProvider() {  
        return "naver";  
    }  
  
    @Override  
    public String getProviderId() {  
        return attribute.get("id").toString();  
    }  
  
    @Override  
    public String getEmail() {  
        return attribute.get("email").toString();  
    }  
  
    @Override  
    public String getName() {  
        return attribute.get("name").toString();  
    }

CustomOAuth2User
사용자 정보 조회, 반환을 위한 OAuth2User 인터페이스 구현체


@RequiredArgsConstructor
public class CustomOAuth2User implements OAuth2User{

	private final UserDTO userDTO;

	@Override
	public Map<String, Object> getAttribute(){
		return null;
	}
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
	    return Collections.singletonList(new SimpleGrantedAuthority(userDTO.getRole())); 
	}

	@Override
	public String getName(){
		return userDTO.getName();
	}
	@Override
	public String getUsername(){
		return userDTO.getUsername();
	}

}

유저 정보 DB 저장

  1. OAuth2Response -> UserDTO -> UserEntity -> UserRepository.save()
  2. 로그인 성공시 JWT 토큰 발급 (JwtUtil) -> CustomSuccessHandler에 등록
  3. SecurityConfig에 successHandler() 등록

DTO는 계층간 데이터 교환을 위한 객체이며 Entity는 DB 매핑을 위한 객체 이다.

OAuth2UserService

  • 소셜/OAuth2 로그인시 프롭이더에서 전달 받은 사용자 정보 추출
    -엔드포인트를 통해 사용자정보 획득
    - 프로바이더 별로 다른 필드 추상화, 필드 추출
		String registrationId = userRequest.getClientRegistration().getRegistrationId();
  1. OAuth2 로그인 시도시 전달되는 OAuth2UserRequest 객체엔 ClientRegisitration 정보가 있다.
  2. ClientRegistration 에는 해당 OAuth2 프로바이더의 정보가 담겨있다
  3. registrationId는 이 ClientRegistration의 고유 ID이다.
  4. 고은을 시도한 OAuth2 프로바이더 종류를 이 ID로 구분하는 것이다.
  5. 즉 registrationId값으로 분기 처리를 하여 프로바이더 별로 개별 로직을 적용하기 위함이다.

@Service
@RequiredArgsConstructor
public Class CustomOAuth2UserService with DefaultOAuth2UserService{

	private final UserRepository userRepository;
	
	@Override
	public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException{
		OAuth2User oAuth2User = super.loadUser(userReqeust);

		String registrationId = userRequest.getClientRegistration().getRegistrationId();
		OAuth2Response oAuth2Response=null;
		if(registrationId.equals("naver")){
			oAuth2Response = new NaverResponse(oAuth2User.getAttributes());
		}
		else if(registrationId.equals("google")){
			oAuth2Response= new GoogleResponse(oAuth2User.getAttribute());
			
		}else{
		null;
		}

	String username= oAuth2Response.getProvider()+ " " + oAuth2Response.getProviderId();

	UserEntity existData = userRepository.findByUsername(username);
	if(existData ==null){
	
		UserEntity userentity = new UserEntity();
		userEntity.setUsername(username);
		userEntity.setName(oAuth2Response.getName());
		userEntity.setEmail(oAuth2Response.getEmail());
		userRepository.save(userEntity);

		UserDTo userDto = new UserDto();
		userDto.setUsername(username);
		userDto.setName(oAuth2Response.getName());
		userDto.setRoel("ROLE_USER");

		return new CustomOAuth2User(userDTO);
		
	}else{
	
		exisData.setUsername(username);
		existData.setEmail(oAuth2Response.getEmail());
		existData.setName(oAuth2Response.getName());

		userRepository.save(existData);

		userDTO.setUsername(username);
		userDTO.setUsername(existData.getUsername());
		userDTO.setName(oAuth2Response.getNmae());
		userDTO.setRole(exisData.getRole());

	return new CustomOAuth2User(userDTO);
	}
	
	
	}
}

OAuth2User를 userReqeust 로 추출 하고, registrationId 또한 userRequest로 추출 한다.
각 OAuth 프로바이더 구분을 위해 임의로 username을 생성한다
값이 존재하지 않으면 생성
값이 존재하면 수정하는 식으로 CustomOAuth2User로 DTO에 담아 return 한다

JWTUtil

-JWT 암호화/복호화 메서드 제공
JWT 생성시 알고리즘 키 관리
토큰 파싱시 서명 검증이나 복호화 위한 메서드 제공

  • Claim 생성 및 파싱
    - 토큰에 담길 정보인 클레임 객체 생성 및 메서드 제공
    - 토큰 파싱시 클레임 추출 및 검증 관련 메서드 제공
  • 토큰 생성 및 파싱
    - 전체 토큰 객체 생성 및 서명, 시리얼라이징, 문자열 변환 등 기능 수행
    - 문자열로 전달 받은 토큰 파싱, 검증 메서드 제공

JWT관련 다양한 연산을 위한 유틸리티 성격의 클래스 - 암호화 알고리즘, 키 관리, 클레임 처리

Claim은 Playload 부분에 담길 정보 조각이라고 보면 된다.


@Component
public class JWTUtil{

	private final SecretKey secretKey;
	
	public JWTUtil(@Value("${spring.jwt.secret}")String secret){
		secretKey= new SecretKeySpec(secret.getBytes(StandardCharsets(UTF_8),Jwts.SIG.H256.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("roloe", 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.currenTimeMills()))
		.expiration(new Date(System.currentTimeMills()+expiredMs))
		.singWith(secretKey)
		.compact();
	}
}
  1. Jwts.parser(): JWT 토큰을 파싱하기 위한 Parser 생성
  2. verifyWith(): 생성한 Parser에 검증에 필요한 Key 제공
  3. build(): Parser 구성 완료
  4. parseClaims(): 주어진 토큰 파싱
  5. parser() 부터 parseClaims()까지 동일
  6. getPayload(): 토큰 Payload 영역 접근
  7. getExpiration(): 토큰의 Expiration Claim 값 얻기
  8. before(): Expiration 값이 현재 시간 이전인지 확인

CustomSuccessHandler


@Component
@RequiredArgsConstructor
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler{

	private final JWTUtil jwtUtil;

	@Override
	public void onAuthenticationSuccess(HttpServeltRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletExcption{

	CustomOAuth2User customUserDetails =(CustomOAuth2User)authentication.getPrincipal();
	// 인증 사용자가 누구인지 
	String username= customUserDetails.getUsername();
	//authentication.getPrincipal() 로 username 추출 

	Collection<? extends GrantedAuthoritiy> authorities= authentication.getAuthorities();

	Iterator<? extends GrantedAuthority> iterator = authentication.getAuthorities().iterator();

	GrandAuthority authority = iterator.next(); 
	// 사용자 권한 정보 목록에서 첫 번째 권한을 가져옴 
	String role= authority.getAuthority();

	String token = jwtUtil.createJwt(username, role, 60* 60*60L);
	// JWTUtil 클래스에서 구현해놓은 토큰 생성 메서드를 실제 작동하는 부분
	// authentication.getPrincipal()로 얻은 username 과 
	// authentication.getAuthorities() 에서 얻은 role 을 넣고
	// 만료 시간을 넣음 
	response.addCookie(createCookie("Authorization",token));
	// response에 쿠키를 addCookie() 하는데 Authorization 과 token을 함꼐 넣음
	response.sendRedirect("http://localhost:3000/");
	//리다이렉트 

	
	
	}
}
  1. oAuthenticationSuccess 메서드 오버라이딩 ( OAuth2 인증 성공시 호출될 핸들러 메서드)
  2. 인증 객체 Authentication 에서 사용자 정보 추출 Principal로 저장된 CustomAuth2User에서 사용자 정보 꺼냄
  3. JWT 토큰 생성 username,role,만료시간 정보를 이용해 JWT 토큰생성
  4. 생성한 토큰을 쿠키에 담아 응답 Authorization 쿠키에 JWT 값 설정, 쿠키 옵션들도 설정
  5. 리다이렉트 응답 반환 - 프론트엔드 리다이렉트 URI 지정

OAuth2 로그인 성공시 JWT 토큰을 생성, 쿠키에 담고, 프론트엔드 페이지로 리다이렉트

JWTFilter

  1. 요청 헤더에서 JWT 토큰 추출
  2. JWT 토큰 유효성 검증
  3. 검증 성공시 SecurityContext 에 인증 정보 저장
  4. 필터체인으로 요청 전달

JWT 토큰을 파싱하고 검증하는 과정을 진행하며 이를 바탕으로 인증 정보를 생설해주는 필터 역할을 수행한다. 인증 정보를 SecurityContext에 저장해주기 때문에 권한 검증, 인가 처리가 가능 해진다.

JWTFilter 가 UsernamePasswordAuthenticationFilter보다 앞단에 위치하는 이유

  1. JWT 토큰으로 인증이 가능하도록 하기 위해
  2. UsernamePasswordAuthenticationFilter을 건너 뛰기 위해
  3. 모든 인증을 JWT기반으로 일원화 하기 위해

@RequiredArgsConstructor
public class JWTFilter extends OncePerRequestFilter{

	private final JWTUtil jwtUtil;

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException{
	
		String authorization = null;
		Cookie[]cookies = request.getCookies();
		for(Cookie cookie : cookies){
		
			if(cookie.getName().equals("Authorization")){
				authorization = cookie.getValue();
				
			}
		}
		if(authorization ==null){
			sout("token null");
			filterChain.doFilter(request,response);
		return;
		}
	String token =authorization;

	if(jwtUtil.isExpired(token)){
		sout("token expired");
		filterChain.doFilter(reqeust,response);
	}

	String username= jwtUtil.getUsername(token);
	String role = jwtUtil.getRole(token);


	UserDto userDto= new UserDto;
	userDto.setUsername(username);
	userDto.setRole(role);

	//UserDetail에 회원 정보 객체 담기
	CustomOAuth2User customOAuth2User = new CustomAuth2User(userDto);

	//스프링 시큐리티 인증 토큰 생성
	Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());

	SecurityContextHolder.getContext().setAuthentication(authToken);

	filterChaindoFilter(request,response);
	
	}
}
  1. 인증 객체로 부터 JWT 토큰을 쿠키나 헤더에서 추출
  2. JWT 토큰 유효 검증
  3. JWT 에서 사용자 정보 추출
  4. UserDetails 구현체인 CustomOAuth2User 사용자 정보 설정
  5. UsernamePasswordAuthenticationToken 생성
  6. SecurityContext 에 Authentication 설정
  7. 필터체인 계속 실행

    요청에서 JWT 토큰을 파싱해 사용자 인증 정보를 SecurityContext 에 다시 설정하는 작업 진행

UsernamePasswordAuthenticationToken 매개변수
1. 사용자 정보 (principal) : 인증된 사용자 정보
2. 패스워드 ( credentials) : 자격 증명
3. 권한목록 (authorities)

OAuth2 진행이므로 비밀번호는 null 로 채워졌다.



FilterChain 흐름
1. 1. WebAsyncManagerIntegrationFilter
2. SecurityContextPersistenceFilter
3. HeaderWriterFilter
4. CorsFilter
5. CsrfFilter
6. LogoutFilter
7. JWT Filter
8. UsernamePasswordAuthenticationFilter
9. RequestCacheAwareFilter
10. SecurityContextHolderAwareRequestFilter
11. AnonymousAuthenticationFilter
12. SessionManagementFilter
13. ExceptionTranslationFilter
14. FilterSecurityInterceptor

AuthenticationFilter

  • UsernamePasswordAuthenticationFilter
  • JwtAuthenticationFilter
  • AnonymousAuthenticationFilter

사용자의 입력 정보나 토큰 정보 등을 바탕으로 Authentication 객체를 생성한다

예를 들어 UsernamePasswordAuthenticationFilter 는 전달 받은 username,password 파라메터를 통해 UsernamePasswordAuthenticationToken 을 생성한다.

JwtAuthenticationFilter는 JWT 토큰을 파싱해서 그 정보로 JwtAuthenticationToken을 생성한다.

생서된 Authentication 객체는 AuthenticationManager AuthenticationProvider 에 사용된다.

profile
백엔드 개발자를 꿈꾸고 있습니다.

0개의 댓글