[SpringBoot API] SpringBoot REST API : REST API 보안 적용

Cover image

REST API 보안 적용


Account 도메인 추가

OAuth2로 인증을 하려면 일단 Account 부터

  • id
  • email
  • password
  • roles

AccountRoles

  • ADMIN, USER

JPA 맵핑

  • @Table(“Users”)

JPA enumeration collection mapping

    @ElementCollection(fetch = FetchType.EAGER)
    @Enumerated(EnumType.STRING)
    private Set<AccountRole> roles;

Event에 owner 추가

    @ManyToOne
    Account manager;

스프링 시큐리티

스프링 시큐리티

  • 웹 시큐리티 (Filter 기반 시큐리티)
  • 메소드 시큐리티
  • 이 둘 다 Security Interceptor를 사용합니다.

    • 리소스에 접근을 허용할 것이냐 말것이냐를 결정하는 로직이 들어있음.

image

의존성 추가

<dependency>
  <groupId>org.springframework.security.oauth.boot</groupId>
  <artifactId>spring-security-oauth2-autoconfigure</artifactId>
  <version>2.1.0.RELEASE</version>
</dependency>
  • 테스트 다 깨짐 (401 Unauthorized)

    • 깨지는 이유는 스프링 부트가 제공하는 스프링 시큐리티 기본 설정 때문.

예외 테스트

  1. @Test(expected)

예외 타입만 확인 가능

  1. try-catch

예외 타입과 메시지 확인 가능. 하지만 코드가 다소 복잡.

  1. @Rule ExpectedException

코드는 간결하면서 예외 타입과 메시지 모두 확인 가능


스프링 시큐리티 기본 설정

시큐리티 필터를 적용하기 않음...

  • /docs/index.html

로그인 없이 접근 가능

  • GET /api/events
  • GET /api/events/{id}

