이 블로그는 개인의 공부 목적으로 작성된 블로그입니다. 왜곡된 정보가 포함되어 있을 수 있습니다.
Spring security
Spring security는 Spring 에서 제공하는 프레임워크로 보안에서의 인증, 인가를 지원한다. Spring security를 통해 애플리케이션 측면에서 보안이 어떻게 이루어지는지 학습하고 최종적으로는 Oauth2를 사용하보자
초기 설정
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
build.gradle 에 다음을 추가하자 이때, thymeleaf-extras-springsecurity6은 thymeleaf위에 springsecurity를 사용하기 위한 것을 spring initializr 에서 자동으로 제공해주었다.
온전하게 설치가 되었는지 확인하기 위해 예제코드를 작성해보자
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello(){
return "Hello";
}
}
/hello 로 GET 요청을 보내면 "Hello"를 response 하는 API이다.
curl http://localhost:8080/hello
curl 을 통해 GET 요청
401(인증 자격 없음) 상태코드를 확인 할 수 있다.
springboot console에 다음과 같이 security password를 확인할 수 있다.
security password를 요청에 user 정보로 같이 보내보자
hello가 잘 출력된 것을 확인 했다면 성공!
주의: powershell에서는 curl -u를 지원하는 않는것으로 확인 되었다. curl.exe를 다운로드 받아 실행하거나 window+R로 cmd를 실행시켜 curl -u 를 사용하는 것을 권장한다.
이렇게 Spring security에서는 자격증명을 제공하고 있다.
password를 자격증명 key로 공유하여 사용하기 때문에 대칭키의 방식으로 암호화한다.
Spring Security architecture
AuthenticationFilter: 인증 필터로 인증 요청을 AuthenticationManager에게 위임하고 response 를 바탕으로 SecurityContextHolder를 관리
AuthenticationManager: AuthenticationProvider를 이용해 인증 처리
AuthenticationProvider: 사용자 관리 책임을 구현하는 사용자 세부 정보 서비스와 암호 인코더를 활용하여 인증 로직을 구현
SecurityContextHolder" 인증 프로세스 이후 인증 데이터를 유지
UserDetailServie: 사용자에 관한 세부 정보에 대해 인스턴스 형태로 관리됨, 이때 기본 자격 증명에서의 사용자는 user이고 기본 암호는 UUID 형식으로 스프링 컨텍스트가 로드 될때 자동으로 생성(위 예제에서 console에서 출력된 값)
그림에서 표시되어 있지 않지만 PasswordEncoder가 존재하여 암호를 인코딩하고, 암호가 기존 인코딩과 일치하는지 판단한다.
단순한 구현으로는 HTTP Authorization header에 클라이언트 이름과 암호를 넣어 보내는 방식으로 통신하기 때문에 중간에 가로채는 경우, 패킷을 통해 다 볼 수 있다.
출처: Spring Security in Action
재정의(UserDetailService + PasswordEncoder)
Spring Security의 기본 구성을 재정의 해보자 UserDetailService와 PasswordEncoder를 재정의 하여 AuthenticationProvider에 인증 로직을 변경해보자
Config 클래스를 선언한다.
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@Configuration
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService(){
InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
UserDetails user = User.withUsername("jiwon")
.password("0000")
.authorities("read")
.build();
userDetailsService.createUser(user);
return userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
}
userDetailsService를 상속한 InMemoryUserDetailsManger 인스턴스를 빈에 등록한다.(메모리 기반 인증)
https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/in-memory.html
userDetailService를 재정의하는 경우 PasswordEncoder를 직접 등록해야한다고 한다 따라서, PasswordEncoder를 빈에 등록한다.
이후 curl 명령어를 통해 get 요청을 보내 보자(본인은 postman을 사용하였음)
1) 유저정보없이 get 요청을 보내는 경우
401(인증 불가)를 확인할 수 있다.
2) 유저정보 추가
200(정상 통신)을 확인 할 수 있다.
재정의(configure-AuthenticationMangerBuilder)
위의 방법과 다른 방법으로 configure를 재정의하여 구현할 수도 있다(결과는 위와 동일)
재정의(AuthenticationProvider)
UserDetailService와 PasswordEncoder에게 역활을 위임하는 AuthenticationProvider를 재정의 함으로서 구현 할 수도 있다.
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username=authentication.getName();
String password=String.valueOf(authentication.getCredentials());
if("john".equals(username)&&"12345".equals(password)){
return new UsernamePasswordAuthenticationToken(username,password, Arrays.asList());
}
else{
throw new AuthenticationCredentialsNotFoundException("Error in authentication");
}
}
@Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication);
}
}
AuthenticationProvider를 구현하여 인증로직(authenticate 함수)를 구현하고 이때, authentication은 사용자의 id,password를 전달한다.위코드는 단순로직을 구현한 것이고, authenticate은 UserDetailService와 PasswordEncoder에게 역활을 부여해야한다. 그리고 AuthentioanProvider를 등록하여 사용할 수 있다
(WebSecuritConfigurer Adapter은 내가 현재 사용하는 스프링 부트 3.xx)에서는 아에 삭제가 되어 아래와 같이 filterChain을 통해 해결 가능하다)
https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter/
@Configuration
public class SecurityConfig {
@Bean
public CustomAuthenticationProvider customAuthenticationProvider(){
return new CustomAuthenticationProvider();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
)
.authenticationProvider(new CustomAuthenticationProvider())
.httpBasic(withDefaults());
return http.build();
}
}
'보안' 카테고리의 다른 글
[Spring Security in action] AuthenticationProvider을 사용한 인증 (0) | 2024.01.16 |
---|---|
[Spring Security in action] 사용자 관리 (0) | 2024.01.15 |
[보안] 전자 지불 시스템, 전자 화페 (0) | 2023.12.08 |
[보안] 전자서명 (0) | 2023.12.02 |
[보안] 비대칭 암호화, ElGamal, RSA (3) | 2023.12.01 |