반응형

 


요청 시

 

토큰은 여러 종류가 있고, 헤더에 토큰 종류를 명시한다.

Authorization : Basic xxxx    - id, pass를 base64 인코딩

Authorization : Bearer xxxxx    - 보통 JWT를 사용할 경우

 

 

 

 

 


보안 설정

 

 

 

 - 서버 설정

Access-Control-Allow-Credentials : true

로 설정하게 되면,

Access-Control-Allow-Origin

를 "*" 로 할 수 없다.

 

모든 Origin의 Credentials를 허가하는 것은 위험하기 때문

Pattern, whiteList을 이용하거나, 클라이언트 Origin을 명시해서 적어야한다.

 

 

 - 브라우저 클라이언트 설정

withCredential : true

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
반응형

 

 

서버에서 쿠키를 만든 후, Vue에서 응답 쿠키를 확인하려고 했지만

Vue를 사용하는 브라우저에 쿠키가 들어오지 않았다.

 

 

 

이유는 서버에서 쿠키는 HttpOnlytrue로 브라우저에게 전송한다.

HttpOnly로 설정된 쿠키는 브라우저에서 실행하는 JS로 "직접 접근"할 수가 없다.

 

 

XSS공격으로 자바스크립트를 이용한 쿠키 탈취를 할 수 있기 때문에 막아둔 것.

 - 브라우저의 저장된 쿠키를 사용자가 모르게 실행시켜서, 해커 자신의 서버로 전송할 수 없도록 한다.

 

 

쿠키에는 Secure 설정도 해줄 수 있는데,

Secure true 쿠키는 https를 이용해야만 서버로 전달해줄 수 있다.

응답으로 브라우저에 저장은 가능하다.

 

 


 

해결

 

 

{ withCredentials: true } 로 설정하면 브라우저에 쿠키가 전달된다.

서버에서도 Credentials 설정을 true로 해줘야 한다.

브라우저가 쿠키 저장, 전송 처리를 허가해준다.

 

 

withCredentials

Same Origin일 경우 Request, Response 에 쿠키가 전달되지만,

Cross Origin일 경우 쿠키가 전달되지 않는다. Cross Origin의 기본은 쿠키 전송이 false이다.

true 값을 주어 전달되도록 설정해주어야한다.

인증과 인가에 사용되는 쿠키와 토큰 등을 공유하겠다는 의미

 

추가적으로, 헤더 authorization 요청을 보낼때도 허가해줘야 한다.

 

 

 

 

처리 이후 브라우저에 쿠키가 브라우저에 저장된 것을 볼 수 있었다.

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

반응형

'프로그래밍 > Security' 카테고리의 다른 글

웹 요청 정리  (0) 2022.10.20
[Web 보안] XSS, CSRF  (0) 2022.08.25
[Spring Security] 로그인 구현  (0) 2022.08.24
[인증과 인가] 쿠키와 세션, JWT  (0) 2022.06.23
반응형

 

 

XSS(Cross-Site Scripting)

(사용자가 사이트를 신뢰한다는 점을 이용)

스크립트 삽입 공격 방식

 

JS 스크립트는 어느위치에 있던 동작하기 때문에 공격자가 스크립트 코드가 포함된 게시글을 작성하여 등록

일반 사용자가 해당 글을 클릭하면 script가 동작하여 이루어지는 공격 방식

 

무조건 Secure Coding을 해줘야 한다. 

 

 

대응

 - Secure coding

<, >, = 과 같은 입력값에 공격에 사용될만한 문자가 있다면 치환하여 사용, DB에 저장한다.

Sql Injection도 비슷한 방식으로 대응할 수 있다.

 

 - HttpOnly

Secure coding이 적용되어 있지 않아 스크립트가 동작

쿠키 탈취 시도 시 쿠키가 HttpOnly 설정이 되어있다면 js로 쿠키를 읽어낼 수 없다.

 

 


 

CSRF (Cross Site Request Forgery)

(사이트가 사용자를 신뢰한다는 점을 이용, 신뢰할만한 쿠키와 토큰을 브라우저에서 갖고 있다는 점)

피싱 사이트를 만들어, 피싱 사이트에 사용자가 접근하면

스크립트를 이용해 특정 사이트로 요청을 보내, 사용자나 관리자의 권한으로 서버에 write 하도록 유도하는 공격

피싱 사이트에서 요청을 위조해서 Request Forgery

 

