스프링 시큐리티(Spring Security)는 스프링 기반의 사용자 인증을 처리하는 스프링 하위 프레임워크로 사용자 인증과 권한에 대한 부분을 Filter로 처리해준다.

 

 

핵심 용어


SecurityContextHolder

 

보안 주체의 세부 정보를 포함하여 응용 프로그램의 현재 보안 컨텍스트에 대한 세부 정보가 저장되며 로그인 인증이 되면 스프링 시큐리티는 SecurityContextHolder에 Authentication을 담아 세션 저장소에 있는 전용 공간에 보관한다.

 

SecurityContext

 

Authentication 객체를 보관하는 역할을 하며 SequrityContext를 통해 Authentication 객체를 꺼낼 수 있다.

 

Authentication

 

현재 접근하는 주체의 정보와 권한을 담는 인터페이스로 Authentication 객체는 SecurityContext에 저장되며, SecurityContextHolder를 통해 SecurityContext에 접근해서 Authentication 객체를 가져올 수 있다.

 

UsernamePasswordAuthenticationToken

 

Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스로 사용자 인증 전 객체를 생성하는 생성자가 있고, 인증이 완료된 객체를 생성하는 생성자가 있다.

 

AuthenticationManager, AuthenticationProvider

 

인증을 처리하는 방법을 정의한 인터페이스로 구현체인 ProviderManager가 있다. ProviderManager는 실제 인증을 처리하는AuthenticaitonProvider 리스트를 실행시켜 인증을 거치고 직접 Provider를 구현하여 등록할 수도 있다. 사용자 입력 정보가 있는 인증 전 객체인 Authentication으로 authenticate() 메서드를 실행하여 인증을 완료하고 하면 인증이 완료된 Authentication을 반환한다.

 

UserDetailsService, UserDetails

 

UserDetailsService는 loadUserByUsername() 메서드를 통해 DB에서 데이터 조회를 하는 역할을 하며 UserDetailsService를 implements한 클래스를 스프링 빈으로 등록하면 스프링이 자동으로 실행된다. UserDetailsService는 DB에서 조회한 User 객체를 담은 UserDetails 객체를 생성하여 반환한다.

UserDetails는 사용자 정보를 담고 있는 인터페이스로 계정이 가지고 있는 권한 리스트 등의 메서드를 Override 할 수 있으며 로그인 요청 정보와 비교하여 인증 완료된 Authentication 객체를 생성하는데 사용된다.

 

 

 

인증 과정


1. 사용자가 아이디, 비밀번호로 로그인 요청을 하면 AuthenticationFilter에서 인증 전 상태의 Authentication 객체(UserPasswordAuthenticationToken)을 생성하여 AuthenticationManager에 전달한다.

 

2. AuthenticationManager는 AuthenticationProvier를 실행하며 사용자 인증을 처리한다.

 

3. AuthenticationProvider는 UserDetailsService를 통해 인증을 처리한다.

 

4. UserDetailsService는 loadUserByUsername() 메서드를 실행하여 DB에서 데이터를 조회하고 UserDetails 객체를 생성하여 반환한다.

 

5. AuthenticationProvider는 UserDetails 객체를 받아서 비밀번호 비교 등 추가 인증을 한다. 

 

6. AuthenticationManager는 인증이 완료된 Authentication 객체 (UsernamePasswordAuthenticationToken)를 AuthenticationFilter, LoginSuccessHandler로 전달해서 SecurityContextHolder에 저장한다.

 

7. SecurityContextHolder는 세션 저장소에 있는 스프링 시큐리티 전용 공간에 저장이 된다.

 

 

 

 

 

 

[참고]

 

[SpringBoot] Spring Security란?

대부분의 시스템에서는 회원의 관리를 하고 있고, 그에 따른 인증(Authentication)과 인가(Authorization)에 대한 처리를 해주어야 한다. Spring에서는 Spring Security라는 별도의 프레임워크에서 관련된 기능

mangkyu.tistory.com

 

 

[무료] 스프링부트 시큐리티 & JWT 강의 - 인프런 | 강의

스프링부트 시큐리티에 대한 개념이 잡힙니다., - 강의 소개 | 인프런...

