시큐어 코딩
03 시간 및 상태
- 검사 시점과 사용 시점 (TOCTOU)
- 자원을 사용하는 시점과 검사하는 시점이 다르기 때문에 존재하던 자원이 사라지는 등 자원의 상태가 변화는 경우가 발생
- 공유자원에 여러 프로세스가 접근하여 사용할 경우 동기화 구문을 사용하여 한번에 하나의 프로세스만 접근하도록 하고, 성능에 미치는 영향을 최소화하기 위해 임계코드 주변만 동기화 구문 사용
- 예시
-
public void run() { // 멀티쓰레드 환경에서 synchronized를 사용하여 동시에 접근할 수 없도록 사용해야한다. synchronized(SYNC) { try { if (manageType.equals("READ")) { File f = new File("Test_367.txt"); ...코드생략... public static void main (String[] args) { FileMgmtThread fileAccessThread = new FileMgmtThread("READ"); FileMgmtThread fileDeleteThread = new FileMgmtThread("DELETE"); fileAccessThread.start(); fileDeleteThread.start(); } }
-
- 종료되지 않는 반복문 또는 재귀함수
- 재귀의 순환횟수를 제어하지 못하여 자원을 과다하게 사용하면 위험하다. 귀납 조건(Base Case)이 없는 재귀 함수는 무한루프에 빠진다.
- 모든 재귀 호출 시 재귀 호출 횟수를 제한하거나 초기값을 설정(상수)하여 재귀 호출을 제한해야 한다.
04 에러처리
- 오류 메시지 정보노출
- 사용자 데이터나 시스템 내부 데이터 같은 민감한 정보를 포함하는 오류 메시지를 생성하여 외부에 제공하는 경우 위험하다.
- 오류 메시지는 정해진 사용자에게 최소한의 정보만 포함하도록 한다.
- 예외 상황은 내부적으로 처리하고 민감한 데이터를 포함하는 오류를 출력하지 않도록 미리 정의된 메시지를 제공하도록 설정한다.
- 예시
-
try { rd = new BufferedReader(new FileReader(new File(filename))); } catch(IOException e) { // 에러 코드와 정보를 별도로 정의하고 최소 정보만 로깅 logger.error("ERROR-01: 파일 열기 에러"); }
-
- 오류 사항 대응 부재
- 오류가 발생할 수 있는 부분에 예외처리를 하지 않을 경우 위험할 수 있다.
- 오류가 발생할 수 있는 부분에 제어문을 사용하여 적절하게 예외처리(try-catch)를 한다.
- 부적절한 예외 처리
- 함수 결과값에 대한 적절한 처리 또는 예외 상황에 대한 조건을 검사하지 않을 경우 문제가 생길 수 있다.
- 값을 반환하는 모든 함수의 결과 값을 검사하여 의도한 값인지 확인하고 예외처리 사용시 구체적인 예외처리 수행
Spring Boot
Spring Security
- build.gradle- dependencies 추가
- implementation 'org.springframework.boot:spring-boot-starter-security'
- implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
- testImplementation 'org.springframework.security:spring-security-test'
- localhost:8080 으로 접속 후 연결 확인
- 별다른 코드 입력 없이 로그인 화면이 출력된다.
- application.properties 추가하여 기본 아이디/비밀번호 설정
- spring.security.user.name=설정한기본아이디
- spring.security.user.password=설정할기본비밀번호
- 이후 위에서 설정한 기본 아이디/비밀번호로 로그인 가능해진다.
- SecurityConfig.java 생성
- 클래스에 어노테이션 주입
- @Configuration
- @EnableWebSecurity
- 웹 보안 활성화를 위한 어노테이션
- 메소드 생성
- filterChain 메소드 (throws Exception)
- 메소드에 어노테이션 주입
- @Bean
- SecurityFilterChain 인터페이스를 return하는 메소드
- 매개변수로 HttpSecurity를 받는다.
- 메소드 안에 기능별 코드 작성
- csrf 보안을 쿠키 방식으로 지정
- .csrf((auth)->auth.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
- 모든 경로의 요청 허가
- .authorizeHttpRequests( (auth) → auth
- .requestMatchers(new AntPathRequestMatcher("/"))**
- .permitAll()
- .authorizeHttpRequests( (auth) → auth
- 로그인 인증
- .formLogin((formLogin) -> formLogin
- .loginPage(”로그인폼 URI”)
- .loginProcessingUrl(”로그인액션 URI”)
- .defaultSuccessUrl("로그인성공시넘어갈 URI")
- .successHandler((((request, response, authentication) -> { response.sendRedirect("인증 성공 후 별도 커스텀 처리 URI"); })))
- .failureUrl("실패시 에러페이지 URI")
- .permitAll()
- .formLogin((formLogin) -> formLogin
- 로그아웃 처리
- .logout((auth) -> auth
- .logoutRequestMatcher(new AntPathRequestMatcher("로그아웃 액션 URI"))
- .logoutSuccessUrl("로그아웃 성공시 넘어 URI")
- .invalidateHttpSession(true)
- 세션 객체를 해제 (내부 저장 데이터도 소멸)
- .deleteCookies("JSESSIONID")
- response 헤더에 Set Cookie에 ""을 넣어준다
- .logout((auth) -> auth
- csrf 보안을 쿠키 방식으로 지정
- 예시
-
@Configuration @EnableWebSecurity // 웹보안 활성화를 위한 Annotation public class SecurityConfig { @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 기본적으로 csrf(사이트간 요청 위조)가 활성화되어 있다. // csrf 보안을 비활성화 한다. // .csrf((auth) -> auth.disable()) //csrf 보안을 쿠키방식으로 지정한다. //CsrfTokenRepository 인터페이스는 HttpSessionCsrfTokenRepository,CookieCsrfTokenRepository //2개가 있다. //기본적으로 스프링 시큐리티는 HttpSessionCsrfTokenRepository로 CSRF 토큰을 HttpSession에 저장한다. //하지만 커스텀 CsrfTokenRepository를 설정하고 싶을 때도 있을 것이다. //예를 들어 자바스크립트 기반 어플리케이션을 지원하려면 쿠키에 CsrfToken을 저장해야 한다. .csrf((auth)->auth.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())) // HTTP 요청에 대한 보안설정을 시작 .authorizeHttpRequests( (auth) -> auth // 루트 밑의 모든 경로에 대한 모든 요청을 허가 .requestMatchers( new AntPathRequestMatcher("/"), new AntPathRequestMatcher("/joinForm"), new AntPathRequestMatcher(("/joinAction")) ).permitAll() .requestMatchers("/admin").hasRole("ADMIN") .requestMatchers("/my/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated()) // 그 외 어떤 요청에도 인증한다. // 로그인 인증에 대한 설정을 시작 .formLogin((formLogin) -> formLogin // 로그인 페이지를 /loginForm URI로 지정 .loginPage("/loginForm") // 로그인 액션 URI .loginProcessingUrl("/loginAction") // 로그인 성공시 넘어갈 URI 지정 .defaultSuccessUrl("/") // 인증 성공 후 별도의 처리가 필요한 경우 커스텀 핸들러를 생성 및 등록한다. .successHandler((((request, response, authentication) -> { System.out.println("로그인 성공"); response.sendRedirect("/"); }))) // 실패시 에러 페이지 .failureUrl("/loginForm?error") // 로그인 페이지를 모든 사용자에게 허용 .permitAll()) // 로그아웃 처리 .logout((auth) -> auth .logoutRequestMatcher(new AntPathRequestMatcher("/logoutAction")) .logoutSuccessUrl("/") .invalidateHttpSession(true) // 세션 객체를 해제 (내부 저장 데이터도 소멸) .deleteCookies("JSESSIONID") // response 헤더에 Set Cookie에 ""을 넣어준다. ) ; return http.build(); } // BCrypt 암호화 엔코더 빈 생성 @Bean public PasswordEncoder passwordEncoder() { //Spring Security 5.3.3에서 공식 지원하는 PasswordEncoder 구현 클래스들은 아래와 같습니다. //BcryptPasswordEncoder : BCrypt 해시 함수를 사용해 비밀번호를 암호화 //Argon2PasswordEncoder : Argon2 해시 함수를 사용해 비밀번호를 암호화 //Pbkdf2PasswordEncoder : PBKDF2 해시 함수를 사용해 비밀번호를 암호화 //SCryptPasswordEncoder : SCrypt 해시 함수를 사용해 비밀번호를 암호화 // 강도는 4 ~ 31까지 설정할 수 있으며 기본강도는 10이다. return new BCryptPasswordEncoder(); //return SCryptPasswordEncoder.defaultsForSpringSecurity_v5_8(); //return Argon2PasswordEncoder.defaultsForSpringSecurity_v5_8(); //return Pbkdf2PasswordEncoder.defaultsForSpringSecurity_v5_8(); } }
-
- 메소드에 어노테이션 주입
- filterChain 메소드 (throws Exception)
- 클래스에 어노테이션 주입
- UserRole.java 생성 (enum)
- 클래스 어노테이션 주입
- @Getter
- ADMIN, USER 등 권한 필드 및 권한 코드(value) 생성
- 예시) USER(”ROLE_USER”)
- 스프링 시큐리티의 권한 코드는 항상 “ROLE_”로 시작해야 한다.
- String value 매개변수로 받는 생성자 생성
- 관련 함수
- hasRole(String)
- 사용자가 주어진 역할이 있다면 접근을 허용
- 예시) hasRole("USER")
- hasAuthority(String)
- 사용자가 주어진 권한이 있다면 접근을 허용
- 예시) hasAuthority("ROLE_USER")
- hasRole(String)
- 예시
-
@Getter public enum UserRole { USER("ROLE_USER"), ADMIN("ROLE_ADMIN"); private final String value; UserRole(String value) { this.value = value; } }
-
- 클래스 어노테이션 주입
- SecurityService.java 생성
- 클래스 어노테이션 주입
- @Service
- implements UserDetailsService
- loadUserByUsername 메소드 오버라이드
- 사용자 아이디를 통해 사용자 정보와 권한을 스프링 시큐리티에 전달해준다.
- 예시
-
@Service @RequiredArgsConstructor public class SecurityService implements UserDetailsService { private final MemberRepository memberRepository; // 사용자 아이디를 통해 사용자 정보와 권한을 스프링 시큐리티에 전달해준다. @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { Optional<MemberEntity> optional = this.memberRepository.findByUserId(username); if(optional.isEmpty()) { throw new UsernameNotFoundException("사용자를 찾을 수 없습니다."); } MemberEntity memberEntity = optional.get(); List<GrantedAuthority> authorities = new ArrayList<>(); if("admin".equals(username)) { authorities.add(new SimpleGrantedAuthority(UserRole.ADMIN.getValue())); } else { authorities.add(new SimpleGrantedAuthority(UserRole.USER.getValue())); } return new User(memberEntity.getUsername(), memberEntity.getPassword(), authorities); } }
-
- 클래스 어노테이션 주입
728x90
'교육 (Today I Learned) > Hanaro' 카테고리의 다른 글
[Hanaro] 79일차 ~ / 해외여행 모임 통장 서비스 프로젝트 시작 (2023-05-13 ~ 2023-06-12) (2) | 2024.05.17 |
---|---|
[Hanaro] 71일차 ~ 78일차 / 키오스크 프로젝트 완료 (회고) (2) | 2024.05.11 |
[Hanaro] 68일차 / 시큐어 코딩 (보안 기능), Spring Boot(Security) (0) | 2024.04.25 |
[Hanaro] 67일차 / 시큐어 코딩 (입력데이터 검증), Spring Boot (네비게이션 바, 페이징) (0) | 2024.04.23 |
[Hanaro] 66일차 / 백엔드 실습 과제 (0) | 2024.04.21 |