1. Spring Security란?
Spring Security는 Java 애플리케이션에 보안 기능을 추가하는 강력한 프레임워크입니다. 이를 통해 인증(Authentication)과 권한 부여(Authorization) 기능을 손쉽게 구현할 수 있습니다.
2. Spring Security의 목적
Spring Security는 웹 애플리케이션의 보안을 강화하기 위해 설계된 프레임워크입니다.
2.1 주요목적
인증(Authentication) | 사용자가 누구인지 확인하는 과정 |
권한 부여(Authorization) | 인증된 사용자가 특정 리소스에 접근할 수 있는지 여부를 결정하는 과정 |
보안 정책 구현 | 세밀한 보안 규칙을 정의하여 애플리케이션의 다양한 부분에서 보안을 적용 |
2.2 인증(Authentication)
인증은 사용자가 누구인지를 확인하는 과정입니다. Spring Security에서는 다양한 인증 방법을 지원하며, 기본적으로 다음과 같은 흐름으로 인증을 처리합니다.
1. 인증 요청
• 사용자가 로그인 폼을 통해 아이디와 비밀번호를 입력하고, 서버에 인증 요청을 보냅니다.
2. 인증 필터(Authentication Filter)
• Spring Security는 인증 요청을 인터셉트하여 인증 필터를 통해 처리합니다. 기본적으로 UsernamePasswordAuthenticationFilter
가 사용됩니다.
3. 인증 매니저(Authentication Manager)
• 인증 필터는 인증 매니저에게 인증 요청을 전달합니다. 인증 매니저는 여러 AuthenticationProvider를 사용하여 인증을 시도합니다.
4. AuthenticationProvider
• AuthenticationProvider는 실제 인증 로직을 수행하는 컴포넌트입니다. 주로 DaoAuthenticationProvider가 사용되며, 이는 사용자 정보를 데이터베이스에서 조회하여 인증을 수행합니다.
5. 인증 성공 및 실패
• 인증이 성공하면 Authentication 객체가 생성되어 SecurityContext에 저장됩니다. 이 객체는 사용자의 인증 정보를 담고 있습니다.
• 인증이 실패하면 인증 실패 핸들러가 호출되어 적절한 응답을 반환합니다.
2.3 권한 부여(Authorization)
권한 부여는 인증된 사용자가 특정 리소스에 접근할 수 있는지 여부를 결정하는 과정입니다. Spring Security에서는 사용자의 역할(Role)과 권한(Authority)을 기반으로 접근을 제어합니다.
2.3.1 보안 설정(Security Configuration)
• Spring Security 설정 클래스에서 URL 경로, 메서드, 리소스에 대한 접근 권한을 설정합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.antMatchers("/", "/home").permitAll()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
}
2.3.2 역할과 권한
• Spring Security에서는 GrantedAuthority 인터페이스를 통해 권한을 부여합니다. 주로 SimpleGrantedAuthority 클래스가 사용되며, 역할은 ROLE_ 접두어를 가집니다.
• 예를 들어, ROLE_ADMIN은 관리자 권한을 의미합니다.
2.3.3 메서드 수준 보안(Method Level Security)
• Spring Security는 메서드 수준에서도 권한을 제어할 수 있습니다. 이를 위해 @PreAuthorize와 @Secured 어노테이션을 사용할 수 있습니다.
@Service
public class MyService {
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void adminMethod() {
// 관리자만 접근 가능
}
@Secured("ROLE_USER")
public void userMethod() {
// 사용자만 접근 가능
}
}
2.4 동작방식
3. 예제 코드
기능
- 회원가입
- 로그인
회원가입
- email, password를 local DB에 저장 (password는 암호화)
로그인
- 회원확인 후 회원페이지로 이동
WebSecurityConfig
package org.example.springsecuritydemo.config;
import lombok.RequiredArgsConstructor;
import org.example.springsecuritydemo.service.UserDetailService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
private final UserDetailService userService;
@Bean
public WebSecurityCustomizer configure() {
return (web) -> web.ignoring()
.requestMatchers(new AntPathRequestMatcher("/static/**"));
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers(
new AntPathRequestMatcher("/signup"),
new AntPathRequestMatcher("/login"),
new AntPathRequestMatcher("/user")
).permitAll()
.anyRequest().authenticated())
.formLogin(formLogin -> formLogin
.loginPage("/login")
.defaultSuccessUrl("/articles")
)
.logout(logout -> logout
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
)
.csrf(AbstractHttpConfigurer::disable)
.build();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, BCryptPasswordEncoder bCryptPasswordEncoder,
UserDetailService userDetailService) throws Exception {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userService);
authProvider.setPasswordEncoder(bCryptPasswordEncoder);
return new ProviderManager(authProvider);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserApiController
package org.example.springsecuritydemo.controller;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.example.springsecuritydemo.dto.AddUserRequest;
import org.example.springsecuritydemo.service.UserService;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
@RequiredArgsConstructor
@Controller
public class UserApiController {
private final UserService userService;
@PostMapping("/user")
public String signup(AddUserRequest request) {
userService.save(request);
return "redirect:/login";
}
@GetMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
new SecurityContextLogoutHandler().logout(request, response,
SecurityContextHolder.getContext().getAuthentication());
return "redirect:/login";
}
}
UserViewController
package org.example.springsecuritydemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class UserViewController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/signup")
public String signup() {
return "signup";
}
}
User
package org.example.springsecuritydemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class UserViewController {
@GetMapping("/login")
public String login() {
return "login";
}
@GetMapping("/signup")
public String signup() {
return "signup";
}
}
AddUserRequest
package org.example.springsecuritydemo.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AddUserRequest {
private String email;
private String password;
}
UserRepository
package org.example.springsecuritydemo.repository;
import java.util.Optional;
import org.example.springsecuritydemo.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
UserDetailService
package org.example.springsecuritydemo.service;
import lombok.RequiredArgsConstructor;
import org.example.springsecuritydemo.repository.UserRepository;
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;
@Service
@RequiredArgsConstructor
public class UserDetailService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException(email));
}
}
UserService
package org.example.springsecuritydemo.service;
import lombok.RequiredArgsConstructor;
import org.example.springsecuritydemo.domain.User;
import org.example.springsecuritydemo.dto.AddUserRequest;
import org.example.springsecuritydemo.repository.UserRepository;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public Long save(AddUserRequest dto) {
return userRepository.save(User.builder()
.email(dto.getEmail())
.password(bCryptPasswordEncoder.encode(dto.getPassword()))
.build()).getId();
}
}
실행 결과 및 로그
'Backend' 카테고리의 다른 글
<Backend> Spring / Batch (0) | 2024.07.10 |
---|---|
<Backend> Spring / QueryDSL (0) | 2024.07.09 |
<Backend> Java / 상속 (0) | 2024.04.05 |
<Backend> Java / Class (3) 접근제한자와 변수의 타입 (0) | 2024.04.04 |
<Backend> Java / Class (2) this와 메소드 (0) | 2024.04.04 |