Project_CRUD - #2 Settings, Dependencies, Security

Quann·2023년 1월 28일
0

CRUD

목록 보기
2/2

프로젝트의 시작을 위한 Security 세팅 먼저 해주자

디펜던시 적용을 위한 build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.6'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'study'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    runtimeOnly 'com.mysql:mysql-connector-j'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

    // json
    implementation group: 'org.json', name: 'json', version: '20220924'

    // security
    implementation 'org.springframework.boot:spring-boot-starter-security'

    // jwt
    compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
    runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
    runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'

    // Swagger
    implementation 'io.springfox:springfox-boot-starter:3.0.0'

    // validation
    implementation 'org.springframework.boot:spring-boot-starter-validation'

    // JUnit5
    testImplementation("org.junit.platform:junit-platform-launcher:1.5.2")
    testImplementation("org.junit.jupiter:junit-jupiter:5.5.2")

    // queryDSL 설정
    implementation "com.querydsl:querydsl-jpa"
    implementation "com.querydsl:querydsl-core"
    implementation "com.querydsl:querydsl-collections"
    annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa" // querydsl JPAAnnotationProcessor 사용 지정
    annotationProcessor "jakarta.annotation:jakarta.annotation-api" // java.lang.NoClassDefFoundError (javax.annotation.Generated) 대응 코드
    annotationProcessor "jakarta.persistence:jakarta.persistence-api" // java.lang.NoClassDefFoundError (javax.annotation.Entity) 대응 코드
}

tasks.named('test') {
    useJUnitPlatform()
}


// Querydsl 설정부
def generated = 'src/main/generated'

// querydsl QClass 파일 생성 위치를 지정
tasks.withType(JavaCompile) {
    options.getGeneratedSourceOutputDirectory().set(file(generated))
}

// java source set 에 querydsl QClass 위치 추가
sourceSets {
    main.java.srcDirs += [generated]
}

// gradle clean 시에 QClass 디렉토리 삭제
clean {
    delete file(generated)
}

인증, 인가의 구현을 위한 JWT, Security
이후 동적쿼리를 작성하기 위한 QueryDsl
테스트를 위한 Junit
등등 프로젝트에 필요한 라이브러리를 추가해준다


UserDetailsImpl.java

public class UserDetailsImpl implements UserDetails {

    private final User user;
    private final String email;

    public UserDetailsImpl(User user, String email) {
        this.user = user;
        this.email = email;
    }

    public User getUser() {
        return user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        UserRole role = user.getRole();
        String authority = role.getAuthority();

        SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(simpleGrantedAuthority);

        return authorities;
    }

    @Override
    public String getUsername() {
        return this.email;
    }

    @Override
    public String getPassword() {
        return user.getLoginPw();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

생각.

스프링 시큐리티에 전달해줄 객체인 UserDetailsImpl 객체이다.
user의 loginId는 변경될 가능성이 많으므로, 변경 가능성이 적은 email을 구분자로 두고, getUsername()이 호출될시 고유값인 email이 호출되도록 만들어 두었다.


UserDetailsServiceImpl.java

@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    @Transactional
    public UserDetailsImpl loadUserByUsername(String email) throws UsernameNotFoundException {
        return userRepository.findByEmail(email)
                .map(this::createUserDetails)
                .orElseThrow(UserNotFoundException::new);
    }

    private UserDetailsImpl createUserDetails(User user) {
        return new UserDetailsImpl(user, user.getEmail());
    }

}

생각.

UserDetailsService를 구현한 UserDetailsServiceImpl 객체이다.
email을 고유값으로 두었기때문에, findByEmail을 통한 유저 객체를 찾아오고, 앞서 만들어둔 인증객체인 UserDetailsImpl 객체를 반환하여 이후 Security ContextHolder에 해당 객체 정보를 저장할 수 있도록 한다.

따라서, 인증 객체를 Security Context Holder에 저장할 때마다(요청에 대한 User객체를 찾아올 때마다) 쿼리문이 발생하게 된다. 이에 대한 추가 생각이 필요할 것 같다.


SecurityConfig.java

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final JwtFilter jwtFilter;
    private final AuthenticationEntryPoint authenticationEntryPoint;
    private final AccessDeniedHandler accessDeniedHandler;

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

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .headers(header -> header
                        .frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))
                .csrf(AbstractHttpConfigurer::disable)
                .exceptionHandling(exception -> exception
                        .accessDeniedHandler(accessDeniedHandler)
                        .authenticationEntryPoint(authenticationEntryPoint))
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(request -> request
                        .antMatchers("/swagger-ui/**", "/swagger-resources/**", "/swagger-ui.html").permitAll()
                        .antMatchers("/css/**", "/js/**", "/images/**", "/webjars/**", "/favicon.*", "/*/icon-*").permitAll()
                        .antMatchers("/h2-console", "/h2-console/**").permitAll()
                        .antMatchers("/test", "/resources/**", "/v3/**").permitAll()
                        .antMatchers("/api/auth/**").permitAll()
                        .anyRequest().authenticated())
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

}

생각.

Spring Security의 전체적인 설정을 담당하는 영역이다.
Deprecated 관련 부분이나 ExceptionHandling 관련 부분은 게시글로 저장해두어 해당 링크를 써놓겠습니다.

TIL - #15 리팩터링
TIL - #20 Security ExceptionHandling

Custom한 Handler나 Filter부분은 생성자 주입을 통해, Config 객체 생성시 주입받은 객체를 사용하도록 만들어두었다.

코드의 변경점을 생각해두어, new AuthenticationEntryPint() 같은 생성자를 사용하는 것이 아니라, 사용할 객체를 Bean객체로 등록해두어 최초 시작시 객체를 주입받을 수 있도록한다.

이를 통해 Handler나 Filter가 변경되어도, Client 코드가 변경되지 않도록 만들 수 있다.


정리.

Security 와 관련된 설정들을 정리해보았다.
기존 코드는 new를 사용해 객체를 생성하거나, Deprecated 된 부분에 대한 정리, WARN LOG 등에 대한 정리 안되어있었는데,

계속해서 더 나은 코드에 대해 생각하며 변경된 부분이 존재한다.
new 를 통해 객체를 생성하지 않는다거나, JWT 관련 부분에 대한 확장성을 위해 인터페이스화를 한다거나 등의 경험을 했다.(이와 관련된 부분은 다음 게시글에서 더 자세히 작성할 예정이다.)

해당 부분은 Security 관련된 부분만 작성했으므로,
가독성 있는 코드, DI/IoC 을 중심으로 코드를 보면 좋을 것 같다!

프로젝트 링크: Project CRUD 깃허브

profile
코드 중심보다는 느낀점과 생각, 흐름, 가치관을 중심으로 업로드합니다!

0개의 댓글