본문 바로가기

Spring/Spring Security

(Spring Security) Form Login 설정

Environment

  • Spring Security
  • Thymeleaf
  • Spring Data JPA
  • H2 (memory)
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>1.4.199</version>
    <scope>runtime</scope>
</dependency>

Entity

회원 인증만 구현할 예정이므로 단순하게 회원을 관리하는 Member 엔티티와 권한을 관리하는 Grade 엔티티로 구성하였다.
추가적으로 Member와 Grade는 일대일 단방향 관계이며 Grade는 Enum 타입의 GradeStatus를 가진다.

GradeStatus는 BEGINNER, JUNIOR, SENIOR 로 구성된다.

Entity Relationship Diagram

Member.java

@Entity
@Table(name = "member")
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    @Column(name = "email")
    private String email;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "grade_id")
    private Grade grade;
    
    @Column(name = "password", length = 100)
    private String password;

}

Grade.java

@Entity
@Table(name = "grade")
public class Grade {

    @Id
    @GeneratedValue
    @Column(name = "grade_id")
    private Long id;
    
    @Column(name = "grade_status")
    @Enumerated(EnumType.STRING)
    private GradeStatus gradeStatus;

}

GradeStatus.java

public enum GradeStatus {

    BEGINNER, JUNIOR, SENIOR

}

Configuration

Spring Security 5.7.0-M2 에서는 WebSecurityConfigurerAdapter  Deprecated 되었다.
이에 따라 SecurityFilterChain, WebSecurityCustomizer Bean으로 등록하여 Security 설정을 진행해보도록 하겠다.

Security Configuration (SecurityConfig.java)

DB 데이터를 확인하기 위한 h2-console 화면은 인증 대상이 아니므로 인증대상에서 제외시킨다.
csrf는 임시로 disable 처리하고 추후에 해당 내용에 대해서 구체적으로 알아보도록 하자
현재는 Form Login 방식을 구현할 예정이므로 formLogin을 활성화시켜준다.

 

 

회원가입이 안되면 어떠한 과정도 진행할 수 없으니 회원가입 요청에 대해서 필터 적용을 제외시킨다.
필터에 대해서는 다음 스텝에서 알아보도록 하자

 

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                .antMatchers(
                        "/h2-console/**"
                )
                .permitAll()
                .anyRequest()
                .authenticated();
        httpSecurity.csrf()
                .disable();
        httpSecurity.formLogin();
        return httpSecurity.build();
    }

    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.ignoring()
                .antMatchers(HttpMethod.POST, "/sign-up");
    }

}

Password 설정 (SecurityConfigFactory.java)

DB에 비밀번호를 plain text 그대로 저장한다면 보안상 위험이 따름으로 암호화해서 저장해야 한다.
Spring Security(org.springframework.security.crypto.bcrypt)에서 제공하는 BCryptPasswordEncoder를 Bean으로 등록하여 Bcrypt 인증 방식을 기본 비밀번호 암호화 방식으로 설정한다.

 

@Configuration
public class SecurityConfigFactory {

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

}

 

Controller (MemberController.java)

sign-up 요청에 대한 컨트롤러 생성

 

@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    @PostMapping("/sign-up")
    public ResponseEntity<ResCreateMember> signUp(@RequestBody ReqCreateMember reqCreateMember) {
        return ResponseEntity.ok(memberService.createMember(reqCreateMember));
    }

}

CustomUserDetailsService (CustomUserDetailsService.java)

UserDetailsService를 구현한 CustomUserDetailsService를 선언한다.
UserDetailsService는 loadUserByUsername(String username) 구현하여 현재 비즈니스 도메인과의 연결고리를 설정한다.
이 또한 다음 스텝에서 알아보도록 하자

 

@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {

    private final MemberRepository memberRepository;

    @Override
    @Transactional(readOnly = true)
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return memberRepository.findByEmail(email)
                .map(member -> createUser(email, member))
                .orElseThrow(EntityNotFoundException::new);
    }

    private UserDetails createUser(String email, Member member) {
        String grade = member.getGrade()
                .getGradeStatus()
                .name();
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(grade);
        return User.builder()
                .username(email)
                .password(member.getPassword())
                .authorities(authority)
                .build();
    }

}

Execution

회원가입 및 로그인


Summary

크게 아래 네 가지에 대해서 알아보았다.

  • 의존성 설정
  • Entity 구성
  • Security 설정
  • Password 설정

다음 스텝에서 해당 내용을 기반으로 스프링 시큐리티 아키텍처에 대해서 알아보도록 하자


Code Link

https://github.com/mike6321/TIL/commit/8389d5baa098edc5b34204b401d6ac7f3d354e18

 

[junwoo.choi] feat : (security) 폼 인증 기본 설정 · mike6321/TIL@8389d5b

Show file tree Hide file tree Showing 12 changed files with 227 additions and 5 deletions.

github.com

References

https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

 

Spring Security without the WebSecurityConfigurerAdapter

<p>In Spring Security 5.7.0-M2 we <a href="https://github.com/spring-projects/spring-security/issues/10822">deprecated</a> the <code>WebSecurityConfigurerAdapter</code>, as we encourage users to move towards a component-based security configuration.</p> <p

spring.io

 

'Spring > Spring Security' 카테고리의 다른 글

(Spring Security) 개요  (0) 2022.06.24