본문 바로가기

IT 공부

9. JWT 및 SpringSecurity


JWT란?


 

JSON 객체를 사용해서 토큰자체에 정보들을 저장하고 있는 Web Token이라고 정의 할 수 있다

JWT는 굉장히 가벼워서 쉽게 적용이 가능해서 사이드 프로젝트에서 유용하게 사용가능

 


JWT의 구성


Header : Signature를 해싱하기 위한 알고리즘 정보를 담음

 

Payload : 서버와 클라이언트가 주고받는 시스템에서 실제로 사용 될 정보에 대한 내용들을 담고 있다

 

Signature : 토큰의 유효성 검증을 위한 문자열, 해당 문자열로 서버에서 토큰 유효성을 확인한다

 


JWT의 장점


중앙의 인증서버, 데이터 스토어에 대한 의존성이 없음, 시스템 수평 확장 유리

URL , Cookie Header에서 사용가능


JWT의 단점


Payload의 정보가 많아지면 네트워크 사용량이 증가, 설계 고려가 필요

토큰이 클라이언트에 저장, 서버에서 클라이언트의 토큰을 조작할 수 없음

 

 

 


위의 간략한 JWT에 대한 설명을 보았다

 

하지만 JWT를 사용하기 위해서는 먼저 Spring Security에 대해서 알아야할 필요가 있다

 


Spring Security 예제


 

localhost:8080 url 입력 시 웹 페이지

 

↑ localhost:8080을 입력시 Controller의 GetMapping("/") 의 매핑값을 불러오게 되고

사진에는 없지만 해당 코드는 index 페이지를 불러오게 지정되어 있다

그리고 결과가 잘 나오는 모습을 보여준다

 

localhost:8080/user 입력 시 웹 페이지

 

↑ localhost:8080/user을 입력시 Controller 에서는 GetMapping("/user")의 매핑값인 user 페이지를 불러와야하는데

보다시피 이동한 페이지의 url은 loginForm으로 이동하게 되어있다 

 

왜 그런 것일까??

 

그 이유는 바로 Spring Security 라는 프레임워크에서 해당 url의 접근 권한을 체크 한 뒤 접근 권한이 없을 때 인증을 요청하기 때문이다

 

 

그렇다면 Spring Security에 대해서 알아보자

 

 

먼저 SpringBoot 기준 Spring Security를 gradle로 받아올 필요가 있다

 

build.gradle

 

현재 셋팅 된 모습을 확인할 수 있다

 

 

현재 클래스 목록

 

먼저 클래스의 목록이다

 

간단하게 요약하자면


1. PrincipalDetails : UserDetails 을 상속받는 클래스

2. PrincipalDetailsService : UserDetailsService 를 상속받는 클래스

3. SecurityConfig : SpringSecurity에 대한 설정을 하는 클래스

4. WebMvcConfig : WebMvcConfigurer를 상속받아 View페이지로 이동할 수 있는 설정을 하는 클래스

5. UserRepository : Jpa 관련 클래스


먼저 WebMvcConfig를 보도록 하자

 

WebMvcConfigurer.class

WebMvcConfigurer를 상속받고 있는 모습을 볼 수 있다

 

현재 뷰단에서는 Mustacher를 사용하고 있는데

 

해당 뷰의 인코딩 작업 및 Prefix, Suffix 경로를 잡아서

 

매개변수인 registry에 넣어주는 모습을 볼 수있다


자, 이제 왜 localhost:8080/user 라는 url에 접근을 시도했을 때

 

url이 localhost:8080/loginForm으로 갔는지를 확인해보기 위해서

 

PrincipalDetail 클래스를 보도록하자

 

 

 

PrincipalDetails 클래스에서 주목해야할 점은 세 가지가 있는데

 

첫번째는 UserDetails를 상속받았다는 점과 해당 클래스의 메소드들을 전부 오버라이딩한다는 점

 

두번째는 생성자

 

세번째는 GrantedAuthority 에 들어가는 데이터이다

 

 


UserDetails

 

UserDetails를 상속 받게 되고 해당 클래스 안에 있는 메소드들을 전부 오버라이딩 해서 재정의 해줘야한다

 

다른 메소드는 크게 신경쓸 필요가 없지만 

 

