SPRING BOOT

[spring boot] Spring Security

라텐느 2024. 11. 18. 14:50
반응형

Spring Security는 강력한 인증 및 인가 기능을 제공하여 웹 애플리케이션의 보안을 효과적으로 관리한다. 유연한 보안 설정과 다양한 인증 방식을 지원하며, CSRF(Cross-Site Request Forgery는 공격자가 사용자의 브라우저를 속여서 사용자가 의도하지 않은 요청을 실행하도록 만드는 공격) 보호 비밀번호 암호화 기능을 통해 안전성을 강화한다. 이때, interceptor는 요청과 응답을 가로채어 로깅, 성능 모니터링 등을 수행하는 데 사용되지만, 애플리케이션의 보안 기능에는 영향을 주지 않는다. 따라서 보안 요구 사항에 따라 Spring Security만으로도 충분히 안전한 애플리케이션을 구축할 수 있게 된다.

전체적인 흐름은 다음과 같다.

 

1.WebSecurityConfig

    @Bean
    public WebSecurityCustomizer configure() {
        return (web) -> web.ignoring()
                .requestMatchers(PathRequest.toH2Console())
                .requestMatchers("/static/**", "/index.html", "/");
    }

.authorizeHttpRequests(auth -> auth

  • return (web) -> web.ignoring()  : 특정 요청을 스프링 시큐리티의 인증 및 인가 처리에서 제외
  • requestMatchers( )  : 요청이나 경로를 제외한다.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests(auth -> auth
            .requestMatchers("/", "/index.html", "/login", "/signup", "/user").permitAll()
            .requestMatchers("/home").authenticated()
            .anyRequest().authenticated()
        )
        .formLogin(form -> form
            .loginPage("/login")
            .loginProcessingUrl("/login")
            .defaultSuccessUrl("/home", true)
            .failureUrl("/login?error=true")
            .permitAll()
        )
        .logout(logout -> logout
            .logoutSuccessUrl("/login")
            .invalidateHttpSession(true)
        )
        .csrf(csrf -> csrf.disable());

    return http.build();
}
  • .authorizeHttpRequests(auth -> auth) : 특정 경로에 대해 인증 및 접근 권한을 설정한다.
로그인 설정
  • permitAll() : 모든 사용자가 접근 가능.(괄호안의 경로는 로그인 가능)
  • authenticated() : 인증된 사용자만 접근 가능 (∴/home은 로그인이 필수이다.)
  • anyRequest() : 위에서 지정되지 않은 모든 경로는 인증 필요.
  • loginPage("/login"): Get방식, 사용자가 로그인할 페이지 경로를 지정.(컨트롤러 주소)
  • loginProcessingUrl("/login"): Put방식, 로그인 요청을 처리하는 URL.
  • defaultSuccessUrl("/home", true): 로그인 성공 후 강제로 /home으로 리다이렉션한다.
  • failureUrl("/login?error=true"): 로그인 실패 시 다시 로그인 페이지로 리다이렉션한다.
로그아웃 설정

 

  • logoutSuccessUrl("/login"): 로그아웃 후 /login으로 이동.
  • invalidateHttpSession(true): 세션 무효화.
csrf.disable(): CSRF비활성화
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setUserDetailsService(userService);
    provider.setPasswordEncoder(bCryptPasswordEncoder());
    provider.setHideUserNotFoundExceptions(false);
    return provider;
}
  • AuthenticationManager는 인증 시도 시 생성된 UsernamePasswordAuthenticationToken 객체를 받아 인증 절차를 시작한다. 이 과정에서 AuthenticationManager는 DaoAuthenticationProvider를 사용하여 데이터베이스 기반 인증을 처리한다.
  • setUserDetailService: 사용자 정보를 로드하는 데 사용하낟.
  • passwordEncoder: 비밀번호 암호화 및 검증 처리.
  • setHideUserNotFoundExceptions(false) : 사용자 미존재 시 예외를 표시하여 디버깅 정볼르 추가로 확인할 수 있도록 예외 핸들러를 추가한다.
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
    return new BCryptPasswordEncoder();
}

: 비밀번호 암호화시 인콛러로 사용할 빈 등록

 

2.User(Entity)