www.inflearn.com

 

 

스프링에서 Mybatis를 사용하기 위해서는 Mybatis 스프링 연동 모듈이 필요한데 이는 마이바티스와 스프링을 편하고 간단하게 사용할 수 있도록 지원해준다.

 

SqlSessionFactory

 

데이터 베이스의 연결과 SQL 실행에 대한 모든 것을 가지고 있으며 DataSource를 필요로 한다.

@Configuration
public class MyBatisConfig {
  @Bean
  public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
  }
}

스프링은 SqlSessionFactoryBean에서 getObject() 메서드를 호출하여 반환된 SqlSessionFactory를 빌드하여 sqlSessionFactory 라는 이름으로 저장한다. SqlSessionFactoryBean이나 SqlSessionFactory를 직접 사용할 일은 없고 MapperFactoryBean를 확장하는 다른 DAO에 주입 될 것이다.

 

 

 

MapperFactoryBean

 

MapperFactoryBean SqlSession을 생성하고 닫는 역할을 하며 실행중인 스프링 트랜잭션을 관리하고 예외 발생시 스프링의 DataAccessException예외로 변환해준다. 

@Configuration
public class MyBatisConfig {
  @Bean
  public UserMapper userMapper() throws Exception {
    SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
    return sqlSessionTemplate.getMapper(UserMapper.class);
  }
}

 

SqlSesssion과 SqlSessionTemplate

SqlSession SqlSessionFactory에 의해 생성되며 매핑 구문을 실행하거나 트랜잭션 커밋, 롤백 등을 할 수 있는데 마이바티스 스프링 연동 모듈은 자동으로 SqlSession을 스프링 빈에 주입하고 스프링이 직접 트랜잭션을 관리한다. 

스프링이 관리하는 SqlSession에서는 SqlSession.commit(), SqlSession.rollback() 또는 SqlSession.close() 메서드를 호출할수가 없다. (사용 시 UnsupportedOperationException 예외가 발생)

 

SqlSessionTemplate은 인자로 SqlSessionFactory를 받아서 생성되는데 SqlSession이 현재의 스프링 트랜잭션에서 사용 될 수 있도록 보장하는데 필요한 시점에 세션을 닫고, 커밋하거나 롤백하는 것을 포함한 세션의 생명주기를 관리한다. 또한 마이바티스 예외를 스프링의 DataAccessException로 변환하는 작업도 처리한다.

 

TransactionManager

 

마이바티스 스프링 연동 모듈은 스프링 PlatformTransactionManager의 구현체인 DataSourceTransactionManager를 사용하여 스프링 트랜잭션에 자연스럽게 연동될 수 있는데 DataSourceTransactionManager는 Connection 객체에 존재하는 트랜잭션 API를 사용하여 트랜잭션을 관리해주는 TransactionManager다.

 

참고)

JPA는 JpaTransactionManager를 사용하며 EntityManagerFactory가 스프링 빈으로 등록된 DataSource를 사용하는데 JPA에서 사용하는 DataSource를 Mybatis에서도 사용할 수 있다. Mybatis만 사용할 경우 DataSourceTransactionManager를 스프링 빈으로 등록하지만, JPA를 사용할 경우 JPATransactionManager로 같이 관리가 가능하기 때문에 별도의 TransactionManager를 등록할 필요가 없다.

 

Spring boot :: JPA, Mybatis Transaction Manager 정리

Introduction 이번에는 Mybatis 와 JPA 를 동시에 적용한 환경에서 어떤 transactionManager 를 사용해야되는지 살펴보려고 한다. 해당 글은 스프링에서 트랜잭션을 담당하는 핵심 인터페이스인 PlatformTransact

wave1994.tistory.com

 

 

Mapper Interface

Mybatis XML 파일에 SQL을 호출하기 위한 인터페이스로  편리하게 XML의 데이터를 찾아서 호출해주고 MyBatis에서 발생한 예외를 스프링 예외 추상화인 DataAccessException에 맞게 변환해주는 등 지금까지 말한 기능들을 담당한다.

 