생성자와 GrantedAuthority에 들어가는 데이터는 한 번 눈여겨 볼만 하다

 

먼저 GrantedAuthority를 보도록 하자

 

해당 메소드의 반환형은 Colleciton으로 해당 메소드의 반환하는 데이터는 Role

권한에 대한 데이터를 반환해서 체크해줘야 할 필요가 있다

 

하지만 우리가 만든 Entity 클래스에 있는 DB의 권한 데이터는 현재 String 타입으로 Collection에는 바로 들어가지 못한다

 

그렇기 때문에 위의 형식을 띄게 된다

 

미리 만들어 놓은 Entity 클래스 User에서 getRole로 권한을 가져와서 해당 메소드에 넣어주면 된다

 

 

getAuthority는 GrantedAuthority에 만들어져있는 메소드를 가져와서 사용하면 된다

 

 

 

그럼 이제 생성자를 알아보자

 

생성자

 

생성자에서는 Entity 클래스인 User 클래스를 매개변수로 넣어서 해당 유저를 PrincipalDetails의 User 클래스에 적용시킬 수 있도록 한다


생성자라는 것은 어딘가에서 만들어서 적용시켜야하는데 이것을 어디서 사용했을까?

 

그건 바로 2번 클래스인 PrincipalDetailService 에서 생성자를 불러와 사용하게 됩니다

 

PrincipalDetailsService

 

UserDetailsService를 상속받은 PrincipalDetailsService 이고

 

여기서 상속 받아 loadUserByUsername 메소드를 재정의한다

 

그렇다면 상속받은 UserDetailsService를 들여보도록하자

 

UserDetailsService

 

해당 인터페이스에는 loadUserByUsername이라는 메소드가 존재하고

 

해당 메소드는 아까봤던 UserDetails를 반환하고 username을 매개변수로 받고 있다

 

여기서 username은 권한 체크를 할 때 들어가는 로그인 페이지에서 넘어오는 id 값을 넣게 된다

 

그렇다면 다시 Service 클래스를 보도록 하자

 

 

loadUserByUsername

다시 해당 메소드를 보도록 하자

 

첫 줄에 User 클래스에 userRepository의 findByUsername 메소드에 username 매개변수를 가져와서 넣어서

결과값을 넣어주도록 한다

 

그러면 5번 클래스였던 UserRepository 클래스를 보도록 하자

 

UserRepository

 

UserRepository 인터페이스를 보면 JpaReposiotry라는 클래스를 상속받고

제네릭을 통해 앞에는 Entity가 뒤에는 Entity의 Id값 (PK) 로 선언된 데이터의 자료형이 들어가게 되고

나는 Entity가 int 형 데이터로 선언되어 있어서 Integer 객체를 반환하도록 설정했다

 

그리고 기존에 JpaRepository를 상속받으면 안에 기본적인 CRUD 메소드는 만들어 져있지만

 

findBy 라는 메소드명 뒤에 Username, Email 등 다양한 데이터명을 붙여서 사용할 수 있다

 

자세한 것은 JPA 사용법을 알아보도록 하자

 

현재 작성되어 있는 findByUsername(String username)은

 

select * from user where email = ?(username이 들어감)

 

라는 sql이 들어가게 된다

 

다시 Service를 보도록 하자

 

loadUserByUsername

 

해당 login에 입력한 id가 있을 시 해당 User의 모든 데이터를 User Entity에 넣어준다

 

그리고 if 문을 통해 해당 데이터가 있을 때 principalDetails 생성자에 해당 Entity를 넣어서 반환하고

 

값이 없을 때는 null을 반환해준다

 

 

생성자

다시 PrincipalDetails를 보자

 

Details 클래스에 생성 된 User 클래스에 권한 체크를 할 때 로그인 ID가 있으면

 

해당 ID의 정보를 Details에 생성 된 Entity에 넣어주고 클래스를 사용할 수 있게 한다

 


 

이제 Details의 나머지 메소드를 보도록 하자

 

 

Entity에 데이터가 들어가 있으면 비밀번호 유저이름을 뽑아 올 수 있도록 한다

 

그리고 나머지는 권한 유지 기한에 관한 내용이라는데 나중에 필요할 때 공부해서 사용하도록 하자

 