기본적으로 브라우저의 SOP 정책으로 응답된 데이터에 접근이 불가능하다. (공격자가 해당 정보를 이용할 수 없다)

하지만, 응답된 데이터 접근 불가능할뿐이지 서버에 write는 가능하다. (공격자가 원하는 대로 정보를 수정)

그래서 write를 방지하고자 Preflight 또는 CSRF token을 도입하게 되었다.

 

 

대응

 - Preflight

(1) 예비 요청을 Option 메서드로 보내서, 서버의 Access-Control-Allow-Origin 을 확인한다.

서버에서 허가된 Access-Control-Allow-Origin 목록을 응답으로 보내준다.

 

(2-1) 허가된 주소에 해당되면 정식 요청을 보내고, 응답을 사용한다.

(2-2) 허가된 Origin이 아니라면 예비 요청에서 막혔음을 확인했기 때문에 정식 요청을 보내지 않는다.

get, head, post 가 아닌 서버 데이터를 변형시키는 put, patch, delete면 Prefilght를 보내게 된다.

 

 - CSRF Token

CSRF토큰 난수를 두면 요청시에 모든 요청은 CSRF토큰 난수를 보내야 인가를 받는다.

서버에서 CSRF Token 난수가 맞는지 확인한다. 서버에서 만든 페이지에서 요청한 것이 맞으므로 수행

위조된 요청은 CSRF Token가 없으므로 write가 동작하지 않는다.

 

 

 


브라우저의 보안 SOP

SOP(Same Origin Policy)

브라우저가 적용한 보안 방식

요청한 Origin과 응답한 Origin이 Same Origin이어야 자원 접근이 가능하다.

script뿐만 아니라, document도 출신(Origin)이 다르면 서로 접근이 불가능하다.

 

 

XSS는 Secure Coding으로 막았다고 가정, CSRF 공격 시

해킹1. (document로 정보 가져오기)

요청한 위조 사이트와 정보를 가져온 사이트의 Origin이 다르기 때문에,

유저 인증을 이용해 document를 가져오는 건 성공해도 document를 읽어낼 수 없다.

결과적으로 해커의 서버에 전송된 document에는 아무것도 없다

 

해킹2. (script로 정보 가져오기)

요청하는 해커의 서버와 응답 사이트의 Origin이 다르기 때문에 요청이 무시되어 동작하지 않는다.

 

 

 

CORS(Cross Origin Resource Sharing)

SOP 보안 정책을 일부 허가된 Origin은 해제

script, document로 다른 Origin 서버에 요청해서 받은 자원을 접근할 수 있게 한다.

 

서버 Access-Control-Allow-Origin 에 요청하는 Origin을 추가한다.

 

 

 


 

HTTPS 

네트워크상에서 정보가 탈취되지 않도록 방지한다.

RSA 암호화를 사용하는 것. 서버가 비밀키를 갖고 있다.

공개키로 암호화해서 데이터를 보내고 전달받은 데이터를 서버가 자신의 비밀키로 푼다.

RSA 암호화는 컴퓨터에게 부담을 주기 때문에 이후부터는 대칭키를 교환하여 사용한다.

 

RSA

두 소수를 곱하여 큰 수를 만들어낸다. (두 소수도 자체도 크다.)

두 소수로 큰 수를 구하는 것은 쉽지만

반대로, 큰 수에서 두 소수를 구하는 것은 어렵다는 특징

반응형
반응형

 


 

 

 

전체적인 인증 흐름

Filter(username, password 토큰 생성) ->  Manager(Provider 구분) ->  Provider(실제 인증 처리부) ->

 

Service(username으로 DB에서 조회) ->  Details(User정보 구성) ->

 

Provider(입력 token과 Details 비교 검증, 일치할 시 Authentication 리턴) -> 

 

Filter(SpringContext에 Authentication 등록)

 

 

간단 요약

(입력된 username, password 토큰)을

(username으로 DB에서 조회한 유저정보)랑 비교 검증

일치하면 (Authentication 권한과 정보)를 SecurityContext에 등록

스택을 쌓아가며 인증처리를 한다.

 


 

AuthenticationFilter

인증의 처음과 마지막을 제어

 

처음

form 값으로 UsernamePassworAuthenticationToken을 만든다.

 

마지막

Security Session(Context)에 인터페이스 Authentication를 넣는다.

