[63일차]JWT 개념, JWT security, JWT Bearer

유태형·2022년 7월 27일
0

코드스테이츠

목록 보기
63/77

오늘의 목표

  1. JWT 개념
  2. JWT Security
  3. JWT Bearer



내용

JWT 개념

JWT(JSON Web Token)은 무연결성을 지키면서도 사용자의 인증 절차를 세션처럼 한번 수행하면 계속 인증된 정보를 사용하기 위해 사용합니다.
JWT는 세션과 다른 큰 차이점이 하나 존재하는데, 그건 서버에 인증 정보를 저장하는 것이 아니라, 클라이언트에 저장한다는 점입니다.

외부 공격으로 부터 안전하기 위하여 세션을 사용하지만, 클라이언트가 많아지면 서버의 과부화 문제, 여러 서버로 분산된 서비스의 경우 세션에 인증정보를 저장하기에 현실적으로 어려우므로 클라이언트에 암호화하여 저장하는 토큰을 사용합니다.

토큰은 엑세스 토큰(Access Token)리프레시 토큰(Refresh Token) 두가지가 존재합니다.

클라이언트가 서버에서 인증을 하면, 서버는 클라이언트에 위의 두가지 토큰을 저장합니다.

엑세스 토큰(Access Token)은 실제 권한이 필요한 리소스에 접근가능 한 권한을 부여하는데 사용합니다. 외부 공격자가 해당 토큰을 사용하여 클라이언트 인척 하는것을 방지하기 위해 짧은 유효기간을 가집니다.

리프레시 토큰은 유효기간이 짧은 엑세스 토큰이 유효기간이 만료된다면 엑세스 토큰을 다시 발급받는데 사용합니다.(다시 로그인할 필요x)
리프레시 토큰도 탈취당하면 큰 문제가 될 수 있으므로 리프레시 토큰을 사용하지 않는 서버도 많이 존재합니다.

JWT토큰은 3부분으로 구성됩니다.

  1. Header
  • header는 어떤 종류의 토큰인지, 어떤 알고리즘이 사용되었는지 알려줍니다.
{
	"alg": "알고리즘",
    "type": "JWT"
}
  1. Payload
  • 어떤 정보에 접근 가능한지에 대한 권한을 담을 수 있고, 사용자 이름등 담을 수 있지만 너무 민감한 정보는 담지 않는 것이 좋습니다.
{
	"sub": "접근권한",
    "name" : "사용자명",
    "ist": 123456
}
  1. Signature
  • header와 payload를 어떻게 인코딩 할 것인지, 비밀 키(+salt)를 지정하여 암호화 합니다.
HMACSHA256(base64UrlEncode(header) + '-' + base64UrlEncode(payload), secret);


JWT의 장단점

장점단점
stateless, scalability 특징을 가짐payload는 해독 쉬움
안전함토큰이 길어지면 네트워크 과부화
어디서나 생성 가능토큰은 자동으로 삭제 안됨
권한 부여에 용이토큰은 어딘가에 저장되야 함



JWT Security

JWT를 사용하기 위해선 build.gradle에 외부 의존 라이브러리를 추가하여야 합니다.

dependencies{
	implementation 'com.auth0:java-jwt:3.19.2'
    ...
}


모델 설정

@Data
@Entity
public class 엔티티{
	@Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String password;
    private String roles;
    
    public List<String> getRoleList(){
    	if(this.roles.length() > 0){
        	return Arrays.asList(this.roles.split(",");
        }
        return new ArrayList<>();
    }
}

이전에 사용하던 엔티티에서 roles 필드가 취가되고 getRoleList() 메서드가 추가되었습니다. roles 필드엔 권한들이 권한1, 권한2, 권한3, ... 문자열로 저장되고 getRoleList()는 문자열을 ,로 나누어 문자열 리스트로 반환합니다.



설정 클래스

@Configuration
@EnableWebSecurity
public class 설정클래스{
	@Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    	http.csrf().disable();
        http.headers().frameOptions().disable();
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .formLogin().disable()
        .httpBasic().disable()
        .authorizeRequests()
        .antMatcher("/경로1/경로1-1/user/**")
        .access("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
        ...
        .anyRequest().permitAll();
        
        return http.build();
    }
}
  • http.csrf().disable() : form태그 외 다른 요청은 무시합니다.
  • headers().frameOptions().disable() : h2 연결할때 필요합니다.
  • .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) : 로그인 인증시 세션을 만들지 않고 토큰을 발행합니다. 로그인 인증시 세션을 생성하지 않을 뿐, 다른 작업에선 그대로 세션을 생성합니다.
  • formLogin().disable() : form Login을 사용하지 않습니다.
  • httpBasic().disable() : http통신을 사용하지 않고 https통신을 사용합니다. https사용시 username과 password가 head의 Authorization에 포함되어 전달됩니다.