Mybatis 3.0 이후 버전부터는 SqlSessionTemplate를 직접적으로 사용하는 DAO를 생성하지 않고 다른 빈에 직접 주입할 수 있는 쓰레드에 안전한 매퍼를 생성하여 사용한다. 리포지토리에 Mapper 인터페이스를 주입하여 사용하는 것을 생각하면 된다. 여러 설정들이 간편하게 자동화되며 스프링 부트를 사용하면 Mapper 인터페이스를 Bean으로 등록할 필요도 없이 @Mapper 애노테이션만 사용하면 된다.

 

 

Mybatis-Spring-Boot-Starter

  • DataSource 자동 감지
  • SqlSessionFactoryBean을 사용해서 DataSource를 전달하는 SqlSessionFactory의 객체를 생성하고 등록
  • SqlSessionFactory를 이용해 SqlSessionTemplate를 생성하고 등록
  • Mapper를 스캔한 뒤 SqlSessionTemplate에 연결하고 Mapper 인터페이스를 프록시 구현체로 생성하여 스프링 빈으로 등록해서 리포지토리에 주입할 수 있게 해준다.

 

 

Mybatis-Spring-Boot-Starter 소개 문서 번역

마이바티스-스프링 부트-스타터 문서 번역

kgmyh.github.io

 

 

mybatis-spring –

소개 MyBatis-Spring 은 무엇일까? 마이바티스 스프링 연동모듈은 마이바티스와 스프링을 편하고 간단하게 연동한다. 이 모듈은 마이바티스로 하여금 스프링 트랜잭션에 쉽게 연동되도록 처리한다.

mybatis.org

 

[Mybatis]란? / SqlSessionTemplate, MapperInterface 개념/빈 등록 설정

[MyBatis]란 무엇인가 에 대해서 또 SqlSessionTemplate/MapperInterface 두가지 방식에 따른 전체적인 구조 차이와 Mapper.xml 속성 차이에 대해 정리합니다. [MyBatis]란? 앞서 사용해 보았던 Jdbc-Template도..

u-it.tistory.com

 

 

회원 정보를 수정하는 경우 해당 회원은 새로 가입하는 것이 아닌 기존 정보를 수정하는 것이기 때문에 DB에 한번 저장되어서 식별자가 존재하는 상태이다. 이런 경우도 준영속 엔티티라고 하는데 준영속 엔티티는 영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.

다음과 같이 회원 수정 폼으로 변경 데이터를 받아와서 임의의 엔티티 객체를 생성하고 값을 전달하면 해당 엔티티는 준영속 상태이기 때문에 변경 감지가 이루어지지 않는다.

 

@Transactional
void update(UpdateForm updateForm) {
    Member member = new Member();
    member.setId(updateForm.getId());
    member.setPhoneNumber(updateForm.getPhoneNumber());
}

 

그래서 준영속 상태의 엔티티를 영속 상태로 변경하는 방법으로 병합(merge) 기능이 있는데 준영속 엔티티를 파라미터로 전달하면 식별자 값으로 1차 캐시에서 엔티티를 조회하고, 1차 캐시에 엔티티가 없을 경우 DB에서 엔티티를 조회하여 1차 캐시에 저장한다. 그리고 조회한 영속 엔티티에 파라미터 준영속 엔티티의 값을 채워 넣은 뒤에 영속 상태의 엔티티를 반환한다. 이렇게 되면 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 DB에 UPDATE SQL이 실행되어 실제 변경이 이루어진다. 

 

@Transactional
void update(UpdateForm updateForm) {
    Member member = new Member();
    member.setId(updateForm.getId());
    member.setPhoneNumber(updateForm.getPhoneNumber());
    
    Member mergeMember = em.merge(member);
}

 

하지만 병합 기능은 모든 필드를 교체해버리기 때문에 파라미터로 전달된 준영속 엔티티의 특정 필드가 비어있는 경우 null로 변경이 되는 위험이 있다. 

 

수정 시 모든 값을 변경해버리는 상황은 거의 없기 때문에 엔티티를 변경할 때는 병합은 되도록 하지 않고 다음과 같이 식별자를 통해 엔티티를 조회해와서 변경 감지를 사용하는 것이 좋다. 