구현체 UsernamePassworAuthenticationToken는두개의 생성자를 갖고 있다. (인증 전, 인증 후)

 

 


 

전체 로직 처리는 AuthenticationManager에서 한다.

 

Provider 종류에 알맞게 인증 처리하는 곳 (Factory)

실제 인증은 AuthenticationProvider에서 처리

for (AuthenticationProvider provider : getProviders()) {
   if (!provider.supports(toTest)) {
      continue;
   }
   if (logger.isTraceEnabled()) {
      logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
            provider.getClass().getSimpleName(), ++currentPosition, size));
   }
   try {
      result = provider.authenticate(authentication);
      if (result != null) {
         copyDetails(authentication, result);
         break;
      }
   }

등록된 provider를 반복문으로 돌려 해당되는 provider를 찾는다.

Token(Username, password)DB정보(UserDetail)와 검증해서 authenticate를 통과하나 확인한다.

토큰과 userDetail이 일치하는 인증된 사용자이므로 break로 반복문 종료.

 

 

if (result != null) {
   if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {
      // Authentication is complete. Remove credentials and other secret data
      // from authentication
      ((CredentialsContainer) result).eraseCredentials();
   }
   // If the parent AuthenticationManager was attempted and successful then it
   // will publish an AuthenticationSuccessEvent
   // This check prevents a duplicate AuthenticationSuccessEvent if the parent
   // AuthenticationManager already published it
   if (parentResult == null) {
      this.eventPublisher.publishAuthenticationSuccess(result);
   }

   return result;
}

인증되면 Authenticaton 객체를 AuthenticationFilter에게 return

 


 

실질적인 인증 처리는 AuthenticationProvider에서 한다.

 

인증 전 Authentication 객체를 받아서 인증 후 객체로 만드는 곳

Provider는 여러개일 수 있다.

해당되는 Provider로 인증을 진행한다.

 

    UserDetailService (Login 요청이 오면 loadUserByUserName 메서드 동작), 인터페이스이므로 구현

    Username으로만 Repository를 이용해 DB에 조회한다.

    유저가 있다면 UserDetails로 반환한다.

@Service
@RequiredArgsConstructor
public class PrincipalDetailsService implements UserDetailsService {
   final UserRepository userRepository;

   @Override
   public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      Optional<User> optionalUser = userRepository.findByUsername(username);
      if(optionalUser.isPresent()){
         return new PrincipalDetail(optionalUser.get()); 
      }else{
         return null;
      }
   }
}

 

 

 

    UserDetails, 인터페이스이므로 구현해서 유저의 정보를 제공 (UserDetailService에서 생성된다.)

    사용자의 정보 제공 목적으로 사용된다. (DB데이터, 로그인된 유저 Session)

    유저명, 비밀번호, 유저 권한 등을 갖고 있다.

    생성자에 User를 넣어준다.

public class PrincipalDetail implements UserDetails {
   private User user;

   public PrincipalDetail(User user){
      this.user = user;
   }

   @Override
   public Collection<? extends GrantedAuthority> getAuthorities() {
      Collection<GrantedAuthority> collect = new ArrayList<>();
      collect.add(new GrantedAuthority() {
         @Override
         public String getAuthority() {
            return user.getRole().name();
         }
      });

      return collect;
   }

   @Override
   public String getPassword() {
      return user.getPassword();
   }

   @Override
   public String getUsername() {
      return user.getUsername();
   }

   @Override
   public boolean isAccountNonExpired() {
      return true;
   }

   @Override
   public boolean isAccountNonLocked() {
      return true;
   }

   @Override
   public boolean isCredentialsNonExpired() {
      return true;
   }

   @Override
   public boolean isEnabled() {
      return true;
   }
}

 

 

 

UserDetails 객체는 UsernamePasswordAuthenticationToken(Authentication)을 생성, 보관한다.

 

AuthenticationProvider에서

전달받은 UserDetails와 UsernamePassworAuthenticationToken(Authentication) 을 검증하여 인증처리

암호가 같다면 인증처리된 Authentication을 AuthenticationManager에게 반환

(이전에 password를 해시암호화 해야한다.)

 

AuthenticationManager에서 AuthenticationFilter에게 반환

Authentication를 SecurityContext에 저장

 

 


 

 

@Configuration
@EnableWebSecurity
public class SecurityConfig {

   @Bean
   public BCryptPasswordEncoder passwordEncoder(){
      return new BCryptPasswordEncoder();
   }

