2026. 4. 27. 21:03ㆍ대우개발원 수업 내용/spring boot, framework
https://docs.spring.io/spring-security/reference/index.html
를 참고 했습니다.
Spring Security 핵심 설정 및 커스텀 인증 시스템 구축
1. 보안 의존성 추가 및 기본 필터 체인(SecurityFilterChain) 설정
- 의존성: spring-boot-starter-security 추가 시 모든 경로가 잠기며 초기 비밀번호가 콘솔에 출력됨
- 필터 체인: HttpSecurity를 통해 인증·인가 정책을 정의하며, 거의 모든 보안 처리가 이 체인 내부에서 실행됨
- 동작: 서블릿 필터 단계에서 DelegatingFilterProxy가 스프링 빈과 연동하여 보안 로직을 위임함
2. 정적 리소스 예외 및 패스워드 암호화(PasswordEncoder)
- 정적 리소스: webSecurityCustomizer를 통해 CSS/JS 등은 보안 필터를 거치지 않도록 설정해 성능을 최적화함
- BCrypt: 비밀번호는 반드시 BCryptPasswordEncoder를 통해 암호화되어야 하며, 시큐리티는 이 빈을 자동 감지해 로그인 시 비교에 활용함
3. 사용자 정의 인증 로직(CustomUserDetailsService) 구현
- UserDetailsService: DB 연동 전, 특정 아이디("user1")와 암호화된 비번을 가진 UserDetails 객체를 수동 생성해 인증 통로를 마련함
- 유연성: 기본 메모리 방식 인증을 넘어, 향후 실제 DB의 회원 테이블과 연동할 수 있는 핵심 인터페이스임
4. 메서드 보안 및 @PreAuthorize 권한 제어
- 활성화: @EnableMethodSecurity를 설정 클래스에 추가하여 메서드 단위의 세밀한 보안 설정을 허용함
- 권한 체크: 컨트롤러 메서드 상단에 @PreAuthorize("hasRole('USER')")를 작성해 로그인된 특정 권한 사용자만 접근 가능하도록 제한함
5. 커스텀 로그인 페이지 연동 및 CSRF 최적화
- Login Page: 기본 제공 창 대신 /member/login 경로의 커스텀 페이지를 호출하도록 설정하고 MemberController로 폼을 띄움
- CSRF: 개발 편의 및 비동기 통신 테스트를 위해 CSRF 보호를 임시 비활성화(disable())하여 정상적인 폼 제출을 확인함
[요약]
Spring Security 핵심 설정 및 커스텀 인증 시스템 구축
1. SecurityFilterChain 설정을 통한 전역 보안 정책 및 필터 체인 제어
2. 정적 리소스(CSS/JS) 보안 제외 및 BCrypt 비밀번호 암호화 빈 등록
3. UserDetailsService 커스텀 구현으로 사용자 정보 로딩 및 인증 객체 생성
4. @EnableMethodSecurity와 @PreAuthorize 기반의 메서드 단위 접근 권한 통제
5. 사용자 정의 로그인 UI(/member/login) 연결 및 CSRF 비활성화 설정
b01Upload폴더를 복사해서 b01Security로 이름을 바꿔 붙여넣는다.


정상적으로 github에 업로드된 모습
bulid.gradle에
implementation 'org.springframework.boot:spring-boot-starter-security'
코드를 추가
CustomSecurityConfig 클래스를 추가