@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
@Builder
public class User implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String email;

    private String password;

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

    // 이하 생략...
}
  • @Entity: 이 클래스는 데이터베이스의 users 테이블과 매핑됨. MyBatis와 비슷한 역할
  • UserDetails 구현: Spring Security에서 사용자 인증 정보를 관리하는 표준 인터페이스.
  • 권한 (getAuthorities): 기본적으로 "user" 권한을 부여.

3.UserRepository

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}
  • JpaRepository: CRUD 및 쿼리 메서드를 제공.(Mapper와 비슷한 역)
  • findByEmail: 이메일(username)을 기준으로 사용자 검색.

4.UserDetailService

@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
    User user = userRepository.findByEmail(email)
        .orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + email));
    return User.builder()
        .email(user.getEmail())
        .password(user.getPassword())
        .build();
}

 

  • email로 사용자 정보 조회.(이때, springsecruriy에서는 usernameid로 통용되는 의미이며, 이 예제에서는 emailid를 선언했다.)
  • 예외 처리: 사용자 미존재 시 UsernameNotFoundException 발생.
  • 주의사항: return시 builder()로 시작했으면 build()로 종료해야함.

5. UserService

public void save(AddUserRequest request) {
    User user = User.builder()
        .email(request.getEmail())
        .password(passwordEncoder.encode(request.getPassword()))
        .build();
    userRepository.save(user);
}

 

  • .password(passwordEncoder.encode(request.getPassword())) : 비밀번호를 인코딩해 암호화
  • userRepository.save(user); : 요청을 받아 데이터베이스에 저장하고 사용자 생성

6.UserApiController

@PostMapping("/user")
public String signup(AddUserRequest request) {
    userService.save(request);
    return "redirect:/login";
}
  • AddUserRequest를 받아 사용자 생성

7.UserViewController

    @GetMapping("/home")
    public String home(Model model,
    		@AuthenticationPrincipal User userInfo) throws Exception {
        model.addAttribute("userInfo", userInfo);
        return "home";
    }
    
    @GetMapping("/logout")
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        new SecurityContextLogoutHandler().logout(request, response,
                SecurityContextHolder.getContext().getAuthentication());
        return "redirect:/login";
    }
  • Spring Security는 로그인 성공 시 **SecurityContext**에 사용자의 인증 정보를 저장한다. 이 정보는 **Principal**이라는 객체에 저장되며, Spring Security는 이 객체를 통해 사용자 정보를 관리한다. @AuthenticationPrincipal을 사용하면, Spring Security가 자동으로 현재 로그인한 사용자 객체를 메서드 파라미터로 주입한다.

8.Tymeleaf 템플릿

<html xmlns:th="http://www.thymeleaf.org">
  <body>
    <div class="main-container">
        <h1>로그인 성공!</h1>
        <p>놀랍지만 메인 페이지입니다.</p>
        <div>
		<span th:text="${userInfo.email} 님 환영합니다."></span>
		<br>
		    <span th:if="${userInfo.password} != null"  
		    	th:text="${userInfo.password}  !!! 비번 유출 주의"></span>
		</div>
		<button type="button" class="btn btn-secondary" onclick="location.href='/logout'">로그아웃</button>
		</body>
    </div>
</body>
</html>
  • ${userInfo.email} : EL 
  • |...| (파이프 기호): Thymeleaf에서 문자열 리터럴을 감싸는 구문이다. 이 기호를 사용하여 문자열 내부에서 EL을 간단하게 사용할 수 있다.
  • 즉, "| |" 사이의 내용은 문자열로 인식되며, ${...} 형태의 표현식을 포함할 수 있다.

참고: https://innovation123.tistory.com/38#2)%20%5B%5B%E2%80%A6%5D%5D-1

 

[Thymeleaf] 기본 문법 (text, 변수표현, URL 링크, 리터럴)

텍스트 - text, utext 1. 내용 변경 : th:text , [[…]] "th:text" 사용 : 텍스트 컨텐츠 안에서 직접 출력하기 : [[${data}]] 1) th:text 내용의 값을 th:text 의 값으로 변경한다. 여기서는 ‘텍스트’을 ${data} 의 값

innovation123.tistory.com

 

전체적인 흐름

반응형