자 이제 권한체크를 하게 되는 클래스를 살펴보자

 

해당 클래스는 SecurityConfig로 WebSecurityConfigurerAdapter라는 클래스를 상속 받고 있다

 

 

 

해당클래스 에서는 어노테이션

 

configure 이라는 오버라이딩한 메소드에 주목해야한다

 

먼저 어노테이션 이다.


1. @Configuration : 스프링 컨테이너에 Bean을 등록하는 클래스라는 것을 알려주는 어노테이션

2. @EnableWebSecurity : SpringSecurity를 실행하기 위한 어노테이션으로 SpringSecurityFilterChain를 자동등록 해준다

3. @EnableGlobalMethodSecurity : Controller 단에서 따로 권한을 체크할 수 있도록 권한을 줄 수 있는 어노테이션

4. @Bean : 해당 클래스를 스프링 컨테이너에 등록해서 어디서든 사용할 수 있도록 해준다


 

 

다른 어노테이션 보다 2, 3번 어노테이션에 주목해야한다

 

2번 어노테이션은 SpringSecurity를 사용하기 위해서 반드시 등록해야한다는 점을 잊지 않도록 하자

 

 

그다음은 BcryptPasswordEncoder 이라는 클래스를 빈에 등록하고 있다

 

해당 클래스는 회원가입을 할 때 만약 우리가 비밀번호를 1234 라고 입력했다고 가정하자

 

하지만 그렇게 회원가입을 해버리면 웹 콘솔에서 비밀번호를 해킹되어 비밀번호를 다른 사람이 알아 볼 수 도 있다

 

그렇기 때문에 우리는 BcryptPasswordEncoder 의 기능을 활용해서 비밀번호를 암호화 시키는 작업을 할 수 있다

 

Controller

 

Controller를 확인해보자

 

BcryptPasswordEncoder 를 Bean에 등록해둬서 Autowired로 해당 객체를 가져올 수 있다

 

해당 클래스의 encode를 통해서 입력 받은 password를 암호화 시켜준다

 

 

다시 SecurityConfig를 보도록 하자

 

 

이제 메소드를 보자 configure 라는 메소드를 오버라이딩 해온다

 

1. antMatchers(url).authenticated() : 해당 url은 인정이 필요하고 로그인이 되면 권한 인증이 된다

2. antMatchers(url).access(hasRole('권한')) : 해당 url은 Role에서 해당 권한이 있어야만 권한 인증이 된다

3. anyRequest().permitAll( ) : 위의 권한 인증 url을 제외하고는 전부 권한이 없어도 접근이 가능하게 해준다

4. formLogin( ).loginPage('URL') : 사용자가 만든 로그인페이지 설정

5. loginProcessingUrl - POST로 로그인 정보를 보낼 시 경로

6. defaultSuccessUrl - 로그인이 성공할 시 경로

 

정도로만 알면 될 꺼 같다

 


 

지금까지 공부했던 개념을 간단하게 요약해보자

 

SpringSecurity는 로그인에 성공하면

SpringSecurity Session이 생성되고 해당 세션을 만들기 위한 정보는

Authentication에서 들어가게 되고 Authentication에 들어갈 정보는

UserDetails에서 만들어져서 들어가게 된다

 

 

조금 더 위의 클래스와 비교해서 이야기하자면

 

1. Page에 접속을 시도했을 때 권한이 필요해서 Login 페이지로 이동

2. 해당 페이지에 접속하기 위해 Login 시도

3. Login을 시도시 DetailsService의 loadUserByUsername 메소드가 자동으로 실행되고 id 값이 있을 때 해당 유저 데이터를 UserDetails 생성자를 통해 반환

4. UserDetails 에서 해당 유저의 권한을 파악

5. 해당 유저 권한이 있을 때 접속을 허가함

 

이런 구도로 이뤄 지는걸로 생각된다!

 

 

 

'IT 공부' 카테고리의 다른 글

11. Cors  (0) 2022.07.30
10. 카카오, 구글, 페이스북 로그인 설정  (0) 2022.07.25
8. JPA(Java Persistence API)  (0) 2022.07.20
7. Spring Test  (0) 2022.06.23
6. Spring security  (0) 2022.06.11