CustomSecurityConfig에 아래 코드를 입력
package com.example.b01.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.context.annotation.Configuration;
@Log4j2
@Configuration
@RequiredArgsConstructor
public class CustomSecurityConfig {
}
실행하면
로그인을 요청하는 화면을 볼 수 있음
패스워드가 로그에 뜨고 아이디는 기본 user이다
로그를 찍을 수 있게 아래 코드를 추가
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
log.info("-----------------configure---------------");
return http.build();
}
Security Filter Chain
Spring Security 에서 제공하는 인증,인가를 위한 필터들의 모음
Spring Security 에서 가장 핵심이 되는 기능을 제공하며, 거의 대부분의 서비스는 Security Filter Chain 에서 실행
기본적으로 제공하는 필터들이 있으며, 사용자는 개발의 취지와 목적에 맞게 커스텀 필터 또한 필터 체인으로 포함시켜 사용가능
Http 요청 -> Web Application Server(Servlet Container) -> 필터 1 -> 필터 2 ..... -> 필터 n -> Servlet -> 컨트롤러
Security Filter Chain 정의
Spring Security에서 제공하는 인증 및 인가를 위한 필터들의 모음임.
서비스의 핵심 기능을 담당하며 거의 대부분의 기능이 이 체인 내에서 실행됨.
기본 필터 제공 외에도 사용자의 목적에 따른 커스텀 필터 포함이 가능함.
동작 흐름 및 초기화
Http 요청이 WAS의 필터들을 거쳐 서블릿과 컨트롤러에 도달하는 구조임.
사용자의 인증 및 인가 과정이 어떻게 진행되는지 파악하는 것이 핵심임.
Application Context 초기화 과정에서 사용자가 정의한 설정에 따라 생성됨.
설정 클래스 내 HttpSecurity 객체가 설정을 기반으로 필터 체인을 구성함.
주요 필터 구성
1. SecurityContextPersistenceFilter
SecurityContextRepository에서 SecurityContext를 가져오거나 생성하는 역할임.
SecurityContext는 인증 객체인 Authentication을 저장하는 저장소이며 SecurityContextHolder를 통해 전역적 접근이 가능인증 이력에 따라 기존 컨텍스트를 가져오거나 새로 생성하여 저장함.
2. LogoutFilter
로그아웃 요청을 전담하여 처리하는 필터임. 요청 URI가 로그아웃 경로인지 확인한 후 세션 무효화,
쿠키 삭제, SecurityContext 내 토큰 삭제 등의 작업을 수행함. 설정 시 HttpSecurity 객체를 통해 관리함.
3. UsernamePasswordAuthenticationFilter
폼 기반 인증을 처리하며 아이디와 패스워드 데이터를 파싱하여 인증을 위임함. 인증 전 객체인 UsernamePasswordAuthenticationToken을 생성하여 AuthenticationManager에 전달함.
API 기반 인증 시에는 해당 필터를 비활성화하고 직접 구현한 필터를 사용함.
4. ExceptionTranslationFilter
필터 체인 실행 중 발생하는 예외를 처리함.
인증 실패 시에는 AuthenticationEntryPoint를 호출하여 로그인 페이지 리다이렉트나 상태 코드 반환을 수행함.
인가 예외 시에는 AccessDeniedHandler를 호출하여 권한 부족에 대한 예외 처리를 진행함.
application.properties 에 아래 코드 추가
logging.level.org.springframework.security=trace
실행하면
로그에서 적용되는 필터의 정보를 다 확인이 가능하다
CustomSecurityConfig 코드를 수정
Static에서는 필터를 사용하지 않겠다는 코드를 적용할 수 있다
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
log.info("-----------------web configure---------------");
return (WebSecurity web) -> web.ignoring().requestMatchers(PathRequest.
toStaticResources().atCommonLocations());
}
코드를 추가하고
상단에
@Bean
을 추가함
실행하면
http://localhost:8080/css/styles.css에 접근했을때
필터가 사용되지 않는다는 로그가 뜬다
🌱 DelegatingFilterProxy
사용자의 요청이 Spring MVC 에 도달하기 전, 즉 Servlet Container 에서 Delegating Filter Proxy 가 요청을 받습니다.
DelegatingFilterProxy 는 Servlet Container 와 Spring 의 Spring Container 을 연결해주는 필터입니다.
DelegatingFilterProxy 는 Servlet 스펙에 있는 기술이기 때문에 Servlet Container 에서만 생성되고 실행됩니다.
Spring 의 Spring Container 와는 다름, Spring Bean 으로 주입하거나 Spring 에서 사용되는 기술을 Servlet 에서 사용🚫
DelegatingFilterProxy 는 실제 보안 처리를 하지 않고 위임만 하는 Servlet Container 에서 동작하는 Servlet Filter
CustomSecurityConfig 클래스 안에 있는
SecurityFilterChain 메소드에 아래 코드를 추가
http.formLogin(withDefaults());
security 패키지를 추가하고
CustomUserDetailsService 클래스를 추가

