[실습편] Spring Boot 에서 Spring Security 기반 JWT 적용하기

mallin·2022년 2월 11일
1

JWT

목록 보기
2/2
post-thumbnail

전체 소스는 깃허브에서 확인하실 수 있습니다.
👉 https://github.com/soyeon207/velog_example/tree/master/jwt-security-server

구현 환경 설명

프레임워크는 스프링 부트 v2.6.2 를 언어는 자바 v.8을 DB 는 H2 를 사용합니다.

구현된 내용

API 이름설명권한
/api/users/signup회원가입 API누구나 가능
/api/users/login로그인 API누구나 가능
/api/users/my마이페이지 API회원가입 한 유저만 가능
/api/users/admin어드민 API어드민 유저만 가능
  1. 로그인 하는 경우 response 로 JWT 토큰 발급
  2. JWT 토큰을 헤더에 X-AUTH-TOKEN 으로 넣고 API 호출하면 권한 판단 하는 프로세스로 진행합니다.

0. 사전세팅

사용할 dependencies 들을 build.gradle 에 추가합니다.

0-1. build.gradle

dependencies {
    implementation "io.jsonwebtoken:jjwt:0.9.1"
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'

    annotationProcessor 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
}

jwt 를 사용하기 위해서는 🌟io.jsonwebtoken:jjwt:0.9.1🌟 라이브러리를 추가해줘야 합니다.

0-2. application.yml

spring:
  h2:
    console:
      enabled: true

  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:

H2 데이터베이스를 사용하기 위한 설정을 application.yml 에 해줍니다.

1. JWT 관련 설정

1-1. WebSecurityConfig.java

👉 전체코드보기

@EnableGlobalMethodSecurity(prePostEnabled = true)

➡️ 해당 프로젝트에서 권한 허용을 @PreAuthorize 로 해주는데 이 어노테이션을 사용하기 위해선 EnableGlobalMethodSecurity 의 prePostEnabled 를 가능하도록 true 로 설정해줘야 합니다.

@Override
public void configure(WebSecurity web) {
    web
            .ignoring()
            .antMatchers("/h2-console/**"); // h2-console
}

➡️ h2-console 은 스프링 시큐리티가 적용되지 않도록 설정
해당 코드를 추가하지 않는 경우 h2-console 에 접속했을 때 아래처럼 보여집니다.

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 스프링 시큐리티가 세션 쿠키 방식으로 동작하지 않도록 설정

            .and()
            .addFilterBefore(new JwtAuthenticationFilter(jwtConfig), UsernamePasswordAuthenticationFilter.class);
}

➡️ sessionCreationPolicy 로 스프링시큐리티 세션 정책을 설정할 수 있습니다. sessionCreationPolicy 에 들어갈 수 있는 값은 아래와 같습니다.

내용설명
SessionCreationPolicy.ALWAYS항상 세션 생성
SessionCreationPolicy.IF_REQUIRED필요시 세션 생성(기본)
SessionCreationPolicy.NEVER스프링 시큐리티는 생성하지 않지만, 기존에 존재하는 경우 사용
SessionCreationPolicy.STATELESS스프링 시큐리티가 생성하지도 않고, 기존의 것을 사용하지도 않음

여기서 설정해준 STATELESS 는 더이상 세션쿠키 방식의 인증 방식을 사용하지 않는다는 의미로, 인증이 성공되었을 때 인증정보가 담겨 있는 SecurityContext 객체를 세션에 저장하는 역할을 하는 SecurityContextPersistenceFilter 는 SecurityContext 객체를 세션에 저장하지 않습니다. 그렇기 때문에 매번 인증을 받아야 하는 상태가 됩니다. JWT 토큰을 사용하는 경우 토큰을 넘겨서 매번 인증을 해줘야 하기 때문에 세션 로그인 방식을 사용하지 않기 위해 STATELESS 로 설정해줍니다.

➡️ 인증을 처리하는 UsernamePasswordAuthenticationFilter 전에 JWT 관련 커스텀 된 필터가 실행되도록 설정해줍니다.