   @Bean // 필터를 bean component 생성
   public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {  // 원하는 필터를 골라서 커스텀 적용시킬 수 있다.
      return http
              // 권한 설정
              .csrf().disable() // 해당 설정 안하면 csrf 토큰이 없어서, post 는 forbidden 이 나오므로 테스트에선 해제
              .authorizeRequests()
              .antMatchers("/user/**").hasRole("USER") // ROLE 이 필요한 URL 을 먼저 적용 시킨다.
              .antMatchers("/manager/**").hasRole("MANAGER")
              .antMatchers("/login").not().hasAnyRole("USER", "MANAGER") // 로그인 이후 접근 금지
              .antMatchers("/**").permitAll()

              // 로그인
              .and()
              .formLogin() // 해당 url 은 form 으로 이동
              .loginPage("/login")
              .loginProcessingUrl("/login") // 시큐리티가 로그인 대신 진행
              .defaultSuccessUrl("/")
              .failureUrl("/login")

              // 로그아웃
              .and()
              .logout()
              .logoutSuccessUrl("/")
              .deleteCookies("JSESSIONID")

              .and().build();

   }
}

 

 

 


 

https://mangkyu.tistory.com/76

반응형

'프로그래밍 > Security' 카테고리의 다른 글

웹 요청 정리  (0) 2022.10.20
[쿠키] Javascript로 쿠키에 직접 접근, HttpOnly, CORS  (0) 2022.10.19
[Web 보안] XSS, CSRF  (0) 2022.08.25
[인증과 인가] 쿠키와 세션, JWT  (0) 2022.06.23
반응형

 

쿠키와 세션 - 서버에서 정보 자체를 갖고있다. 쿠키의 세션ID 매칭을 확인

JWT - 서버에서 정보의 위조만 검사한다.

 

 


쿠키와 세션

 

로그인 유지를 위해선 세션과 쿠키를 활용해야 한다.

하지만 HTTP는 기본적으로 stateless하다.

 

Stateful

채팅과 같이 지속적으로 연결되어있는 상태를 stateful (지속적 연결)이라고 하고,

HTTP에서 이러한 stateful과 같은 효과를 주는 것이 쿠키와 세션

 


 

기존에는 쿠키만 사용했는데, 로그아웃을 안 하면 브라우저가 종료돼도 남아있기 때문에 문제가 되었다.

세션을 도입하여 브라우저가 종료되면 자동 로그아웃되게 했다. (기간 지정 가능)

 

서버에 중요 데이터를 보관하는 것이 보안적으로도 우수

 


 

{세션 ID : Session}으로 서버에 저장하고, 쿠키에 세션 ID를 담아서 브라우저에게 응답

브라우저는 다음부터 쿠키와 함께 서버에 요청.

서버는 쿠키를 열어보고 세션 ID를 확인, 매칭 되는 Session을 사용한다.

 

 

톰캣의 SESSION 구조

{ 세션Id : {key : value} }

한번 더 감싸 져 있기 때문에 key:value는 여러 개가 가능하다. 여러 값을 저장할 수 있다.

 

 

JSESSION은 쿠키 이름 (세션 ID를 갖고 있다.)

 

세션id KEY VALUE
BK291823LAK USERNAME CHA
92J1K20D892 USERNAME PARK
FKDJ209DJF3 USERNAME SOO
F92JD93H1KD USERNAME MYOUNG

서버상에서는 세션 ID로 이미 분리된 상태이기 때문에, KEY가 똑같아도 전부 구분된다.

 

 


 

JWT

 

 Http 헤더에 JSON 토큰을 넣은 후 서버는 헤더에 포함되어 있는 JWT 정보를 통해 인증.

서버는 유저 정보를 갖고 있지 않고, 비밀키만 갖고 있기 때문에 부담이 적다. (stateless)

 

 

세 부분으로 구성

(1) Header

토큰 유형: JWT

암호방식: (HS256 또는 RSA) 

 

(2) Payload   (Map 형태)

유저 정보,

만료 시간

 

(3) Signature

해시알고리즘(

Header +

Payload +

Secret key

) = 시그니처 값

 

 

주의점

기본적으로 토큰은 공개되어지면 안된다. (HTTPS)

토큰으로 악용할 가능성이 있다.

 

JWT의 중점은 위변조 관점, 탈취될 가능성이 있으므로 시간 제한이 있는 Access Token 사용