CustomUserDetailsService 클래스에 아래 코드를 추가
스프링 시큐리티(Spring Security)에서 사용자 인증을 처리하기 위해 기본적으로 제공하는 설정 대신,
개발자가 직접 사용자 정보를 조회하고 검증할 수 있도록 설계된 사용자 정의 인증 서비스.
- 인증 인터페이스 구현: UserDetailsService 인터페이스를 상속받아 구현함으로써,
스프링 시큐리티의 표준 인증 프로세스 내에서 커스텀 로직(DB 연동 등)이 작동할 수 있는 통로를 마련함. - 사용자 식별 로직: loadUserByUsername 메서드를 오버라이드하여, 로그인 시 입력받은 아이디(username)를
기반으로 데이터베이스나 외부 저장소에서 사용자 정보를 가져오는 역할을 수행함. - 로깅 및 모니터링: log.info를 활용하여 실제 인증 요청이 들어온 사용자의 정보를 로그에 기록함으로써,
인증 프로세스의 진입 단계에서 정상 동작 여부를 확인할 수 있도록 함. - 인증 객체 반환 지점: 최종적으로 사용자의 비밀번호와 권한 정보가 담긴 UserDetails 객체를 반환해야 하며,
현재는 null을 반환하고 있어 실제 서비스 시에는 고유 객체를 구성하여 리턴하는 로직이 필요함. - 확장성 확보: 기본 메모리 방식의 사용자 관리가 아닌, 실제 애플리케이션의 도메인 모델(Member 등)과 연동하여
보안 시스템을 유연하게 확장할 수 있는 기초 구조를 제공
package com.example.b01.security;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
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;
@Log4j2
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("loadUserByUsername: " + username);
return null;
}
}
실행하고
에 접속하고
틀린 비밀번호를 입력하면

