ToBe끝판왕

[ 스프링 시큐리티 ] Spring Security 개념 및 설정 본문

■ 프로그래밍 SKILLS/SPRING FRAMEWORK

[ 스프링 시큐리티 ] Spring Security 개념 및 설정

업그레이드중 2024. 12. 16. 22:04
반응형

 

 


 

 

스프링 시큐리티

 

 

•   스프링 시큐리티( Spring Sercurity ) 란 ?

 

 

•  스프링 프레임워크 기반 애플리케이션의 보안을 담당하는 하위 프레임워크

-   복잡한 로직 없이도 어노테이션으로 설정 가능

-   기본적으로 세션 기반 인증 제공

-   기본적으로 필터 기반으로 동작

 

※ 필터란 ?

-  웹 어플리케이션에 도달하기 전 또는 후에 요청(Request) 과 응답(Response)를 가로채서 특정 작업을 수행 가능토록 한다.

-  요청 흐름  :  클라이언트 → 필터 체인 → 서블릿 → 응답 흐름: 서블릿 → 필터 체인 → 클라이언트

 인증과 권한부여 처리 가능

-  로깅 및 감사 가능

-  보안 관련 작업 ( 헤더 추가 및 요청 차단 가능 )

 

 

•  인증 ( Authentication )

-   사용자의 신원 확인

-  허가된 사용자만 시스템 접근 가능

-  사용자의 이름(userName) 과 비밀번호(password) 기반으로 인증

 

 

•  권한

-   인증된 사용자에게 특정 기능 허용

-  역할(Role) 과 권한(Authority) 기반으로 접근 제어

 

 

•  스프링 시큐리티 인증처리 과정

 

 

-   사용자가 폼에 아이디 , 패스워드 입력

-  HttpServletRequest 에 아이디 , 패스워드 정보가 전달

-  AuthenticationFilter 가 넘어온 아이디 , 패스워드 정보의 유효성 검사를 실시

-  유효성 검사 이후, 실제 구현체인 UsernamePasswordAuthenticationToken을 만들어 넘겨준다.

-  인증용 객체인 UsernamePasswordAuthenticationToken을 AuthenticationManager 에게 전달

UsernamePasswordAuthenticationToken 을 AuthenticationProvider 에게 전달

-  사용자 아이디를 UserDetailService 로 보내고, UserDetailService 는 사용자 아이디로 찾은 정보를

    UserDetails 객체로 만들어 AuthenticationProvider 에게 전달

-  DB에 저장되어 있는 사용자 정보를 가져와 입력정보와 UserDetails 의 정보를 비교해 인증처리 진행

-  인증이 완료되면 SercurityContextHolder 에 Authentication 을 저장

    인증이 성공하면 AuthenticationSuccessHandler, 실패하면 AuthenticationFailureHandler 실행

 

 


 

 

 

•  스프링 시큐리티 적용

-   먼저 스프링 시큐리티( Spring Sercurity) 를 사용하기 위해서 의존성을 주입해야 한다.

-  build.gradle 안 dependencies 블록에 의존성 추가

* build.gradle 에 아래내용 추가

// 스프링 시큐리티 코어 의존성 추가
implementation 'org.springframework.boot:spring-boot-starter-security'

 

 

 

•  스프링 시큐리티 설정( Config ) 클래스 파일

-  Spring Sercurity 설정 클래스 파일 작성

 

1)  AuthenticationManager 빈 등록

-  AuthenticationManager 는 스프링 시큐리티에서 인증을 담당하는 핵심 인터페이스

-  사용자의 인증정보를 검증하고 Authentication 객체 생성

-  스프링 시큐리티 5.7 이상 버전부터는 AuthenticationManager 를 직접 생성하고 빈으로 등록해야 한다.

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

 

 

 

2) 사용자 정보 조회 UserDetailService 의존성 주입

-  loadUserByUsername(String username) 메서드를 구현하여 특정 사용자 이름(username) 에 해당하는 사용자 정보

    (UserDetails)를 반환

    // 사용자 정보 조회 Service 클래스 주입
    private UserDetailService userDetailService;

 

 

 

3) 비밀번호 암호화 PasswordEncoder 의존성 주입

-  스프링 시큐리티에서는 비밀번호를 항상 암호화된 상태로 데이터베이스에 저장하고 비교한다.

-  BCryptPasswordEncoder 는 스프링 시큐리티에서 가장 널리 사용되는 암호화 클래스이다.

   ( BCrypt 알고리즘을 사용하여 비밀번호를 암호화하고 검증 )

    // 비밀번호 암호화
    private PasswordEncoder passwordEncoder;

 

 

-  비밀번호 암호화 설정 클래스 파일