@Transactional
void update(Member memberForm) {
    Member findMember = em.find(Member.class, memberForm.getId());
    findMember.setPhoneNumber(memberForm.getPhoneNumber());
}

 

 

 

 

실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 - 인프런 | 강의

실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다., - 강

www.inflearn.com

 

 

 

 

JPA는 스펙상 엔티티, 임베디드 값 타입에 리플렉션(Reflection), 프록시(Proxy) 기술을 적용하기 위해 public 또는 protected의 기본 생성자를 두는 것을 정의하고 있다.

 

 

리플렉션이란?

더보기

Java Reflection API

 

자바는 컴파일 시점에서 타입이 결정되는데 다음과 같이 구체적인 클래스로 생성하지 않을 경우 컴파일 시점에서 Object 타입으로 결정이 되기 때문에 Car 클래스의 메소드 등에 접근할 수가 없다.

 

Object car = new Car(); 

 

자바에서는 JVM이 실행되면 자바 코드가 컴파일러를 거쳐 바이트코드로 변환되어 static 영역에 저장되는데 리플렉션은 이 정보를 활용하여 구체적인 클래스 타입을 알지 못해도 그 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해준다. 하지만 런타임 시점에 동적으로 타입을 분석하기 때문에 JVM을 최적화할 수 없는 등 여러 문제점이 있어서 일반적으로는 사용하지 않는 것이 좋다.

리플렉션은 애플리케이션 개발보다 프레임워크나 라이브러리에서 많이 사용하는데 프레임워크나 라이브러리는 사용자가 어떤 클래스를 만들지 예측할 수가 없기 때문에 리플렉션을 활용한다.

JPA도 동적으로 객체 생성시 리플렉션을 활용하는데 리플렉션으로 가져올 수 없는 정보 중 하나가 생성자의 인자 정보이다. 그래서 기본 생성자가 반드시 있어야 객체를 생성할 수 있다. 기본 생성자로 객체를 생성만 하면 필드 값 등을 리플렉션으로 넣어줄 수 있다.

 

 

Reflection API 간단히 알아보자.

Spring Framework를 학습하다 보면 Java Reflection API를 자주 접하게 된다. 하지만 Reflection API…

tecoble.techcourse.co.kr

 

 

하이버네이트 같은 구현체들은 기본 생성자가 없어도 라이브러리들을 통해 이러한 문제를 어느정도 회피하지만 완벽한 해결책이 아니기 때문에 기본 생성자를 필수로 두는 것이 좋다. 기본 생성자를 public으로 열어두면 의도치 않은 무분별한 생성이 될 수 있기 때문에  protected로 선언하여 다른 패키지에서 기본 생성자를 생성할 수 없도록 제한한다.

 

그리고 엔티티는 생성자나 Setter를 사용하는 것보다는 빌더(Builder) 패턴 또는 정적 팩토리 메서드(static factory method)를 사용하는 것이 좋은데 빌더 패턴은 필요한 데이터만 설정할 수 있고 유연성, 가독성이 뛰어나며 변경 가능성을 최소화 할 수 있다.

 

[Java] 빌더 패턴(Builder Pattern)을 사용해야 하는 이유

객체를 생성하기 위해서는 생성자 패턴, 정적 메소드 패턴, 수정자 패턴, 빌더 패턴 등을 사용할 수 있습니다. 개인적으로 객체를 생성할 때에는 반드시 빌더 패턴을 사용해야 한다고 생각하는

mangkyu.tistory.com

 

정적 팩토리 메서드(static factory method)는 객체 생성을 캡슐화하는 기법으로 객체를 생성하는 메소드를 만들고 static으로 선언하여 사용한다. 메서드명을 의미 있게 지을 수 있어 생성자에 비해 가독성이 좋아지고 호출할 때마다 새로운 객체를 생성할 필요가 없다. 그리고 하위 타입 객체를 반환할 수 있으며 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다. 

 

public static User createUser(String name, int age) {
    OrderItem orderItem = new OrderItem();
    this.name = name;
    this.age = age;
    
    return user;
}

 

파라미터가 많거나 변경 가능성이 많은 경우에는 빌더 패턴을, 그렇지 않은 경우에는 정적 팩토리 메소드를 사용하는 것이 좋다.

+ Recent posts