Spring/Spring Security

[Spring Security] Spring Security의 기본 구조, 동작 방식 (1) - InMemory User

jungha_k 2022. 11. 21. 13:18

 


Spring Security

흐름 적용을 위해

CSR 방식이 아닌 SSR 방식을 이용함

 

각각의 화면에 맞는 html 파일이 있다! (타임리프 방식) 

 

 

화면 설명 ⬇

커피 보기 화면 - 모든 사용자 접근 가능

전체 주문 목록 보기 화면 - admin only

마이페이지 화면 - 일반사용자(user) only

 


Spring Security 적용하기

 

1. 의존 라이브러리 추가 : spring-boot-starter-security

 

2. spring security 디폴트 로그인 페이지로 이동

username : user

password : 로그에 출력됨

 

 

디폴트 페이지가 아닌 만들어둔 html 파일로 로그인하고 싶다?

➡ Spring Security Configuration 적용

 

: 원하는 인증방식, 웹 페이지 접근 권한 설정 가능함

 

import org.springframework.context.annotation.Configuration;

@Configuration
public class SecurityConfiguration {
    // 여기에 Spring Security의 설정을 진행합니다.
}

 

애플리케이션의 사용자 인증을 위한

두 가지 방법을 배움. 이 글에서는 1)만 다룬다!

 

 

1) InMemory User 로 인증하기

2) DB 연동을 통한 User로 인증하기


 

1) InMemory User 로 인증하기 (테스트 환경, 데모 환경에 유용)

 

: UserDetail을 직접 코드 안에 박아 넣음! (객체를 @Bean 으로 등록)

...
@Bean
    public UserDetailsManager userDetailsService() {
        UserDetails user =
                User.withDefaultPasswordEncoder()
                        .username("hello@gmail.com")
                        .password("1111")
                        .roles("USER")
                        .build();

return new InMemoryUserDetailsManager(user);
...

: InMemoryUserDetailManager 구현체 사용

 

 

* UserDetailmanager - 사용자의 핵심 정보를 포함한 UserDetails 관리

 

* withDefaultPasswordEncoder()

: 패스워드를 암호화

 

 

HTTP 보안 구성 기본

: SecurityFilterChain, HttpSecurity

@Configuration
public class SecurityConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        // HttpSecurity를 통해 HTTP 요청에 대한 보안 설정을 구성한다.
        // SecurityFilterChain 을 Bean 으로 등록
        ...
    }

 

 

커스텀 로그인 페이지 지정하기 ➡ SecurityFilterChain 안에서 작성됨

request URI 에 접근 권한 부여(role) ➡ SecurityFilterChain 안에서 작성됨

 

@Configuration
public class SecurityConfiguration {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf().disable()                 //csrf 공격 설정 비활성화
            .formLogin()                      // 폼 로그인 방식
            .loginPage("/auths/login-form")   // html 파일
            .loginProcessingUrl("/process_login")    // 요청 URL
            .failureUrl("/auths/login-form?error")   // 실패 URL
            .and()                                   // 체인 형태
            .authorizeHttpRequests()                 // 클라이언트 요청 - 접근 권한 확인
            .anyRequest()                            
            .permitAll();   //모든 요청에 접근 허용

        return http.build();
    }
    
    ⬇  ⬇  ⬇  ⬇  ⬇
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
        
            ... (동일)
            ...
            // 권한 없는 사용자가 접근할경우 나오는 html (403)
            .exceptionHandling().accessDeniedPage("/auths/access-denied")   
            .and()
            // request URI 접근권한 부여
            .authorizeHttpRequests(authorize -> authorize       
            		// ADMIN - 모든 orders 하위 URL 접속 가능
                    .antMatchers("/orders/**").hasRole("ADMIN") 
                    	// USER - ROLE 부여받은자만 my-page 접속 가능
                    .antMatchers("/members/my-page").hasRole("USER") 
                    	// 모든 URL - ROLE에 상관없이 접속 가능
                    .antMatchers("⁄**").permitAll()                   
            
            *andMatchers 순서 유의할 것!
            
            );
        return http.build();
    }

 

 

InMemory User에 새로운 User 등록하기 (회원가입)

 

   🤔 순서

  1. PasswordEncoder Bean 등록
  2. MemberService Bean 등록을 위한 JavaConfiguration 구성
  3. InMemoryMemberService 클래스 구현

 

 

1. PasswordEncoder Bean 등록

 

* PasswordEncoder : Spring Security 에서 제공하는 패스워드 암호화 기능 제공 컴포넌트

(default = bcrypt)

 

회원가입 폼 ➡ 애플리케이션 으로 비번 전달시

암호화 된 비번이 아니라 plain text (비번 그대로) 임!

 

InMemory User 로 등록하기 전에 비밀번호는 암호화 되어야 한다.

 

@Configuration
public class SecurityConfiguration {

...
...

@Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    
    //DelegatingPasswordEncoder - 실질적으로 PasswordEncoder 구현 객체 생성
}

 

 

2. MemberService Bean 등록을 위한 JavaConfiguration 구성

 

1) MemberService 인터페이스 생성 (편의상 class 아닌 interface)

: createMember()

 

2) MemberService 인터페이스의 구현체 InMemoryMemberService 클래스 생성

 

 

JavaConfiguration ⬇

@Configuration
public class JavaConfiguration {

//InMemoryMemberService를 Bean 으로 등록

    @Bean
    public MemberService inMemoryMemberService(UserDetailsManager userDetailsManager, 
                                               PasswordEncoder passwordEncoder) {
        return new InMemoryMemberService(userDetailsManager, passwordEncoder);
    }
    
    // ⬆ DI
    // UserDetailsManager - db 연동 없이 메모리에 user 등록
    // PasswordEncoder - user등록시 패스워드 암호화
}

 

 

3) InMemoryMemberService 클래스 구현

public class InMemoryMemberService implements MemberService {
    private final UserDetailsManager userDetailsManager;
    private final PasswordEncoder passwordEncoder;

	// DI
    public InMemoryMemberService(UserDetailsManager userDetailsManager, PasswordEncoder passwordEncoder) {
        this.userDetailsManager = userDetailsManager;
        this.passwordEncoder = passwordEncoder;
    }

    public Member createMember(Member member) {
        // User의 권한 목록 list 생성 
        // ROLE_ + 권한명 형태
        List<GrantedAuthority> authorities = createAuthorities(Member.MemberRole.ROLE_USER.name());

        // 유저 패스워드 암호화
        String encryptedPassword = passwordEncoder.encode(member.getPassword());

        // UserDertails 생성
        UserDetails userDetails = new User(member.getEmail(), encryptedPassword, authorities);

        // User 등록
        userDetailsManager.createUser(userDetails);

        return member;
    }

    private List<GrantedAuthority> createAuthorities(String... roles) {
        // SimpleGrantedAuthority 객체 생성 ➡ List 형태로 리턴
        return Arrays.stream(roles)
                .map(role -> new SimpleGrantedAuthority(role))
                .collect(Collectors.toList());
    }
}