로그인 해야 접근 가능

  • 나머지 다...
  • POST /api/events
  • PUT /api/events/{id{
  • ...

스프링 시큐리티 OAuth 2.0

  • AuthorizationServer: OAuth2 토큰 발행(/oauth/token) 및 토큰 인증(/oauth/authorize)

    • Oder 0 (리소스 서버 보다 우선 순위가 높다.)
  • ResourceServer: 리소스 요청 인증 처리 (OAuth 2 토큰 검사)

    • Oder 3 (이 값은 현재 고칠 수 없음)

스프링 시큐리티 설정

  • @EnableWebSecurity
  • @EnableGlobalMethodSecurity
  • extends WebSecurityConfigurerAdapter
  • PasswordEncoder: PasswordEncoderFactories.createDelegatingPassworkEncoder()
  • TokenStore: InMemoryTokenStore
  • AuthenticationManagerBean
  • configure(AuthenticationManagerBuidler auth)

    • userDetailsService
    • passwordEncoder
  • configure(HttpSecurity http)

    • /docs/**: permitAll
  • configure(WebSecurty web)

    • ignore

      • /docs/**
      • /favicon.ico
  • PathRequest.toStaticResources() 사용하기

스프링 시큐리티 폼 인증 설정

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .anonymous()
                .and()
            .formLogin()
                .and()
            .authorizeRequests()
                .mvcMatchers(HttpMethod.GET, "/api/**").authenticated()
                .anyRequest().authenticated();
    }
  • 익명 사용자 사용 활성화
  • 폼 인증 방식 활성화

    • 스프링 시큐리티가 기본 로그인 페이지 제공
  • 요청에 인증 적용

    • /api 이하 모든 GET 요청에 인증이 필요함. (permitAll()을 사용하여 인증이 필요없이 익명으로 접근이 가능케 할 수 있음)
    • 그밖에 모은 요청도 인증이 필요함.

스프링 시큐리티 OAuth 2 설정 : 인증 서버 설정

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <version>${spring-security.version}</version>
  <scope>test</scope>
</dependency>

토큰 발행 테스트

  • User
  • Client
  • POST /oauth/token

    • HTTP Basic 인증 헤더 (클라이언트 아이디 + 클라이언트 시크릿)
    • 요청 매개변수 (MultiValuMap<String, String>)

      • grant_type: password
      • username
      • password
    • 응답에 access_token 나오는지 확인

Grant Type: Password

AuthorizationServer 설정

  • @EnableAuthorizationServer
  • extends AuthorizationServerConfigurerAdapter
  • configure(AuthorizationServerSecurityConfigurer security)

    • PassswordEncode 설정
  • configure(ClientDetailsServiceConfigurer clients)

    • 클라이언트 설정
    • grantTypes

      • password
      • refresh_token
    • scopes
    • secret / name
    • accessTokenValiditySeconds
    • refreshTokenValiditySeconds
  • AuthorizationServerEndpointsConfigurer

    • tokenStore
    • authenticationMaanger
    • userDetailsService

스프링 시큐리티 OAuth 2 설정: 리소스 서버 설정

테스트 수정

  • GET을 제외하고 모두 엑세스 토큰을 가지고 요청 하도록 테스트 수정

ResourceServer 설정

  • @EnableResourceServer
  • extends ResourceServerConfigurerAdapter
  • configure(ResourceServerSecurityConfigurer resources)

    • 리소스 ID
  • configure(HttpSecurity http)

    • anonymous
    • GET /api/** : permit all
    • POST /api/**: authenticated
    • PUT /api/**: authenticated
    • 에러 처리

      • accessDeniedHandler(OAuth2AccessDeniedHandler())

문자열을 외부 설정으로 빼내기

기본 유저 만들기

  • ApplicationRunner

    • Admin
    • User

외부 설정으로 기본 유저와 클라이언트 정보 빼내기

  • @ConfigurationProperties

이벤트 API 점검

토큰 발급 받기

  • POST /oauth/token
  • BASIC authentication 헤더

    • client Id(myApp) + client secret(pass)
  • 요청 본문 폼

    • username: admin@email.com
    • password: admin
    • grant_type: password

image

토큰 갱신하기

  • POST /oauth/token
  • BASIC authentication 헤더

    • client Id(myApp) + client secret(pass)
  • 요청 본문 폼

    • token: 처음에 발급받았던 refersh 토큰
    • grant_type: refresh_token

image

이벤트 목록 조회 API

  • 로그인 했을 때

    • 이벤트 생성 링크 제공

이벤트 조회

  • 로그인 했을 때

    • 이벤트 Manager인 경우에는 이벤트 수정 링크 제공

스프링 시큐리티 현재 사용자

SecurityContext

  • 자바 ThreadLocal 기반 구현으로 인증 정보를 담고 있다.
  • 인증 정보 꺼내는 방법: Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

@AuthenticationPrincipal spring.security.User user

  • 인증 안한 경우에 null
  • 인증 한 경우에는 username과 authorities 참조 가능

spring.security.User를 상속받는 클래스를 구현하면

  • 도메인 User를 받을 수 있다.
  • @AuthenticationPrincipa me.whiteship.user.UserAdapter
  • Adatepr.getUser().getId()

SpEL을 사용하면

  • @AuthenticationPrincipa(expression=”account”) me.whiteship.user.Account
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal(expression = "account")
public @interface CurrentUser {
}

커스텀 애노테이션을 만들면

  • @CurrentUser Account account
  • 엇? 근데 인증 안하고 접근하면..?
expression = "#this == 'anonymousUser' ? null : account"
  • 현재 인증 정보가 anonymousUse 인 경우에는 null을 보내고 아니면 “account”를 꺼내준다.

조회 API 개선

  • 현재 조회하는 사용자가 owner인 경우에 update 링크 추가 (HATEOAS)

수정 API 개선

  • 현재 사용자가 이벤트 owner가 아닌 경우에 403 에러 발생

Events API 개선: 출력값 제한하기

생성 API 개선

  • Event owner 설정
  • 응답에서 owner의 id만 보내 줄 것.
{
  "id" : 4,
  "name" : "test 3PISM1Ju",
  "description" : "test event",
...
  "free" : false
  "eventStatus" : "DRAFT",
  "owner" : {
    "id" : 3,
    "email" : "keesun@email.com",
    "password" : "{bcrypt}$2a$10$3z/rHmeYsKpoOQR3aUq38OmZjZNsrGfRZxSnmpLfL3lpLxjD5/JZ6",
    "roles" : [ "USER", "ADMIN" ]
  },
  • JsonSerializer 구현
  • @JsonSerialize(using) 설정

깨진 테스트 살펴보기

EventControllerTests.updateEvent()

  • 깨지는 이유
  • 해결 방법

EventControllerTests.getEvent()

  • 깨지는 이유
  • 해결 방법

DemoApplicationTests

  • 깨지는 이유
  • 해결 방법

스프링 부트 2.2.5 버전으로 업데이트

현재 스프링 부트 최신 권장 버전은 2.2.5

스프링 부트 버전을 올리면 바뀔 수 있는 일

  • 기본 (자동) 설정 값 변경
  • 의존성 변경
  • 기존 애플리케이션의 동작이 바뀌거나 컴파일 에러가 발생할 수 있다.

스프링 부트 2.2.* 주요 변화

  • JUnit 4 -> JUnit 5
  • 스프링 HATEOAS 버전이 올라가면서 스프링 HATEOAS의 API가 바뀌었다.

스프링 HATEOAS

  • Resource -> EntityModel
  • Resources -> CollectionModel
  • PagedResrouces -> PagedModel
  • ResourceSupport -> RepresentationModel
  • assembler.toResource -> assembler.toModel
  • org.springframework.hateoas.mvc.ControllerLinkBuilder -> org.springframework.hateoas.server.mvc.WebMvcLinkBuilder
  • MediaTypes 중에 (UTF8)인코딩이 들어간 상수 제거.

JUnit 5

  • org.junit -> org.junit.jupiter
  • @Ignore -> @Disabled
  • @Before, @After -> @BeforeEach, @AfterEach
  • @TestDescription (우리가 만든거) -> @DisplayName

스프링 MVC 변경

  • MediaType 중에 (UTF8)인코딩이 들어간 상수 deprecation.