package com.wsd.invest.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordEncoderConfig {


    // 비밀번호 암호화
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

 

 

 

4) Spring SecurityFilterChain 정의

-  SecurityFilterChain 은 HTTP 요청을 처리하는 필터들이 연결된 집합체

-  CSRF( Cross Site Request Forgery ) 설정

-  권한에 따른 HTTP URL 설정

    // HTTP 요청에 따른 보안정책 정의 (스프링 시큐리티 버전 5.7 이상 버전 방식 )
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                // CSRF 비활성화 (필요시 활성화 가능)
                .csrf(csrf -> csrf.disable())

                // 권한에 따른 HTTP url 설정
                .authorizeHttpRequests(auth -> auth
                        // 메인 / 로그인 / 회원가입 / .jsp 파일 경로 접근 허용
                        .requestMatchers("/user/main", "/user/login", "/user/join", "/WEB-INF/views/**").permitAll()
                        // 정적 리소스 허용
                        .requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
                        // forward / include 타입 요청 허용
                        .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
                        .dispatcherTypeMatchers(DispatcherType.INCLUDE).permitAll()
                        // 나머지 요청 인증 필요
                        .anyRequest().authenticated()
                )

                // 로그인 설정
                .formLogin(form -> form
                        // Get 요청 로그인 페이지
                        .loginPage("/user/login")
                        // Post 요청 로그인 처리 Url
                        .loginProcessingUrl("/user/login")
                        // 로그인 성공 url
                        .defaultSuccessUrl("/user/main", true)
                        // 로그인 실패 url
                        .failureUrl("/user/login?error=true")
                        .permitAll()
                )

                // 로그아웃 설정
                .logout(logout -> logout
                        // POST 요청 로그아웃 처리 Url
                        .logoutUrl("/user/logout")
                        // 로그아웃 성공 url
                        .logoutSuccessUrl("/user/login")
                        // 로그아웃 시 세션쿠키 삭제
                        .deleteCookies("JSESSIONID")
                        .permitAll()
                );

                return http.build();
    }

 

 

 

※  View 렌더링 문제

-  JSP 를 렌더링 하는데 스프링 시큐리티 권한 및 URL 설정에서 오류가 뜨는 문제 발생

-  설명 및 해결방법 아래 링크 참고

https://baby9235.tistory.com/128

 

[ 스프링 시큐리티 ] 스프링 시큐리티 View 렌더링 문제

스프링 시큐리티 설정파일 View 렌더링 설정  • Spring Security 설정 파일-  스프링 부트 3.x 이상 버전-  스프링 시큐리티 5.7 이상 버전-  기존 회원가입 페이지 url 허용함  • 회원가입 URL 입력

baby9235.tistory.com

 

 

 

 

5) 스프링 시큐리티 설정 클래스 파일 최종본

package com.wsd.invest.security;

import jakarta.servlet.DispatcherType;
import org.springframework.beans.factory.annotation.Autowired;
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.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.firewall.DefaultHttpFirewall;
import org.springframework.security.web.firewall.HttpFirewall;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    // 회원 정보 조회 Service 클래스 주입
    private UserDetailService userDetailService;

    private PasswordEncoder passwordEncoder;

    @Autowired
    public SecurityConfig(UserDetailService userDetailService, PasswordEncoder passwordEncoder) {
        this.userDetailService = userDetailService;
        this.passwordEncoder = passwordEncoder;
    }

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

    // "//" 허용
    @Bean
    public WebSecurityCustomizer webSecurityCustomizer() {
        return (web) -> web.httpFirewall(defaultHttpFirewall());
    }

    @Bean
    public HttpFirewall defaultHttpFirewall() {
        return new DefaultHttpFirewall();
    }

    // HTTP 요청에 따른 보안정책 정의 (스프링 시큐리티 버전 5.7 이상 버전 방식 )
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
                // CSRF 비활성화 (필요시 활성화 가능)
                .csrf(csrf -> csrf.disable())

                // 권한에 따른 HTTP url 설정
                .authorizeHttpRequests(auth -> auth
                        // 메인 / 로그인 / 회원가입 / .jsp 파일 경로 접근 허용
                        .requestMatchers("/user/main", "/user/login", "/user/join", "/WEB-INF/views/**").permitAll()
                        // 정적 리소스 허용
                        .requestMatchers("/css/**", "/js/**", "/images/**").permitAll()
                        // forward / include 타입 요청 허용
                        .dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
                        .dispatcherTypeMatchers(DispatcherType.INCLUDE).permitAll()
                        // 나머지 요청 인증 필요
                        .anyRequest().authenticated()
                )

                // 로그인 설정
                .formLogin(form -> form
                        // Get 요청 로그인 페이지
                        .loginPage("/user/login")
                        // Post 요청 로그인 처리 Url
                        .loginProcessingUrl("/user/login")
                        // 로그인 성공 url
                        .defaultSuccessUrl("/user/main", true)
                        // 로그인 실패 url
                        .failureUrl("/user/login?error=true")
                        .permitAll()
                )

                // 로그아웃 설정
                .logout(logout -> logout
                        // POST 요청 로그아웃 처리 Url
                        .logoutUrl("/user/logout")
                        // 로그아웃 성공 url
                        .logoutSuccessUrl("/user/login")
                        // 로그아웃 시 세션쿠키 삭제
                        .deleteCookies("JSESSIONID")
                        .permitAll()
                );

                return http.build();
    }

}

 

 

 

 


 

 

 

