Spring Security 사용해보기

이상혁·2024년 2월 12일
0

이제 Spring Security를 사용해보자

Security Config 설정

package com.github.backproject.config.security;

import com.github.backproject.web.filter.JwtAuthenticationFilter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {

    private final JwtProvider jwtProvider;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.headers(httpSecurityHeadersConfigurer -> httpSecurityHeadersConfigurer.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
                .formLogin(AbstractHttpConfigurer::disable)
                .csrf(AbstractHttpConfigurer::disable)
                .httpBasic(AbstractHttpConfigurer:: disable)
                .rememberMe(AbstractHttpConfigurer::disable)
                .sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(authorize -> authorize.requestMatchers("/resources/static/**", "/api/*").permitAll())
                .exceptionHandling(httpSecurityExceptionHandlingConfigurer -> httpSecurityExceptionHandlingConfigurer
                        .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                        .accessDeniedHandler(new CustomerAccessDeniedHandler()))
                .addFilterBefore(new JwtAuthenticationFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class);

        return http.build();

    }

    @Bean
    public AuthenticationManager authenticationManager (AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }
}

SecurityFilterChain을 빈으로 등록을 해주고 AuthenticationManager도 빈으로 등록을 해준다.

JwtAuthenticationFilter 만들기

package com.github.backproject.web.filter;

import com.github.backproject.config.security.JwtProvider;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtProvider jwtProvider;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String jwtToken = jwtProvider.resolveToken(request);

        if(jwtToken != null && jwtProvider.validateToken(jwtToken)) {
            Authentication auth = jwtProvider.getAuthentication(jwtToken);
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }
}

UsernamePasswordAuthenticationFilter를 실행하기 전에 토큰을 먼저 확인하고 넣어주는 커스텀 필터를 만들어서 넣어준다.

UserDetailes 구현하기

package com.github.backproject.respository.UserDetails;

import lombok.*;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

@Builder
@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class CustomerUserDetail implements UserDetails {

    private Integer user_id;
    private String email;
    private String password;
    private List<String> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return null;
    }

    public String getEmail() {
        return this.email;
    }

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

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

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

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

사용자의 정보를 담는 인터페이스이다.
인터페이스를 구현해주어야 한다.

UserDetailsService

package com.github.backproject.service.security;

import com.github.backproject.respository.UserDetails.CustomerUserDetail;
import com.github.backproject.respository.roles.RolesEntity;
import com.github.backproject.respository.userPrincipal.UserPrincipalEntity;
import com.github.backproject.respository.userPrincipal.UserPrincipalRepository;
import com.github.backproject.respository.userPrincipalRoles.UserPrincipalRolesEntity;
import com.github.backproject.service.exceptions.NotFoundException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.stream.Collectors;

@Primary
@RequiredArgsConstructor
@Service
@Slf4j
public class CustomUserDetailsService implements UserDetailsService {

    private final UserPrincipalRepository userPrincipalRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        UserPrincipalEntity userPrincipalEntity = userPrincipalRepository.findByEmailFetchJoin(email)
                .orElseThrow(() -> new NotFoundException("email에 해당하는 UersPrincipal이 없습니다"));

        CustomerUserDetail customerUserDetail = CustomerUserDetail.builder()
                .user_id(userPrincipalEntity.getUserEntity().getUserId())
                .email(userPrincipalEntity.getEmail())
                .password(userPrincipalEntity.getPassword())
                .authorities(userPrincipalEntity.getUserPrincipalRoles().stream().map(UserPrincipalRolesEntity::getRolesEntity).map(RolesEntity::getName).collect(Collectors.toList()))
                .build();

        return customerUserDetail;

    }
}

DB에서 유저의 정보를 가져오는 역할을 하는 인터페이스이다.
DB에서 유저의 정보를 가지고 와서 UserDetailes를 구현한 구현체에 담아 주어야 한다.

Login 구현하기

	public String login(Login loginRequest) {
          try {
              String email = loginRequest.getEmail();
              String password = loginRequest.getPassword();

              Authentication auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(email, password));
              SecurityContextHolder.getContext().setAuthentication(auth);

              UserPrincipalEntity userPrincipalEntity = userPrincipalRepository.findByEmailFetchJoin(email)
                      .orElseThrow(() -> new NotFoundException("UserPrincipal을 찾을 수 없습니다."));

              UserEntity userEntity = userRepository.findByEmail(email).orElseThrow(
                      () -> new NotFoundException("userEntity를 찾을 수 없습니다")
              );


              List<String> roles = userPrincipalEntity.getUserPrincipalRoles()
                      .stream().map(UserPrincipalRolesEntity::getRolesEntity).map(RolesEntity::getName).collect(Collectors.toList());

              return jwtProvider.createToken(userEntity, roles);

          } catch (Exception e) {
              throw new NotFoundException("로그인 할 수 없습니다");
          }
      }

이메일과 비밀번호로 UsernamePasswordAuthenticationToken을 만들고 이 토큰을 AuthenticationManager에게 전달하여 Authentication 인증용 객체를 받는다.
그리고 이를 SecurityContextHolder에 담아주어서 사용을 한다.

profile
개발 공부 하기 위해 만든 블로그

0개의 댓글