[64일차]JWT유저인증, JWT토큰이용

유태형·2022년 7월 28일
1

코드스테이츠

목록 보기
64/77

오늘의 목표

  1. JWT유저인증
  2. JWT토큰이용
  3. FilterChain에 추가



내용

JWT 유저 인증

UserDetails를 구현한 클래스와 JpaRepository를 상속하고 findByUsername(String member)를 정의한 레포지토리, UserDetailsService를 구현한 서비스가 필요합니다. 해당 클래스는 이미 수차례 정의한 적이 있습니다.

JWT 로그인을 처리하기 위해서는 UsernamePasswordAuthenticationFilter를 상속하여 attemptAuthentication() successfulAuthentication() 메서드를 구현해야 합니다.

@RequiredArgsConstructor
public class 토큰생성필터 extends UsernamePasswordAuthenticationFilter{
	private final AuthenticationManager authenticationManager;
    
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException{
    	try{
        	ObjectMapper 매퍼 = new ObjectMapper();
            엔티티 엔티티 = 매퍼.readValue(request.getInputStream(),엔티티.class);
            
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(엔티티.getUsername(), 엔티티.getPassword());
            
            Authentication authentication = authenticationManager.authenticate(authenticationToken);
            
            return authentication;
        }catch(IOException e){
        	e.printStackTrace();
        }
        return null;
    }
    
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
    	UserDetails클래스 userDetals = (UserDetails클래스) authResult.getUserDetails();
        
        String 토큰 = JWT.create()
        	.withSubject("cos jwt token")
            .withExpiresAt(new Date(System.currentTimeMillis() + (60 * 1000 * 10)))
            .withClaim("id", userDetals.getMember().getId())
            .withClaim("username", userDetals.getMember().getUsername())
            .sign(Algorithm.HMAC512("cos_jwt_token"));
        response.addHeader("Authorization","Bearer " + 토큰);
    }
}
  • attemptAuthentication(HttpServletRequest, HttpServletResponse) : 사용자의 로그인 입력 정보를 받아들여 AuthenticationManager에게 전달하고 Authentication을 반환합니다.

  • ObjectMapper : 사용자로 부터의 JSON입력 정보를 객체로 매핑하기 위해 사용합니다.

  • UsernamePasswordAuthenticationToken : 사용자로부터 입력받은 ID와 비밀번호를 저장하는 인증 객체입니다.

  • successfulAuthentication() : 인증 성공시에 추가적인 처리를 지정할 수 있습니다. 위의 메서드는 토큰을 생성하여 응답에 추가하였습니다.

  • Authentication.getUserDetails() : UserDetails를 구현한 클래스의 객체를 반환합니다.

  • JWT.create() : JWT 토큰을 생성합니다.

  • JWT.withSubject() : JWT 토큰의 주제를 설정합니다.

  • JWT.withExpiresAt(new Date(System.currentTimeMillis() + (60 * 1000 * 10))) : 현재 시간으로 부터 10분(1000ms 60초 10분)후에 토큰이 만료됩니다.

  • JWT.withClaim("키","깂") : 키와 값 쌍을 토큰에 추가합니다.

  • JWT.sign(Algorithm.HMAC512("비밀키")) : 비밀키가지고 HMAC512 알고리즘으로 토큰의 Signature 부분을 암호화 합니다.(확인용)

  • response.addHeader("Authorization", "접두어 " + 토큰); : 응답 헤더의 Authorization에 접두어를 추가한 토큰을 추가합니다.




JWT 토큰 이용

public class 토큰이용필터 extends BasicAuthenticationFilter{
	private 레포지토리 레포지토리;
    public 토큰이용필터(AuthenticaitonManager authenticationManager, 레포지토리 레포지토리){
    	super(authenticationManager);
        this.레포지토리 = 레포지토리;
    }
    String 헤더 = request.getHeader("Authorization");
    
    if(헤더 == null || !해더.startsWith("접두어")){
    	chain.doFilter(request,response);
        return;
    }
    
    String 토큰 = 헤더.replace("접두어","");
    
    String username = JWT.require(Algorithm"HMAC512("비밀키")).build.verify(토큰).getClaim("username").asString();
    
    if(username != null){
    	엔티티 엔티티 = 레포지토리.findByUsername(username);
        
        UserDetails클래스 userDetails = new UserDetails클래스(엔티티);
        Authentication authenticaiton = new UsernamePasswordAuthenticationToken(principalDetails,null,principalDetails.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        
        chain.doFilter(request,response);
    }
    
    super.doFilterInternal(request,response,chain);
}
  • doFilterInternal() : BasicAuthenticationFilter는 토큰 인증시 사용되는 필터 이며 메서드는 토큰인증을 위해 오버라이딩합니다.
  • HttpServletRequest.getHeader("Authroization") : 헤더의 Authoriaiotn 파라미터의 값을 읽어들입니다. 이후 "접두어"가 필터가 찾는 것이 맞는지 확인하고 제거합니다.
  • JWT.require(Alogrithm.HMAC512("비밀키")).build().verify(토큰).getClaim("username"),asString() : 토큰을 비밀키로 디코딩 하여 username의 값을 문자열로 불러옵니다.

만약 username이 존재한다면 엔티티를 찾아 UserDetails객체로 만들어 Authentication을 생성하여 SecurityContextHolder에 저장하고 다음 필터를 실행합니다.




FilterChain 추가

필터를 만들기만 한다고 원하는 때에 실행되지는 않습니다. 필터를 필터 체인에 추가하여야만 원하는 때에 필터가 작동합니다.
필터는 설정클래스의 SecurityFilterChain(스프링 필터체인)빈 객체에 추가합니다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class 설정클래스{
	private final CorsFilter corsFilter;
    private final 레포지토리 레포지토리;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    	http.csrf().disable()
        ...
        .aply(new CustomDsl())
        .and()
        ...
    }
    
    public class CustomDsl extends AbstractHttpConfigurer<CustomDsl,HttpSecurity>{
    	public void configure(HttpSecurity builder) throws Exception{
        	AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
            builder
            	.addFilter(corsFilter)
                .addFilter(new 토큰생성필터(authenticationManager));
                .addFilter(new 토큰이용필터(authenticationManager, 레포지토리));
                
        }
    }
}
  • class customDsl extends AbstarctHttpConfigurer<CustomDsl, HttpSecurity> : SpringSecurity가 버전업을 하면서 더이상 직접적으로 addFilter()메서드로 추가하는 것이 deprecated 되었습니다. 따라서 AbstractHttpConfigurer 클래스를 상속하는 클래스를 만들어 apply() 합니다.
  • configure(HttpSecurity) : 필터에 추가할 내용을 정의합니다.
  • HttpSecurity.getSharedObject(AuthenticationManager.class) : AuthenticationManager을 반환 받습니다.
  • HttpSecurity.addFilter(new 필터) : 필터를 필터체인에 추가합니다.



후기

클라이언트로 부터 JSON정보를 받아와 토큰으로 만들고 만든 토큰을 이용하여 인증도 해보았습니다. 세션과 함께 토큰도 많이 사용되므로 알아두는것이 좋습니다.




GitHub

https://github.com/ds02168/CodeStates_Spring/tree/main/section4-week1-THR

profile
오늘도 내일도 화이팅!

0개의 댓글