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에서는 username이 id로 통용되는 의미이며, 이 예제에서는 email로 id를 선언했다.)
- 예외 처리: 사용자 미존재 시 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
'SPRING BOOT' 카테고리의 다른 글
restserver,restclient (0) | 2024.11.16 |
---|---|
[spring boot]RestApi (0) | 2024.11.12 |
[spring boot] HTTP호출방식 (0) | 2024.11.11 |
[spring boot] JPA와 H2 데이터베이스로 간단한 게시판 만들기 (0) | 2024.11.09 |
[spring boot] 머스태치(mustache)/JPA (0) | 2024.11.08 |