1-2. JwtAuthenticationFilter.java

👉 전체코드보기

public class JwtAuthenticationFilter extends GenericFilterBean {

➡️ GenericFilterBean 는 스프링 시큐리티 에서 CustomerFilter 를 만들기 위해 제공하는 인터페이스로 doFilter 메소드만 구현해주면 됩니다.

1-3. JwtConfig.java

👉 전체코드보기

@Value("${JWT_TOKEN}")
private String secretKey;

@Value("${JWT_EXPIRE_TIME}")
private long expireTime;

➡️ signature 에 들어갈 secret 값은 base64 로 인코딩 해서 넣어줍니다.
https://www.base64encode.org/ 해당 사이트를 이용하거나 터미널에서 echo 'soeyon-velog-jwt-spring-boot-training-secret-key' |base64 해당 명령어로 간단하게 base64 로 인코딩 해줄 수 있습니다.
그리고 해당 값을 서버에 Environment 값으로 지정해줍니다.

2. Controller 구현

UsersController.java
👉 전체코드보기

  @PreAuthorize("isAuthenticated() and hasRole('ROLE_ADMIN')")

➡️ 스프링 시큐리티에서 제공하는 권한을 통제하기 위한 어노테이션으로 사용할 수 있는 표현식은 다음과 같습니다.

표현식설명
hasRole(role1)현재 사용자 권한이 role1 과 동일한 경우 true
hasAnyRole(role1,role2)현재 사용자 권한이 role1, role2 중 있는 경우 true
isAnonymous()사용자가 인증되지 않은 경우 true
isAuthenticated()사용자가 인증된 경우 true
@PostMapping("/login")
public String login(UserRequest userRequest) {
  UserDTO users = usersService.findByEmailAndPassword(userRequest.getEmail(), userRequest.getPassWord());
  return jwtConfig.createToken(users.getEmail(), Arrays.asList(users.getUserRole().getValue()));
}

➡️ 로그인 하는 경우 JWT Token 을 리턴해줍니다.

3. 포스트맨을 이용해서 테스트 해보기

🌟 실제 테스트 해보기 전에 Environment Variables 설정을 해주세요. 설정하는 방법은 1-3. JwtConfig.java 에서 확인하실 수 있습니다.

회원가입 API

/api/users/signup

email, passWord, userRole 을 넘겨서 회원가입해줍니다.

로그인 API

/api/users/login

위에서 회원가입 했던 내용을 바탕으로 로그인해줍니다. 정상적으로 로그인된 경우 회원 정보를 JWT 토큰으로 변환해서 response 로 리턴해줍니다.

마이 페이지 API

/api/users/my

Header 에 X-AUTH-TOKEN 을 키로 로그인 API 를 호출했을 때 리턴되었던 JWT TOKEN 을 값으로 넣어서 API 를 호출해줍니다. 해당 토큰에 일치하는 회원 정보가 나온 걸 확인할 수 있습니다.

정상적인 토큰을 넣은 경우

정상적이지 않은 토큰을 넣은 경우

어드민 페이지 API

/api/users/admin

어드민 페이지 API 는 어드민 ROLE 인 사람만 접근할 수 있습니다.

유저 JWT Token 으로 API 호출했을 때
유저 토큰으로 접근했을 때에는 403 Forbidden 오류가 발생하며 접근이 되지 않습니다.

어드민 JWT Token 으로 호출했을 때
어드민 JWT 토큰으로 접근하는 경우에는 정상적으로 데이터가 보이는 걸 확인 할 수 있습니다.

원래는 JWT Security 를 정리하려고 했는데 .. Spring Security 정리부터 시작하다 보니 실습 정리하는데까지 엄청 오랜 시간이 걸렸네요 🥲
여기까지 읽어주셔서 감사합니다. 🙇🏻‍♀️

레퍼런스

JSESSIONID의 생성에 대해서

0개의 댓글