권한 설정 필터

@Configuration
public class Cors설정{
	@Bean
    public CorsFilter corsFilter(){
    	UrlBasedCorsConfigurationSource 소스 = new UrlBasedCorsConfigurationSource();
        CorsConfiguration 설정 = new CorsConfiguration();
        설정.setAllowCredentials(true);
        설정.addAllowedOrigin("*");
        설정.addAllowedHeader("*");
        설정.addAllowedMehtod("*");
        소스.registerCorsConfiguration("/경로/**", 설정);
        
        return new CorsFilter(소스);
    }
}
  • .setAllowCredentials(true) : CORS접근 요청을 허용합니다.
  • .setAllowedOrigin("*") : 모든 IP에 대한 응답을 허용합니다.
  • .setAllowedHeader("*") : 모든 header에 응답을 허용합니다.
  • .setAllowedMehtod("*") : GET,POST,PATCH,UPDATE,DELETE등 모든 http메서드 요청을 허용합니다.
  • .registerCorsConfiguration("/경로/**",설정); : 해당 경로의 하위경로들에 해당 설정을 적용합니다.

CorsFilter는 필터로써 빈 객체로 등록하여 SpringContainer가 자동으로 생성은 시켜주지만 Filter를 SpringSecurity에 적용하기 위해서는 설정클래스의 FilterChain 빈에 등록을 해야만 합니다.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class 설정클래스{
	private final CorsFilter corsFilter;
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    	...
        .and()
        .addFilter(corsFilter)
    }
}

.addFilter(corsFilter) : SecurityFilterChain에 해당 필터를 추가합니다.




JWT Bearer

세션, 쿠키은 사용자를 확인 한 다음 Session ID를 발급하여 클라이언트에 저장합니다. 이후 Session ID에 해당하는 사용자이름, 권한등의 정보를 이용하여 사용자 인증을 생략합니다.

하지만 TOKEN방식은 로그인시 클라이언트에 발급을 하고 단순히 토큰에 권한정보를 담고 있어 서버에 따로 정보를 저장할 필요가 없습니다.

BearerJWT 또는 OAuth에 대한 토큰을 사용합니다.



설정클래스에 등록

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class 설정클래스{
	
   	...
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    	http.addFilterBefore(new 필터(), SecurityFilterChain내필터.class);
        //http.addFilterAfter(new 필터(), SecurityFilterChain내필터.class);
        ...
    }
}

.addFilterBefore() .addFilterAfter() 는 필터 체인에 필터를 추가합니다. 이때 매개변수 인자로 추가할 필터, 기준이될 필터(존재 해야함)를 지정합니다. 메서드명 에서 알수 있듯이 Before는 바로앞에, After는 바로뒤에 필터를 추가합니다.

public class 필터 implements Filter{
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOExceptoin, SevletException{
    	HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResopnse) response;
        
        res.setCharacterEncoding("UTF-8");
        if(req.getMethod().equals("HTTP메서드")){
        	String headerAuth = req.getHeader("Authorization");
            
            if(headerAuth.equals("Authorization값")){
            	chain.doFilter(req,res);
            }else{
            	PrintWriter writer = res.getWriter();
                writer.println("인증 실패");
            }
        }
    }
}

HttpServletRequest, HttpServletResponse는 ServletRequest와 ServletResponse를 상속받아 다시 다운캐스팅이 가능합니다.
해당하는 HTTP메서드가 일치하고 header의 Authroization필드의 값이 동일하면 다음 필터를 실행하고 다를시 에러를 발생시킵니다.




후기

JWT는 이전에 해본적이 없지만, 많이 언급되어 이미 현업에서 많이 사용되고 있겠구나 예상은 했었습니다. 쿠키,세션과 더불어 오늘은 토큰에 대하여 학습하였고 클라우드 컴퓨팅의 발전, 분산 시스템의 도입 확대 등으로 앞으로 더욱더 많이 사용될 방식이겠구나 싶습니다.




GitHub

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

profile
오늘도 내일도 화이팅!

0개의 댓글