진짜 개발자
본문 바로가기

FrameWork/Spring Security

Spring Security - SpringSecurity 이용하기 4 (Database를 이용한 Login 구현)

728x90
Spring Security Database User Login 구현

SpringSecurity 이용하기 - 1 : https://galid1.tistory.com/688  (custom login form 구현)

SpringSecurity 이용하기 - 2 : https://galid1.tistory.com/698 (login Success Handle)

SpringSecurity 이용하기 - 3 : https://galid1.tistory.com/699 (logout 기능 추가하기)

SpringSecurity 이용하기 - 4 : 현재 포스트 (database를 이용한 로그인 구현)






이번 포스팅에서는, Spring Security 로그인시, 직접 구축한 Database의 User 정보를 이용해 로그인하는 방법을 알아보도록 하겠습니다.

 

 

 

Core Concepts : https://docs.spring.io/spring-security/site/docs/3.0.x/reference/technical-overview.html

Architecture : https://spring.io/guides/topicals/spring-security-architecture

이번 포스팅을 시작하기 앞서 위글을 읽고 Spring Security가 동작하는 전반적인 개념을 숙지하신 뒤에 진행하신다면 더욱 이해가 쉽습니다.

 

 

 

 

SpringSecurity 사용자 인증 개념

SpringSecurity 이용하기 관련글을 4개나 포스팅하면서, 이제야 SpringSecurity의 Concept에 대해 알아보려 합니다. SpringSecurity를 이용함으로써 얻는 편의를 알기도 전에, 이러한 내부의 개념들을 익혀나간다면, 기반지식이 없기 때문에 당연히 이해가 더 어려울 것으로 판단되어, 우선은 간단한 기능들을 사용해보고, 알려드리려 했습니다.

 

 

1. SecurityContextHolder, Authentication

SecurityContextHolder는 Application과 상호작용하는 보안주체의 정보가 저장되는 공간입니다. SpringSecurity에서는 이러한 보안주체를 Authentication을 이용하여 나타냅니다.

 

현재 인증된 사용자의 정보 가져오기

위의 코드를 이용하여 우리는 현재 인증된 사용자의 정보를 가져올 수 있습니다. SecurityContextHolder.getContext()SecurityContext를 반환하며, SecurityContext는 현재 스레드와 관련된 보안정보를 정의합니다.

 

 

 

2. UserDetailsService

2.1 UserDetails

Authentication으로부터 Principal을 가져올 수 있습니다. 다시 Principal은 UserDetails로 캐스팅이 가능합니다. UserDetails는 우리의 Application Database와 Spring Security의 SecurityContextHolder 사이의 일종의 어댑터입니다.

 

UserDetails -> Spring Security

예를들어 Spring Security에서 인증에 성공시 SecurityContextHolderAuthentication을 저장이 되는데요, UserDetails는 이 Authentication객체를 만드는데에 사용이 됩니다.

 

UserDetails -> Application Database

UserDetails는 또한 User Database를 나타내는 역할을 합니다. 따라서 비즈니스 모델 객체로 캐스팅하여 사용하기도 합니다.

 

 

2.2 UserDetailsService

그렇다면 위와 같은 UserDetails를 어떻게 제공하고, 언제 제공해야할까요?

 

Spring Security에서는 사용자가 로그인을 시도할시, 해당 로그인 정보와 Backend(직접 구축한 서버)에 존재하는 비교대상을 찾는 방법을 구현하기위해, UserDetailsService interface를 제공합니다.

 

UserDetails를 Spring Security에 제공하는 방법은 UserDetailsService를 구현하여, loadUserByUsername(String username);를 오버라이딩 하면 됩니다. loadUserByUsername() 메소드는 Spring Security에서 사용자정보를 로드하는 일반적인 방법입니다.

 

인증에 성공한다면, UserDetails는 SecurityContextHolder에 저장될Authentication객체를 만드는데 됩니다.

 

 

 

3. GrantedAuthority

Authentication으로부터 제공되는 또다른 메소드 중 getAuthorites()란 것이 있습니다. 이 메소드는 GrantedAuthority라는 객체의 배열을 반환합니다.

 

GrantedAuthority는 객체의 이름에서 쉽게 알 수 있듯이, 부여된 권한을 나타내는 객체입니다.

 

 

 

 

 

 

Spring Security Database User Login(with JPA) 구현

