스프링 시큐리티는 인증(Authentication)과 인가(Authorization) 기능을 제공하여 애플리케이션의 보안을 강화한다.
적용을 함으로써
1. csrf 공격을 방어하기 위한 기능을 기본적으로 제공하며,
2. URL 기반의 접근 제어를 통해 특정 리소스에 대한 접근 권한을 세밀하게 관리할 수 있다
package com.example.TodoList.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.OAuth2ClientDsl;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//csrf disable
http
.csrf((auth) ->auth.disable());
//form 로그인 방식 disable
http
.formLogin((auth) -> auth.disable());
//basic 인증 방식 disable
http
.httpBasic((auth) -> auth.disable());
http
.authorizeHttpRequests((auth) -> auth
.requestMatchers(new AntPathRequestMatcher("/**")).permitAll()
.requestMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated());
http
.sessionManagement((session) -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
BCryptPasswordEncoder
스프링 시큐리티에서 제공하는 비밀번호 암호화 클래스이며, 이에 비밀빈호를 안전하게 해시하고, 사용자 인증을 보다 안전하게 수행시킨다.
.csrf().disable()
스프링 시큐리티에서 CSRF(Cross-Site Request Forgery) 보호 기능을 비활성화하는 설정이다. CSRF는 악의적인 웹사이트가 사용자로 하여금 의도하지 않은 요청을 보내도록 유도하는 공격 방식인데, 이를 사용하면 CSRF 보호 기능이 비활성화 된다.
보통은 CSRF 공격의 위험이 없는 상황에서만 사용하는 것이 좋다. 때에 따라
(이를 적용하지 않을 경우 403 forriden 오류가 발생할 수 있음)
http // http 권한 요청에 대한 설정을 허용 .authorizeHttpRequests((auth) -> auth //모든 요청에 대해 접근을 허용, 때에 따라 필요한 엔드포인트만 설정하여 접근 권한을 설정할 수 있음 .requestMatchers(new AntPathRequestMatcher("/**")).permitAll() // 관리자 페이지에 대해 ADMIN 권한을 가진 사람만 접근이 가능 .requestMatchers("/admin").hasRole("ADMIN") .anyRequest().authenticated());
return http.build();
스프링 시큐리티에서 HttpSecurity 객체를 구성한 후, 해당 설정을 기반으로 최종적으로 SecurityFilterChain 객체를 생성하는 메서드
package com.example.TodoList.entity.user;
import jakarta.persistence.*;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "User")
public class User {
// 아이디 = primary key
@Id
@Column(name = "userId", nullable = false)
private String userId;
// 비밀번호
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String nickname;
private String intro;
private String role;
}
@PostMapping("/signup")
public ResponseEntity<String> registerUser(@RequestBody SignUpDto signUpDto) {
try {
userService.registerUser(signUpDto);
return new ResponseEntity<>("회원가입에 성공하였습니다.", HttpStatus.CREATED);
} catch (IllegalArgumentException e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
}
}
package com.example.TodoList.service;
import com.example.TodoList.dto.SignUpDto;
import com.example.TodoList.entity.user.User;
import com.example.TodoList.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public UserService(UserRepository userRepository, BCryptPasswordEncoder bCryptPasswordEncoder){
this.userRepository = userRepository;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
//회원가입
public void registerUser(SignUpDto signUpDto) {
String userId = signUpDto.getUserId();
String password = signUpDto.getPassword();
String nickname = signUpDto.getNickname();
String intro = signUpDto.getIntro();
Boolean isExist = userRepository.existsByUserId(userId);
if (isExist) {
return;
}
if (userRepository.findByUserId(signUpDto.getUserId()) != null) {
throw new IllegalArgumentException("아이디가 이미 존재합니다");
}
if (userRepository.findByNickname(signUpDto.getNickname()) != null) {
throw new IllegalArgumentException("닉네임이 이미 존재합니다.");
}
User user = new User();
user.setUserId(userId);
user.setPassword(bCryptPasswordEncoder.encode(password)); //비밀번호 인코딩
user.setNickname(nickname);
user.setIntro(intro);
user.setRole("ROLE_ADMIN"); // 어드민 권한 설정
userRepository.save(user);
}
}
기존 userService에서 시큐리티를 적용하기 전에는
User user = signUpDto.toEntity(); User saved = userRepository.save(user); return saved;
다음과 같이 코드를 적용시키면서 코드를 구성했지만 시큐리티를 적용하면서 User 객체를 수동으로 생성 후 패스워드 해시화를 시키면서 명시적으로 설정시켰다. 기존의 코드가 더 객체 지향적이며 유지보수에 편리하지만 시큐리티를 적용함으로써, 객체를 생성시키면서 명시적으로 속성을 선언해줌으로써 구조를 변경시켰다.
///