BE/Spring

[Spring Security] JWT - 3. JWT 임시 토큰 테스트 및 로그인 시 토큰 생성

멍목 2022. 6. 18. 17:53
반응형

이 포스팅은 아래의 강의를 참고하였으니 여기에서 공부하시는 것을 추천드립니다.

https://inf.run/tcLk

 

[무료] 스프링부트 시큐리티 & JWT 강의 - 인프런 | 강의

스프링부트 시큐리티에 대한 개념이 잡힙니다., - 강의 소개 | 인프런...

www.inflearn.com


1. JWT 임시 토큰을 만들어서 기본 흐름 확인하기

 

1) MyFilter3 자바 파일 수정하기

이전 포스팅에서 생성한 MyFilter3 자바 파일에 토큰을 확인하는 로직을 추가

package com.cos.security1.config.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class MyFilter3 implements Filter {

    // 토큰 값을 확인하는 필터
    // 토큰 생성 시점: 정상적으로 로그인 시, 토큰을 만들어주고 토큰을 알려줌 => 사용자가 요청할 때마다 header에 Authorization에 value값으로 토큰을 가지고 요청 => 그 때 토큰이 유효한 지 검증하는 로직
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        System.out.println("filter3");

        // POST 요청일 때만 확인
        if(req.getMethod().equals("POST")){


            String headerAuth = req.getHeader("Authorization");     // Header의 name이 'Authorization' 인 value
            System.out.println(headerAuth);

            if(headerAuth != null && headerAuth.equals("cos")){     // Authorization이 'cos' 면 토큰을 가지고 있는 것으로 간주
                chain.doFilter(req,res);
            }
            else{           // 아니라면 인증이 안 된것
                PrintWriter outPrintWriter = res.getWriter();
                outPrintWriter.println("인증X");
            }
        }
    }
}

 

2) RestControllerAPI에 아래의 함수 추가 (아무 Controller에 추가해도 상관 X)

@PostMapping("token")
public String Token(){
    return "<h1>token</h1>";
}

 

 

3) 테스트

a) header의 Authorization의 value에 cos가 아닌 경우

 

b) header의 Authorization의 value에 cos인 경우

 

참고) POST 요청 테스트 방법 : postman 이용(https://ajdahrdl.tistory.com/255)

 

개발한 API 테스트 도구 - postman

안녕하세요. 이번 포스팅에서는 개발한 API를 테스트하고 결과를 확인하는 도구인 postman에 대해서 포스팅 하겠습니다. 1. postman 가입 아래의 링크로 이동해서 편한 방법으로 가입 https://www.postman.c

ajdahrdl.tistory.com

 

 

2. 로그인 시, JWT 토큰을 생성해서 반환해주기

1) PrincipalDetails 생성

package com.cos.jwt.security1.config.jwtAuth;

import com.cos.jwt.security1.model.User;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

@Data
public class PrincipalDetails implements UserDetails {

    private User user;

    public PrincipalDetails(User user){
        this.user = user;
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        user.getRoleList().forEach(r->{
            authorities.add(()->r);
        });

        return authorities;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

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

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

 

2) PrincipalDetailsJwtService 생성

package com.cos.jwt.security1.config.jwtAuth;


import com.cos.jwt.security1.Repository.UserRepository;
import com.cos.jwt.security1.model.User;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;


@Service
@RequiredArgsConstructor
public class PrincipalDetailsJwtService implements UserDetailsService {

    private final UserRepository userRepository;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("jwtAuth.PrincipalDetailsService.loadUserByUsername");

        // **** 로그인 과정 **** //
        // 1. username과 password를 받고

        // 2. 정상인지 확인. authentcationManager로 로그인 시도를 하면 PrincipalDetailsJwtService의 loadUserByUsername이 실행됨

        // 3. PrincipalDetails를 세션에 담고(권한 관리를 위해서. 권한같은 정보가 없다면 세션이 필요없음)

        // 4. JWT 토큰을 만들고 응답

        User userEntity = userRepository.findByUsername(username);
        System.out.println("찾은 UserEntity : " + userEntity.toString());
        PrincipalDetails p = new PrincipalDetails(userEntity);

        return p;
    }
}

 

 

3) JwtAuthenticationFilter 생성

package com.cos.jwt.security1.config.jwtConfig;


import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.cos.jwt.security1.config.jwtAuth.PrincipalDetails;
import com.cos.jwt.security1.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;

// spring security에서 UsernamePasswordAuthenticationFilter 가 있는데
// login 요청 시, username, pw 전송 시, UsernamePasswordAuthenticationFilter가 동작함

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private final AuthenticationManager authenticationManager;

    // /login 요청을 하면 로그인 시도를 위해서 실행되는 함수
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        System.out.println("JwtAuthenticationFilter.attemptAuthentication : 로그인 시도중입니다");

        try{
            // 1. usernamee, pw 받기

            // request로 부터 온 데이터들 확인 방법
            /*
            BufferedReader br = request.getReader();

            String input = null;
            while(true){
                input = br.readLine();
                if(input == null){
                    break;
                }
                System.out.println(input);
            }
            */

            // request로 부터 온 데이터(JSON으로 보냈다는 가정 하에) 를 JSON 형태로 변환하기
            ObjectMapper objectMapper = new ObjectMapper();
            User user = objectMapper.readValue(request.getInputStream(), User.class);
            System.out.println(user.toString());
            System.out.println("========================================1");


            // 토큰 생성 (폼 로그인 시에는 자동으로 해줌)
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
            System.out.println("========================================2");


            // PrincipalDetailsJwtService의 loadUserByUsername() 함수가 실행됨
            Authentication authentication =  authenticationManager.authenticate(authenticationToken);
            System.out.println("========================================3");


            // 로그인을 성공했다는 뜻
            PrincipalDetails principal = (PrincipalDetails) authentication.getPrincipal();
            System.out.println("로그인 한 사용자 : " + principal.getUser().getUsername());
            System.out.println("========================================4");

            // authentication 객체가 session 영역에 저장
            // 리턴하는 이유는 권한 관리를 security가 대신 해주기 때문에 편하기 위해서.
            // JWT 토큰을 사용한다면 세션을 사용할 필요가 없지만 권한 관리 때문에 세션을 사용하는 것
            return authentication;
        } catch(IOException e){
            e.printStackTrace();
        }

        return null;
    }

    // attemptAuthentication 실행 후 인증이 정상적으로 완료되면 successfulAuthentication 함수가 실행됨
    // JWT 토큰을 만들어서 request 요청한 사용자에게 JWT 토큰을 주면 된다.
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        System.out.println("successfulAuthetication 실행 : 인증 완료란 뜻");
        PrincipalDetails principalDetails = (PrincipalDetails) authResult.getPrincipal();

        // JWT 토큰 만들기
        // RSA 방식이 아닌, Hash암호방식 (최근 이 방식을 더 많이 사용한다고 함)
        String jwtToken  = JWT.create()
                .withSubject("토큰 제목")       // 토큰 제목
                .withExpiresAt(new Date(System.currentTimeMillis()+(60000*10)))      // 토큰 만료시간 ms 기준 60초 * 10 (10분)
                .withClaim("id", principalDetails.getUser().getId())
                .withClaim("username", principalDetails.getUser().getUsername())
                .sign(Algorithm.HMAC512("cos"));

        response.addHeader("Authentication", "Bearer " + jwtToken);
    }
}

 

 

4) 결과 확인

 

반응형