전체적인 그림을 먼저 인지하고 진행하시는 것이 도움이 될것같아 우선, 전체적인 파일 구조를 보여드립니다.

 

 

 

1. jdbc 설정

실습에 사용될 database는 mysql입니다. mysql을 운영체제에 맡게 우선 설치를 했다고 가정한뒤, 진행하겠습니다.

 

 

 

1.1 database 생성 및 table생성

먼저 database를 생성하기위해 mysql을 실행합니다. mysql workbench를 사용하면 시각적으로 database를 볼 수 있어 더욱 좋습니다.

 

testdb를 생성합니다.

 

위의 쿼리를 통해 user 테이블을 생성합니다.

 

 

 

1.2 mysql connector 의존성 추가

jdbc를 사용하기위해, build.gradle을 열어 다음과같이 의존성을 추가합니다.

(최신 mysql connector 의존성은 https://mvnrepository.com/artifact/mysql/mysql-connector-java 에서 구하실 수 있습니다.)

 

 

 

1.3 database configuration class 생성

driverClassName : com.mysql.cj.jdbc.Driver

mysql 5.x에서 8.x로 버전이 바뀌면서, jdbc에 필요한 설정 몇가지가 변경되었습니다. 원래는 com.mysql.jdbc.Driver 였던 driverClassName이 위처럼 변경이 되었습니다.

url: jdbc:mysql://DOMAIN:3306/DATABASE_NAME?serverTimezone=UTC

기존과 동일하지만 serverTimezone설정을 해주지 않는다면 에러가 발생합니다.

 

 

 

1.4 Database 연결 테스트

연결에 성공하면 콘솔창에 위와같이 나타납니다.

 

 

 

 

2. JPA 설정

database 프로그래밍을 위해서, 저는 JPA를 사용하도록 하겠습니다.

 

 

2.1 의존성 설정

 

의존성 설정

JPA를 이용하기 위해 의존성을 위 그림과같이 추가합니다.

 

https://galid1.tistory.com/531

추가적으로, 가독성 향상 및 코드 수정을 편리하게 할수있도록 도와주는 lombok을 추가해줍니다. lombok에 대한 설정방법과 설명은 위 링크를 참조해주세요.

 

 

2.2 JPA관련 설정

application.yml을 열고 위의 설정을 추가합니다.

hibernate: ddl-auto: update

필요한 table들을 생성하기위해, 자동으로 ddl(Data Definition Language)쿼리를 만들어 database에 요청합니다.

 

show-sql

jpa에의해서 자동으로 생성되는 sql을 콘솔에 출력해주는 설정입니다.

 

properties: dialect: org.hibernate.dialect.MySQL5InnoDBDialect

jpa 구현체에서 사용할 방언을 지정합니다. database별로 sql이 달라지기 때문에(예를들면, pagination 쿼리 등..)이를 위해 특정 방언을 사용하도록 지정합니다.

 

 

 

2.3 User Entity 생성

우리가 만들 Server에서 User를 표현하기위해 User Entity를 생성합니다. 이 UserEntity는 Spring Security의 필요에 따라서, Authentication으로 변환되기도, Application에서 사용되기도 합니다. 한줄한줄 살펴보겠습니다.

 

JPA

아래의 어노테이션들은 jpa에 관련한 코드들입니다.

 

@Entity

해당 클래스가 Entity임을 알려줍니다. 즉 이 클래스는 Database의 table에 맵핑됩니다. 이 클래스에 존재하는 field들이 table의 column에 맵핑됩니다.

 

@Table(name = "user")

database에 저장될 테이블의 이름을 user로 설정합니다.

 

 

 

lombok

아래의 어노테이션들은 lombok에 관련한 코드들입니다.

 

@Getter

내부의 field들에 대한 getter() 메소드를 자동으로 생성해줍니다.

 

@NoArgsConstructor(access = AccessLevel.PROTECTED)

아무런 파라미터가 없는 기본 생성자를 생성해줍니다. JPA에서는 프록시 생성을 위해서 기본생성자를 필요로하기 때문에, 이 어노테이션을부여합니다.

access는 이 기본생성자에 대한 접근제어설정을 합니다. default는 PUBLIC인데요, 기본생성자의 경우, 엔티티에 필수적으로 생성해야하는 값을 생성하지 않기 때문에, 외부에 공개한다면 위험할 수 있습니다. 따라서 PROTECTED로 지정합니다.

 

@Builder

생성자에 @Builder를 부여하는 경우, 생성자에 필요한 파라미터들을 포함하는 builder를 자동으로 생성해줍니다.

 

@Id

이 어노테이션이 부여된 field를 Database table의 primary key로 지정합니다.

 

@GeneratedValue(strategy = GenerationType.AUTO)

@Id어노테이션과 같이 사용되며, primary key의 생성 전략을 정의합니다. GenerationType.AUTO는 새로운 row가 추가됨에 따라 자동으로 값이 증가하며 부여되도록합니다. 즉 Database의 auto_increment옵션이 부여되었다고 생각하면됩니다.

 

 

 

 

2.4 User Repository 생성

spring data jpa에서는 java에서 database 프로그래밍시 반복되는 코드들을 줄여주는 편리한 기능을 제공합니다. 위와 같이 interface를 생성한뒤, JpaRepository<Entity, PrimaryKey>를 상속받으면, 기본적인 CRUD(CREATE, READ, UPDATE, DELETE)를 위한 메소드가 자동으로 생성됩니다.

 

 

Optional findByName(String name);

Spring Data Jpa는 또한, 메소드의 이름과 return Type을 이용하여, 자동으로 Database 관련한 기능이 구현된 메소드를 생성해줍니다.

예를들어 위와 같이 Optional<UserEntity> findByName(String name)로 작성된 메소드의 이름을 추론하여, select from user where name = "파라미터"라는 쿼리를 Database에 요청하며, 이를 return Type인 Optional<UserEntity>로 반환하는 메소드를 자동으로 생성해줍니다.

이 메소드는 사용자가 form Login시 요청한 id(name)를 이용해 Spring Security가 인증을 위해 Database에 존재하는 User정보를 찾을 때 사용됩니다.

 

 

 

2.5 user table 확인

SpringBoot Application을 실행한뒤, mysql workbench에서 database를 확인합니다.

 

우리가 작성한, UserEntity의 내용대로 table이 생성된 것을 볼 수 있습니다.

 

 

 

 

3. 로그인을 위한 사용자 추가

이제 Database에 사용자를 추가 해보도록 하겠습니다.

 

3.1 Controller 수정

이전 포스팅에서 작성했던 Controller에 /signUp에 대한 요청을 처리하는 핸들러를 추가합니다. 이 핸들러에서는 회원가입 페이지를 작성하기 귀찮기 때문에, 코드상에서 사용자를 생성하도록합니다.

 

먼저 UserRepository, PasswordEncoder를 주입받습니다.

 

그다음, UserEntity를 생성한뒤, userRepository를 이용해 entity를 저장합니다. 이렇게되면, table에 user row가 추가됩니다. password 저장시, passwordEncoder를 통해, 인코딩된 암호가 database에 저장되도록 합니다.

 

마지막으로, signUp요청을 한뒤에는, login페이지로 리다이렉트 시킵니다.

 

 

 

3.2 Spring Security 설정 추가

사용자가 /signUp에 접근할 수 있도록 antMathcers()에 /signUp엔드포인트를 추가합니다.

 

 

 

 

 

3.3 Database 확인

어플리케이션을 실행한뒤, /signUp으로 요청합니다. 우리가 생성한 코드대로 user가 생성된뒤, login페이지로 리다이렉트 되어집니다.

 

 

user가 추가된것을 확인할 수 있습니다. 이제 위에 추가한 user를 이용해 로그인을 할 수 있도록 해보겠습니다.

 

 

 

 

4. Spring Security에서 Database 정보를 이용하도록 만들기

방법은 간단합니다. UserDetailsService를 구현하는 클래스를 만들고, 이를 Bean으로 등록하면 준비는 끝입니다.

 

 

loadUserByUsername(String username)

이 메소드는 spring security에서 사용자가 로그인할때 인증을 위해 사용하는 메소드입니다. username 파라미터에는 사용자가 로그인시 form의 id parameter에 담긴 내용이 담기게됩니다.

 

따라서 이 id를 이용해 database에서 사용자를 가져오고, database에서 가져온 정보를 이용해 UserDetails를 만들어 return하면 됩니다.

 

 

 

 

 

5. 최종 Test

어플리케이션을 실행한뒤, 로그인페이지로 접근하여, 생성한 user정보를 이용해 로그인을 시도합니다.

 

성공적으로 로그인이 이루어지는것을 볼 수 있습니다.