본문 바로가기
개발공부/JAVA Spring

[Spring Boot] JWT 튜토리얼 [chap.1~3] - 인프런 강의

by bzerome240 2023. 7. 1.

인프런 강의를 보고 실습하고 정리하기! (무료입니다~)

회원가입, 로그인, 권한 로직을 실제로 구현해보고 Spring Security의 인증과 인가 로직을 경험해본다.

  • 인증 :  Request를 보낸 User가 누구인지? 올바른 User인지 확인하는 과정
  • 인가 : Request가 수행하고자 하는 행동이 허가된 행동인지를 확인하는 과정
 

[무료] Spring Boot JWT Tutorial - 인프런 | 강의

Spring Boot, Spring Security, JWT를 이용한 튜토리얼을 통해 인증과 인가에 대한 기초 지식을 쉽고 빠르게 학습할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

 

JWT

: json 객체를 사용해서 토큰 저장하는 웹토큰

  • 가볍게 적용할 수 있어서 사이드 프로젝트 시 유용하다.
  • 장점 : 데이터 스토어에 대한 의존성이 없어 수평 확장 유리
  • 단점 : palyoad의 정보가 많아지면 네트워크 사용량 증가, 토큰이 클라이언트에 저장됨.

 

구조

  • Header : 알고리즘 정보
  • Payload : 실제로 사용할 정보
  • Signature : 토큰 유효성 검증하는 문자열

 

 


개발환경

- Java11
- jdk11
- Spring Boot 2.7.13
- Gradle
- 종속성
  - Lombok - 편의
  - Spring Web
  - Spring Data JPA
  - Spring Security
  - H2 - 메모리DB
  - Validation

 


 

프로젝트 생성

** lombok을 사용하기 때문에 설정 > 어노테이션 처리 활성화 켜두는게 편함

 

controller > HelloController.java

@RestController
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/hello")
    public ResponseEntity<String> hello() {
        return ResponseEntity.ok("hello");
    }
}

 

👉 http://localhost:8080/api/hello 를 호출하면 401 unauthorized 에러가 발생한다.

 

401 unauthorized 해결을 위한 security 설정

config > SecurityConfig.java

[TroubleShooting] 강의에 나오는 WebSecurityConfigurerAdapter 는 지원 중단됐다.

@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/api/hello").permitAll() // 해당 Request는 허용한다.
                .anyRequest().authenticated();
        return http.build();
    }
}

 

👉 200 성공 응답을 받을 수 있다.

 


 

Datasource, JPA 설정

resources > application.properties -> application.yml 로 변경한다 (읽기 쉬움)

- ddl-auto: create-drop : SessionFactory가 시작될 때 Drop, Create, Alter / 종료될 때 Drop

spring:

  h2:
    console:
      enabled: true

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

  jpa:
    defer-datasource-initialization: true
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: create-drop
    properties:
      hibernate:
        format_sql: true
        show_sql: true

logging:
  level:
    me.silvernine: DEBUG

 


 

Entity 생성

entity > Autority.java, Users.java 클래스 생성

 

resources > data.sql

ddl-auto: create-drop 이므로 서버가 실행할 때마다 data.sql 쿼리들이 실행된다

 


 

H2 Console 결과 확인

SecurityConfig.java

h2-console 하위 모든 요청들, 파비콘 관련 요청은 Spring Security 로직을 수행하지 않도록 추가

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        // h2-console 하위 모든 요청들, 파비콘 관련 요청은 Spring Security 로직을 수행하지 않는다
        return (web) -> web.ignoring().antMatchers("/h2-console/**", "/favicon.ico");
    }

 

[TroubleShooting]    

data.sql 에서 insert문 실행 시 user 테이블을 찾을 수 없다고 에러발생

-> H2 Database가 2.1.212 버전에서 user 키워드가 예약어로 지정되어 해당 버전 이후부턴 user라는 테이블 생성이 이제 안된다. -> user 테이블명을 다른거로 바꿔줌.

-> 그래도 에러발생 

-> Spring Boot 2.5버전부터 data.sql은 Hibernate 초기화 되기 전에 실행되는 내용이 추가되었다.

  jpa:
    defer-datasource-initialization: true

 

 

localhost:8080/h2-console

👉 h2 DB에 연결할 수 있는 화면이 나옴

[TroubleShooting] 

h2 1.4.198 이후 버전부터는 보안 문제로 데이터베이스가 자동으로 생성되지 않기 때문에 연결 시 에러 발생

c:/Users/사용자 경로로가서 test.mv.db 파일을 생성해준다.

연결 성공! 그런데 users 테이블이 보이지 않는다.

 

음 강의를 다시보니 JDBC URL이 나와 다르다! ;;;; 

 

👉 data.sql 의 쿼리내용이 잘 들어와있는지 확인

잘 들어가 있네요 하하..

 


 

JWT 설정 추가

 

application.yml 에 jwt 설정 추가

  • HS512 알고리즘을 사용하기 때문에 Secret Key는 64 Byte 이상이 돼야한다.
  • 토큰 만료시간: 86400초 설정
