반응형

앞서, 보안 용어 정리
- 인증: 사용자가 누구인지 확인한다. - 로그인
- 인가: 인증된 사용자가 리소스에 접근할 권한이 있는지 확인한다.
- 토큰: 인증 시 생성되어 인가에 쓰인다.
- 접근 주체: 어플리케이션을 사용하는 주체 - 사용자, 시스템, 디바이스
스프링 시큐리티
https://docs.spring.io/spring-security/reference/
: 인증, 인가 등의 보안 기능을 제공하는 스프링 하위 프레임워크로 편리하게 보안 기능을 사용할 수 있다.
동작 구조 (Authentication)
- 요청이 들어오면 Dispatcher Servlet을 거치기 전 Filter Chain에서 각 Filter를 거친다. (doFilter로 이동)
- Filter를 거치다 DelegationgFilterProxy가 실행되면 보안 필터 체인에서 보안 필터들을 실행한다.
- SecurityFilterChain(HttpSecurity) 빈을 통해 설정할 수 있다.
- 인증 Filter가 request에서 username, password를 추출해서 토큰을 생성 -> (로그인)
- 토큰값을 이용해 일치하는 UserDetails 객체를 조회하여 인증 Provider에서 인증을 수행한다. -> (인증)
- 인증 성공 시 인증 Filter로 토큰을 전달한다. (UsernamePasswordAuthenticationToken)
- 토큰을 SecurityContextHolder의 SecurityContext의 Authentication에 저장한다.
JWT
: JSON 정보를 암호화하여 토큰으로 전송한다.
- Base64Url로 인코딩된 문자열로만 구성되어 http 어느 요소든 들어갈 수 있다.
- 디지털 서명이 적용되어 있다.
- 주로 인가 목적으로 쓰인다.
- .으로 구분되어 [헤더.내용.서명]으로 구성된다.
헤더
: 검증과 관련된 내용
- alg : 토큰 검증 시 사용하는 해싱 알고리즘 (SHA256, RSA)
- typ : 토큰의 타입 (JWT)
내용
: 토큰에 담을 내용으로 각 속성은 클레임(Claim)
- Registered Claims : 이미 이름이 정해져 있는 클레임으로 토큰에 대한 기본적인 정보
- iss(uer), sub(ject), aud(ience), exp(iration), nbf(not before), iat(issued at), jti(jwt id)
- Public Claims : 키 값을 마음대로 정의하여 속성을 지정
- Private Claims : Registered, Public이 아닌 클레임
서명
: 인코딩 한 헤더, 내용과 비밀키, 알고리즘 속성값을 가져와 생성하며 무결성을 확인한다.
인증 구현 - UsernamePasswordAuthentication
1. 인증에 사용할 username, password, 권한(string) list가 있는 UserDetails를 구현한 엔티티 클래스 생성
- getAuthorities() : 부여된 역할(권한)을 List 형태로 리턴
- getUsername(), getPassword() : 인증에 쓰이는 계정의 아이디, 비밀번호를 리턴
- isAccountNonExpired() : 계정이 만료되었는지 여부를 리턴
- isAccountNonLocked() : 계정이 잠겨있는지 여부를 리턴
- isCredentialsNonExpired() : 비밀번호가 만료됐는지 여부를 리턴
- isEnabled() : 계정이 활성화 됐는지 여부를 리턴
2. UserDetailsRepository에서 사용자 정보를 가져올 UserDetailsService를 구현한 클래스 생성
- UserDetails loadUserByUsername(String username) : 유니크한 username을 통해 계정의 엔티티를 리턴
3. TokenProvider : UserDetails 정보를 이용해 JWT 토큰을 생성, 제공하는 클래스
- generateToken : username과 key를 이용해 토큰을 생성한다.
// 키와 사용자 정보를 이용해 토큰 생성
String secretKey = Jwts.SIG.HS256.key().build();
String token = Jwts.builder()
.header()
.add("roles", roles) // list<String>로 된 권한을 값으로 등록
.and()
.subject(username) // subject로 username(unique한 id) 등록
.issuedAt(new Date()) // 토큰 발행 날짜
.expiration(new Date(now.getTime()+1000)) // 토큰 만료 날짜
.signWith(secretKey) // secretKey로 서명
.compact();
- 생성된 토큰의 유효성을 검증하고 subject(username)를 추출한다.
Claims claims = Jwts.parser().verifyWith(secretKey).build()
.parseSignedClaims(token).getPayload();
if (!claims.getExpiration().before(new Date())) {
String username = claims.getSubject();
}
- username을 통해 SecurityContextHolder에 저장할 UsernamePasswordAuthenticationToken 생성
UserDetails userDatails = userDetailsService.loadUserByUsername(username);
Authentication auth = new UsernamePasswordAuthenticationToken
(userDetails, "", userDetails.getAuthorities());
4. JwtAuthenticationFilter : 요청이 들어오면 헤더의 토큰을 이용해 SecurityContextHolder에 저장하는 필터
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final TokenProvider tokenProvider;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
String token = request.getHeader("Auth-Header");
if (tokenProvider.getAuthentication(token)) { // TokenProvider 내 토큰을 검증하는 메서드
Authentication auth = tokenProvider.getAuthentication(token) // TokenProvider 내 토큰을 얻는 메서드
SecurityContextHolder.getContext().setAuthentication(auth);
}
filterChain.doFilter(request, response);
}
}
- OncePerRequestFilter를 상속받은 것은 GenericFilterBean을 바로 상속하는 것과 달리 매 요청마다 한 번만 실행됨
5. SecurityFilterChain(HttpSecurity) Bean 등록 : 보안 설정
- 리소스 접근 권한 설정
- 인증 실패 시 발생하는 예외 처리
- 인증 로직 커스터마이징
- csrf, cors 등 스프링 시큐리티 설정
private final JwtAuthenticationFilter authenticationFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement((sm) ->
sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests((auth) -> {
auth.requestMatchers(
antMatcher("**all**"),
antMatcher(HttpMethod.GET, "/get/**")).permitAll()
.requestMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated();
})
.exceptionHandler(e -> {
e.authenticationEntryPoint((request, response, exception) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED));
e.accessDeniedHandler((request, response, exception)->
response.sendError(HttpServletResponse.SC_FORBIDDEN));
})
.addFilterBefore(authenticationFilter,
UsernamePasswordAuthenticationFilter.class)
.build();
}
- httpBasic : 브라우저의 인증 UI를 이용해 아이디, 비밀번호를 확인하는 것으로 jwt 기반 인증에서는 비활성화한다.
- csrf : 의도치 않은 요청을 보내는 공격을 막기 위해 csrf 토큰을 보내 검증하는데 jwt 토큰을 사용하므로 비활성화한다.
- sessionManagement : 세션 동작 방식으로 jwt 토큰으로 인증을 처리하며 세션을 사용하지 않으므로 STATELESS
- authorizeHttpRequests : 요청에 대한 권한 체크
- exceptionHandling : 시큐리티 필터 내에서 발생하는 예외 처리 방법
- authenticationEntryPoint : 인증 정보가 없거나 잘못된 정보일 때 발생하는 예외
- accessDeniedHandler : 인증이 되었지만 접근 권한이 없을 때(인가) 발생하는 예외
- addFilterBefore(a, b) : b 필터 앞에 a 필터를 추가한다.
Custom ExceptionHandling
- AccessDeniedHandler 인터페이스를 구현해 handle(request, response, exception)을 오버라이딩한다.
- AuthenticationEntryPoint 인터페이스를 구현해 commence(request, response, exception)을 오버라이딩한다.
private final CustomAccessDeniedHandler accessDenied;
private final CustomAuthenticationEntryPoint authEntryPoint;
// securityFilterChain
.exceptionHandling(e -> {
e.authenticationEntryPoint(authEntryPoint);
e.accessDeniedHandler(accessDenied);
})
회원가입
: (username, password, roles)+a를 저장
- password는 PasswordEncoder를 통해 암호화한다.
// Entity
@ElementCollection
@JoinTable(name="생성될 테이블명", joinColumns=@JoinColumn(name="id 컬럼명"))
private List<String> roles;
// Service
private PasswordEncoder passwordEncoder; // 비밀번호 암호화를 위한 클래스
public User signUp(String username, String password, List<String> roles) {
User user = User.builder()
.username(username)
.password(passwordEncoder.encode(password))
.roles(roles)
.build();
userRepository.save(user);
}
반응형
'공부 > 백엔드' 카테고리의 다른 글
| [JMeter] 성능 측정 도구로 성능 테스트 및 개선 수치 파악 (2) | 2024.11.01 |
|---|---|
| [스프링 부트 핵심 가이드] 12 : 서버 간 통신 (0) | 2024.06.10 |
| [스프링 부트 핵심 가이드] 11 : 액추에이터 활용하기 (1) | 2024.06.08 |
| [스프링 부트 핵심 가이드] 10 : 유효성 검사와 예외 처리 (0) | 2024.05.31 |
| [스프링 부트 핵심 가이드] 9 : 연관관계 매핑 (0) | 2024.05.25 |