•  UserDetailService 클래스 파일

-  데이터베이스 등에서 사용자 정보를 조회하여 UserDetails 객체를 생성

-  UserDetailsService 인터페이스를 상속받아 작성

-  loadUserByUsername 메서드 사용  :  사용자 인증을 위한 핵심적인 메서드

   주어진 사용자 이름(username)을 바탕으로 데이터베이스에서 사용자 정보를 조회하여 UserDetails 객체 반환

 

public UserDetails loadUserByUsername(String memberId) throws UsernameNotFoundException {

        // 사용자 정보 조회 ( 타입 = UserDetails 인터페이스를 구현한 클래스 )
        CustomUserDetails user = userDetailMapper.findByUsername(memberId);

        // 사용자 정보 조회 X -> 예외
        if( user == null ) {
            throw new UsernameNotFoundException("User Not Found with memberId: " + memberId);
        }

        // 사용자의 권한을 GrantedAuthority로 변환
        List<SimpleGrantedAuthority> authorities = Collections.singletonList(
                new SimpleGrantedAuthority(user.getRole())
        );

        // User 객체 생성
        return User.builder()
                .username(user.getUsername())
                .password(user.getPassword())
                .build();
    }

 

 

 

 

 

•  CustomusterDetails클래스 파일

-  UserDetails 객체의 구체적인 형태를 정의한다.

-  UserDetails 를 상속받아 작성

-  메서드들을 통해서 사용자의 정보를 제공

-  getUsername()  :  사용자의 고유ID를 반환 ( 일반적으로 회원ID, 이메일 주소, 사용자의 이름 )

-  getPassword()  :  사용자의 암호화된 비밀번호 반환

-  getAuthorities()  :   사용자가 가지고 있는 권한 반환 ( ROLE_USER, ROLE_ADMIN 등 )

-  isEnabled()  :  사용자 계정이 활성화 되어있는지 반환

package com.wsd.invest.security;


import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
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.Date;
import java.util.List;

@Data
public class CustomUserDetails implements UserDetails {


    // DB 에서 PK 값
    private String memberId;


    @Pattern(regexp = "^[가-힣]{2,}$", message = "회원 이름은 2글자 이상의 한글로 입력하세요.")
    @NotBlank(message = "회원 이름은 필수값입니다.")
    private String memberNm;

    // 비밀번호
    @NotBlank(message = "비밀번호는 필수값입니다.")
    @Pattern(regexp = "^(?=.*[a-zA-Z])(?=.*[!@#$%^*+=-])(?=.*[0-9]).{8,15}$", message = "비밀번호는 8~15자 영문 대 소문자, 숫자, 특수문자를 사용하세요.")
    private String memberPw;

    // 역할기반 권한
    private String role;

    @Pattern(regexp = "^\\d{2}$", message = "회원 나이는 2글자의 숫자로 입력하세요.")
    @NotBlank(message = "회원 나이는 필수값입니다.")
    private String memberAge;

    @NotBlank(message = "올바른 이메일 형식이 아닙니다.")
    private String memberMail;

    private Date regDt;
    private String regId;
    private Date modDt;
    private String modId;

    public CustomUserDetails() {
        this.memberId = memberId;
        this.memberNm = memberNm;
        this.memberPw = memberPw;
        this.role = role;
        this.memberAge = memberAge;
        this.memberMail = memberMail;
        this.regDt = regDt;
        this.regId = regId;
        this.modDt = modDt;
        this.modId = modId;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(role));
    }

    @Override
    public String getPassword() {
        return memberPw;
    }

    // 스프링 시큐리티에서 username이 memberId를 반환하도록 설정
    @Override
    public String getUsername() {
        return memberId;
    }
}

 

 

 

 

 

 

▶  Spring Security Config 클래스 파일, CustomUserDetails 클래스 파일, UserDetailService 클래스

       파일을 세팅하고 작성함으로써 기본적인 설정을 마무리 하였다.

반응형
Comments