CustomUserDetailsService 에 코드를 수정
스프링 시큐리티의 인증 과정에서 특정 아이디로 로그인을 시도할 때,
시스템이 인식할 수 있는 사용자 정보(UserDetails)를 메모리상에 임시로 생성하여 반환하는 테스트용 인증 로직임.
- 사용자 정보 객체 생성: 스프링 시큐리티에서 제공하는 User.builder()를 사용하여 인증에 필요한
최소한의 정보인 아이디, 비밀번호, 권한을 포함하는 객체를 구성함. - 하드코딩된 인증 데이터: 데이터베이스 조회 대신 아이디는 "user1", 비밀번호는 "1111"로 고정하여,
실제 DB 연동 전 단계에서 로그인 기능의 정상 작동 여부를 확인하기 위한 용도로 사용함. - 권한 부여(Authorities): 해당 사용자에게 "ROLE_USER"라는 권한을 할당함으로써,
시큐리티 설정에서 특정 경로에 대해 접근 제어가 적절히 이루어지는지 테스트할 수 있게 함. - 인증 객체 반환: 생성된 userDetails 객체를 리턴함으로써 스프링 시큐리티는
이 정보와 사용자가 입력한 정보를 비교하여 로그인 성공 여부를 결정하게 됨. - 실제 연동을 위한 교두보: 현재는 고정된 값을 반환하고 있으나,
이후 단계에서는 이 메서드 내부에서 Repository를 호출하여 실제 DB에 저장된 회원 정보를 조회하는 로직으로 대체
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("loadUserByUsername: " + username);
UserDetails userDetails = User.builder()
.username("user1")
.password("1111")
.authorities("ROLE_USER")
.build();
return userDetails;
// return null;
}
실행하면
user1
1111으로 로그인을 하면 오류가 뜨는데 아직 패스워드 인코딩( DelegatingPasswordEncoder )을 안해서 그럼
CustomSecurityConfig 에 아래 코드 추가
스프링 시큐리티에서 비밀번호를 안전하게 암호화하기 위해 사용하는 BCryptPasswordEncoder를
스프링 빈(Bean)으로 등록하는 설정 로직임.
- 암호화 알고리즘 지정: 단방향 해시 함수인 BCrypt 알고리즘을 사용하는 객체를 생성하여, 비밀번호를 평문이 아닌 복잡한 암호문으로 저장할 수 있게 함.
- 빈 등록의 목적: @Bean 어노테이션을 통해 해당 객체를 스프링 컨테이너에 등록함으로써, 애플리케이션 어디에서든 PasswordEncoder를 주입받아 사용할 수 있는 공용 컴포넌트로 만듦.
- 보안 표준 준수: 사용자가 입력한 비밀번호를 데이터베이스에 저장하거나 로그인 시 비교할 때, 보안상 권장되는 암호화 방식을 적용하여 시스템의 보안 강도를 높임.
- 자동 설정과 연동: 스프링 시큐리티는 빈으로 등록된 PasswordEncoder를 자동으로 감지하여, 로그인 과정에서 제출된 비밀번호와 저장된 비밀번호를 비교할 때 내부적으로 활용함.
- 유연한 관리: 서비스 전체에서 동일한 암호화 방식을 일관되게 적용할 수 있으며, 나중에 암호화 정책이 변경되더라도 이 설정 부분만 수정하면 되므로 유지보수가 용이함.
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
CustomUserDetailsService 의 코드를 수정
스프링 시큐리티의 인증 과정에서 사용자 비밀번호를 안전하게 처리하기 위해 암호화 알고리즘을 도입하고,
인증 객체를 생성하는 로직임.
- 암호화 도구 설정: BCryptPasswordEncoder를 직접 생성하여 passwordEncoder 멤버 변수에 할당함으로써, 평문 비밀번호를 해시화된 문자열로 변환할 수 있는 기반을 마련함.
- 비밀번호 보안 강화: User.builder()를 통해 사용자 정보를 만들 때, 단순 문자열인 "1111"을 그대로 사용하지 않고 encode 메서드를 거쳐 암호화된 상태로 저장함.
- 시큐리티 표준 준수: 스프링 시큐리티는 저장된 암호와 입력된 암호를 비교할 때 암호화된 상태를 기대하므로, 인코딩 로직을 추가하여 실제 로그인 인증이 가능하도록 수정함.
- 사용자 정보 빌드: 아이디(user1), 암호화된 비밀번호, 그리고 ROLE_USER라는 권한을 하나의 UserDetails 객체로 묶어 시큐리티 인증 엔진에 전달함.
- 로깅을 통한 추적: 메서드 시작 부분에 로그를 남겨 어떤 아이디로 인증 요청이 들어왔는지 확인하며, 리턴된 객체는 이후 시큐리티 시스템에 의해 비밀번호 일치 여부 검증에 쓰임
//@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private PasswordEncoder passwordEncoder;
public CustomUserDetailsService() {
this.passwordEncoder = new BCryptPasswordEncoder();
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("loadUserByUsername: " + username);
UserDetails userDetails = User.builder()
.username("user1")
// .password("1111")
.password(passwordEncoder.encode("1111")) // 패스워트 인코드
실행하면
Spring Security @EnableMethodSecurity
Spring Security의 메서드 수준 보안을 활성화하는 데 사용되는 어노테이션입니다.
이 어노테이션을 통해 특정 메서드 또는 클래스에 접근 제어를 적용할 수 있습니다.
@EnableMethodSecurity는 다양한 메서드 보안 어노테이션(@PreAuthorize, @PostAuthorize, @Secured 등)을
사용할 수 있게 해줍니다.
• 주요 기능
@PreAuthorize: 메서드가 호출되기 전에 접근을 허용할지 결정합니다.
@PostAuthorize: 메서드가 호출된 후에 접근을 허용할지 결정합니다.
@Secured: 특정 롤(role)로 접근을 제한합니다.
@RolesAllowed: 특정 롤(role)로 접근을 제한합니다 (JSR-250)
• 어노테이션 내 사용 가능한 함수
hasRole([role]) : 현재 사용자의 권한이 파라미터의 권한과 동일한 경우 true
hasAnyRole([role1,role2 ...]) : 현재 사용자의 권한 파라미터의 권한 중 일치하는 것이 있는 경우 true
principal: 사용자를 증명하는 주요객체(User)를 직접 접근할 수 있다.
authentication : SecurityContext에 있는 authentication 객체에 접근 할 수 있다.
permitAll : 모든 접근 허용
denyAll : 모든 접근 비허용
isAnonymous() : 현재 사용자가 익명(비로그인)인 상태인 경우 true
isRememberMe() : 현재 사용자가 RememberMe 사용자라면 true
isAuthenticated() : 현재 사용자가 익명이 아니라면 (로그인 상태라면) true
isFullyAuthenticated() : 현재 사용자가 익명이거나 RememberMe 사용자가 아니라면 true
CustomUserDetailsService 클래스에
메서드 단위에서 @PreAuthorize나 @PostAuthorize 같은 보안 어노테이션을 사용하여 권한 제어를
세밀하게 설정할 수 있도록 활성화하는 설정
@EnableMethodSecurity(prePostEnabled = true)
를 사용할 수 있게 true를 주는 어노테이션을 추가
BoardController @GetMapping("/register")위에 아래 코드를 추가
메서드 실행 전 호출자의 권한을 체크하여, USER 권한(ROLE_USER)을
가진 사용자만 해당 기능을 사용할 수 있도록 제한하는 보안 설정
@PreAuthorize("hasRole('USER')")
실행하면

를 삭제하고
다시 http://localhost:8080/board/register 로 접속하면
로그인창이 먼저 뜨고


USER / 1111를 입력해서 register화면으로 들어갈 수 있게 된다.
CustomSecurityConfig 코드를 수정
스프링 시큐리티의 기본 로그인 페이지 대신 사용자가 직접 만든 /member/login 경로의
커스텀 로그인 페이지를 사용하도록 설정하는 코드
// http.formLogin(withDefaults());
http.formLogin(httpSecurityFormLoginConfigurer -> {
httpSecurityFormLoginConfigurer.loginPage("/member/login");
});
하지만 현재 member는 없고 board 밖에 없기 때문에
member에 접근하는 MemberController가 있어야 함
MemberController 생성 한다.

package com.example.b01.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/member")
@Log4j2
@RequiredArgsConstructor
public class MemberController {
@GetMapping("/login")
public void loginGET(String errorCode, String logout) {
log.info("login get.................");
log.info("logout : " + logout);
}
}
member폴더 추가 후 login.html생성

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>LOGIN Page</h1>
</body>
</html>
프로젝트를 실행해서 http://localhost:8080/board/register에 접속하면
member/login으로 리다이렉트가 되고
http://localhost:8080/login으로 접속하면 에러 페이지가 뜬다.
이제 로그인 페이지의 디자인을 바꿔야하는데
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-
fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Simple Sidebar - Login</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" th:href="@{/assets/favicon.ico}" />
<!-- Core theme CSS (includes Bootstrap)-->
<link th:href="@{/css/styles.css}" rel="stylesheet" />
</head>
<body class="align-middle" >
<div class="container-fluid d-flex justify-content-center" style="height:100vh">
<div class="card align-self-center">
<div class="card-header">
LOGIN Page
</div>
<div class="card-body">
<form id="registerForm" action="/member/login" method="post">
<div class="input-group mb-3">
<span class="input-group-text">아이디</span>
<input type="text" name="username" class="form-control"
placeholder="USER ID">
</div>
<div class="input-group mb-3">
<span class="input-group-text">패스워드</span>
<input type="text" name="password" class="form-control"
placeholder="PASSWORD">
</div>
<div class="my-4">
<div class="float-end">
<button type="submit" class="btn btn-primary submitBtn">LOGIN</button>
</div>
</div>
</form>
</div><!--end card body-->
</div><!--end card-->
</div>
</body>
</html>
바꾸고 실행하면
디자인은 폼은 제대로 나온다
하지만 아직 controller에서 로그만 보내고 보내주질 않기때문에
CustomSecurityConfig 코드에 추가
http.csrf(httpSecurityCsrfConfigurer -> {
httpSecurityCsrfConfigurer.disable();
});
이 코드를 추가하고 로그를 확인해보면 csrf에 대한 요청을 더 이상 하지 않는것을 확인할 수 있다.
실행하면
http://localhost:8080/board/register에 접속하면
member/login으로 리다이렉트가 되고 로그인을 했을때, http://localhost:8080/board/register로 정상적으로 이동이된다.
'대우개발원 수업 내용 > spring boot, framework' 카테고리의 다른 글
| 자바 스프링 부트 16일차 / b01Security JPA 연동 실무 회원 엔티티 설계 및 중복 방지 회원가입 시스템 구축 (0) | 2026.04.29 |
|---|---|
| 자바 스프링 부트 15일차 / b01Security Spring Security 기반 자동 로그인 구현 및 사용자별 보안 로직 강화 (0) | 2026.04.28 |
| 자바 스프링 부트 13일차 b01Upload (1) | 2026.04.24 |
| 자바 스프링 부트 13일차 b01Upload (1) | 2026.04.23 |
| 자바 스프링 부트 12일차 b01Upload /첨부파일 비동기 업로드 구현 및 파일 정보를 포함한 게시글 등록 처리 (0) | 2026.04.22 |


