(1)Header, (2)Payload 는 암호화가 아니다. (Base 64 encoder)

인가에 사용되는 부분은 (3)Signature

 

HTTPS덕에 정보를 보긴 어렵다.

그래도 중요한 정보는 저장하지 말자. (비밀번호가 있다면 토큰이 만료되도 접근할 가능성 존재)

 

 

 

인증 방식

Header + Payload + SecretKey가 (3) Signature이 나오나 확인한다.

 

인증 흐름

(1) + (2) + Secret key 이용해 해시 암호화해서 Signature를 만들고, 유저에게 전달한다.

 

토큰이 다시 왔을 때는 (1) + (2)와 서버의 Secret Key로 Signature를 만들어 본다.

유저가 보내온 Signature 값과 같은지 확인한다. 

일치한다면 서버에서 만들어낸 토큰이고, 위조가 없는 것이니 사용한다.

 

 

 - 유저는 Secret Key를 모르기 때문에 Payload를 위조해도 서버와 일치하는 Signature를 만들 수 없다.

 - JWT의 인가 과정은 서버에서 만든 토큰이 맞는지, 데이터가 위조되지 않았는지를 확인하는 것이다.

 

 


 

RSA를 사용하면,

Header + Payload를 개인 키로 암호화하여 Signature를 생성, 토큰을 유저에게 준다.

유저가 다시 서버에게 토큰을 주면, 서버는 공개 키로 Signature를 복호화를 한다.

 

복호화된 Signature(Header + Payload)를 전달받은 Header, Payload와 일치하는지 확인한다.

두 값이 일치하면 서버의 개인 키로 서명한 위조가 안된 토큰이 맞다.

 

 - 유저는 개인 키를 모르므로 Header, Payload가 일치하는 암호화 Signature를 만들 수 없다.

 


 

Refresh Token, Access Token

로그인 시 JWT 토큰을 두개를 생성해서 사용자에게 주는 방식EXP 기간이 다르기 때문에 해시 값은 완전히 다르다.

 

기존에는 Access Token만 사용

 - 토큰은 유효 시간이 짧으면 재 로그인으로 불편하고, 유효 시간이 길면 탈취의 위험이 있다.

 - 토큰 요청시마다 새로 발급하면, 서버에 부담을 준다.

 

Refresh Token을 추가로 사용

 - Refresh Token은 유효기간이 길다. Access Token 만료 시 새로운 Access Token의 발급으로 사용된다.

 - 클라이언트는 브라우저 스토리지에 저장한다.

 

 

서버에서는 Access Token이 만료된 것이 확인되면, 

Refresh Token을 달라고 응답을 내려준다.

 

클라이언트는 Access Token이 만료되면 Refresh Token도 같이 줘야 한다.

두개의 Token을 전부 갖고 있음을 증명해야하기 때문.

Refresh Token이 만료되지 않았다면, Access Token을 재발급해준다.

둘다 만료라면 재로그인 필요.

 

 

 

 

 

 


Spring Security

 

JWT 

권한 관리가 필요없다면 SecurityContext에 Authentication 객체를 안넣어도 된다.

Security Context에 Authentication을 넣는 이유는, 스프링 시큐리티의 권한 관리 사용을 위해

 

인증 시 DB 접근 -> 토큰 생성 -> 토큰 반환

인가 시 토큰 검증 -> 토큰 Signature 정상 -> Security Context에 Authentication 객체 담는다.

 

 

세션

세션 안에 있는 Security Context에서 꺼내와서 SecurityContextHolder에 담는다.

 

 

 

https://www.inflearn.com/questions/558844

 

jwt 토큰방식에서의 세션 미사용 질문 - 인프런 | 질문 & 답변

강의에서 jwt방식을 예로 드셨던 세션정책Stateless에서 세션을 사용하지 않는다고 말씀하셨습니다. 궁금한 점은  jwt토큰방식을 사용하더라도 세션은 사용하는 것이 아닐까라는 의문입니다.  이

www.inflearn.com

 

반응형

'프로그래밍 > Security' 카테고리의 다른 글

웹 요청 정리  (0) 2022.10.20
[쿠키] Javascript로 쿠키에 직접 접근, HttpOnly, CORS  (0) 2022.10.19
[Web 보안] XSS, CSRF  (0) 2022.08.25
[Spring Security] 로그인 구현  (0) 2022.08.24

+ Recent posts