jwt:
  header: Authorization
  #HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다.
  #echo 'silvernine-tech-spring-boot-jwt-tutorial-secret-silvernine-tech-spring-boot-jwt-tutorial-secret'|base64
  secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK
  token-validity-in-seconds: 86400

 

build.gradle 에 JWT 의존성 추가

    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
    runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

 


 

JWT 관련 코드 개발

 

jwt > TokenProvider.java

  • 토큰의 생성, 토큰의 유효성 검증
  • InitializingBean 을 implements하고 afterPropertiesSet을 오버라이드 - 빈이 생성되고 주입을 받은 후에 secret값을 Base64 Decode해서 key 변수에 할당
  • createToken() : Authentication객체의 권한정보를 이용해서 토큰 생성
  • getAuthentication() : Token에 담겨있는 정보를 이용해 Authentication 객체를 리턴
  • validateToken() : 토큰의 유효성 검증 수행

 

jwt > JwtFilter.java

  • JWT를 위한 커스텀 필터 만들기
  • GenericFilterBean을 extends해서 doFilter 오버라이드, 실제 필터링 로직은 doFilter 내부에 작성
  • doFilter() : 토큰의 인증정보를 SecurityContext에 저장
  • resolveToken() : Request Header에서 토큰 정보를 꺼내오기

 

jwt > JwtSecurityConfig.java

  • TokenProvider, JwtFilter를 SecurityConfig에 적용
  • SecurityConfigurerAdapter를 extends하고 TokenProvider를 주입받아서 JwtFilter를 통해 Seurity로직에 필터 등록

 

jwt > JwtAuthenticationEntryPoint.java

  • 유효한 자격증명을 제공하지 않고 접근하려 할 때 401 unauthorized 에러 리턴

 

jwt > JwtAccessDeniedHandler

  • 필요한 권한이 존재하지 안흔ㄴ 경우 403 forbidden 에러 리턴

 


 

SecurityConfig 추가

 

SecurityConfig.java

  • TokenProvider, JwtAuthenticationEntryPoint, JwtAccessDeniedHandler 주입
  • passwordEncoder() : BCryptPasswordEncoder 사용
  • configure()
    • csrf 설정 disable
    • Exception 을 핸들링할때 JwtAuthenticationEntryPoint, JwtAccessDeniedHandler 추가
    • h2-console을 위한 설정 추가
    • 세션 설정을 stateless로 설정
    • /api/authenticate, /api/signup - permitAll() 설정
    • JwtSecurityConfig 클래스 적용
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
    private final TokenProvider tokenProvider;
    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;

    public SecurityConfig(
            TokenProvider tokenProvider,
            JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
            JwtAccessDeniedHandler jwtAccessDeniedHandler
    ) {
        this.tokenProvider = tokenProvider;
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        // h2-console 하위 모든 요청들, 파비콘 관련 요청은 Spring Security 로직을 수행하지 않는다
        return (web) -> web.ignoring().antMatchers("/h2-console/**", "/favicon.ico");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // token을 사용하는 방식이기 때문에 csrf를 disable합니다.
        http
                .csrf().disable();

        http
                .authorizeRequests()
                .antMatchers("/api/hello", "/api/authenticate", "/api/signup").permitAll() // 해당 Request는 허용한다.
                .requestMatchers(PathRequest.toH2Console()).permitAll()
                .anyRequest().authenticated();

        // exception handling for jwt
        http
                .exceptionHandling()
                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                .accessDeniedHandler(jwtAccessDeniedHandler);

        // 세션을 사용하지 않기 때문에 STATELESS로 설정
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        // enable h2-console
        http
                .headers().frameOptions().sameOrigin();

        // Apply JWT
        http
                .apply(new JwtSecurityConfig(tokenProvider));

        return http.build();
    }
}

 


 

참고 

 

강의 Git 

 

GitHub - SilverNine/spring-boot-jwt-tutorial

Contribute to SilverNine/spring-boot-jwt-tutorial development by creating an account on GitHub.

github.com

 

websecurityconfigureradapter 지원 중단에 대한 대응

 

WebSecurityConfigurerAdapter Deprecated 대응법

WebSecurityConfigurerAdapter란? 스프링 시큐리티를 사용하면 기본적인 시큐리티 설정을 하기 위해서 WebSecurityConfigurerAdapter라는 추상 클래스를 상속하고, configure 메서드를 오버라이드하여 설정하였습

covenant.tistory.com

 

트러블슈팅2

 

[H2 in-memory Database] Table not found (this database is empty) 에러 해결 방법

서론 jwt 인증 공부 중 다음과 같은 에러를 만났다. 해당 에러를 해결하는 과정과 발생한 이유에 대해 간략히 정리해놓으려 한다. 사용 기술 스택 Spring Boot: version 2.7.0 Spring Data JPA Spring Security Lombo

jeongkyun-it.tistory.com

 

728x90
반응형

'개발공부 > JAVA Spring' 카테고리의 다른 글

Spring MVC vs WebFlux 패턴 비교하기  (0) 2023.08.13
[SpringBoot] Spring Batch 5  (1) 2023.07.20
[SpringBoot 통신] RestTemplate, WebClient  (0) 2023.05.09
객체지향 패러다임  (0) 2023.04.12
[Spring Boot] 예외처리  (0) 2023.03.28

댓글