https://www.inflearn.com/course/spring_rest-api
ㅁ 이벤트 조회 및 수정 REST API 개발
ㅇ 이벤트 목록 조회 API 구현
- page를 PagedResourcesAssembler<Event> 를 통해 Resource로 구현
> var pagedResources = pagedResourcesAssembler.toModel(page);
- 구현한 30개의 event 들도 EventResource로 만들어서 링크를 만들어 담을 수 있다.
- modelMapper로 객체와 객체DTO 연결
ㅁ REST API 보안 적용
ㅇ Account 도메인 추가
- 스프링 시큐리티 OAuth2 추가
- @Table(“Users”)로 구현할 수 있으나, Account 도메인 생성
ㅇ 스프링 시큐리티
- 웹 시큐리티와 메소드 시큐리티로 구분
> 웹 시큐리티 : 웹 요청에 보안 인증
> 메소드 시큐리티 : 어떤 메소드가 호출되었을때 인증, 권한을 확인함
>> 공통된 SecurityIntercepter 인터페이스를 가짐
>> 구현체가 2개 : Method Security Intercepter, Filter SecurityIntercepter
- 스프링 5부터 웹기반이 서블릿과 webflux로 나눠짐
- SecuritContext Holder (자바 쓰레드 로컬 - 한 쓰레드 내에서 공유하는 자원(저장소) )에 인증정보를 담아옴
> 쓰레드 로컬은 자원을 넘겨줄때 파라미터로 넘겨주지 않아도 됨
> 인증된 정보를 꺼내서 있을 경우, Authentication Manager (로그인 담당) 이 로그인을 시킴
>> 인증 요청 헤더에 Authentication, basic, username, password를 합쳐서 인코딩 한걸로 입력을 받음
>> UserDetailsService를 통해 DB에서 읽어 온 패스워드와 사용자가 입력한 password가 매칭되는지 passwordencorder로 비교
>> 이후 로그인이 되었으면, AccessDecisionManager를 통해 Account 의 Role을 확인
- UserDetailService의 구현체 서비스에서 우리가 로직을 짜는데, 여기서 UserDetails라는 형식으로 반환을 해줘야함
> 그러기 위해서 스프링 시큐리티의 authorities(account.getRoles()) 구현
- fail() 이라는 메소드도 있음
- @Rule은 Junit4 애노테이션인데, 아래 디펜던시 추가하여 Junit5에서 사용
> Junit3, Junit4 애노테이션 지원
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
- 스프링 시큐리티 디펜던시를 추가하면, 스프링 부트는 스프링시큐리티 자동설정을 적용하게 되고, 모든 요청은 인증을 필요하게 됨
> 인메모리에 사용자 계정이 추가됨
> 아래 추가시, 시큐리티 자동설정은 해지되고 아래에서 설정한 것만 적용됨
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
- PasswordEncorder는 hashmap을 만들고, 인코딩을 진행하여 인코딩타입, 인코딩값 으로 해시맵 저장
- HttpSecurity와 WebSecurity 필터로 나누는걸 실습했는데,
http 보다는 web에서 필터링 하는게 더 부하를 적게 준다.
> web 필터링 -> http 필터링 순으로 진행
- 아래와 같이 작성하면, 특정 url에 인증정보가 없을 경우 로그인 창으로 가게 된다. (폼 인증)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.anonymous()
.and()
.formLogin()
.and()
.authorizeRequests()
.mvcMatchers(HttpMethod.GET,"/api/**").authenticated()
.anyRequest().authenticated();
}
ㅇ 스프링 시큐리티 oAuth2 설정 : 인증 서버 설정
- Grant Type : Password 방식의 토큰 인증 방식 구성
> 토큰 발급시, 1홉으로 진행됨
> 실제 여러홉으로 진행해야하는 토큰발급 방식이 많음
> 인증정보를 가지고 있는 쪽에서 사용해야함
- 인증서버 설정에
> passwordEncorder 설정
> 클라이언트디테일서비스 설정 : 클라이언트 아이디, 토큰 타입, 토큰 시간 등
> 엔드포인트 설정 : authenticationmanager, userdetailsservice, tokenstore
+) 클라이언트 아이디, 시크릿은 어플리케이션 기준으로 제공됨
좋은 질문 & 답글
Token 발급 TEST 코드를 작성하면서 "/oauth/token" URL을 POST 방식으로 요청하는 것을 볼 수 있었습니다.
저희가 직접 "/oauth/token" URL에 대해 매핑을 하지 않아도 처리가 가능했던 이유는
pom.xml에서 "spring-security-oauth2-autoconfigure" 의존성 설정을 함으로써 가능했던 것이고,
더 나아가 실제로 어떤 지점에서 "/oauth/token" URL이 매핑되어 처리가 되는지 디버깅을 통해 찾아본 결과,
org.springframewirk.security.oauth2.config.annotaion.web.configuration 패키지의
WebSecurityConfigurerAdapter를 상속받은 AuthorizationServerSecurityConfiguration 클래스의 configure(HttpSecurity http) 메소드가 재정의 됨으로써
"/oauth/token" URL이 매핑이 되었다는 것을 알 수 있었습니다
ㅇ 리소스 서버 설정
- 토큰 기반으로 인증을 확인하고, 접근을 제한하는 서버
- 테스트 전에 db를 비워준다
> 인메모리 h2 이지만, 테스트 동시 실행시 테스트간에 디비가 공유됨
> 위 문제로 토큰을 가져오지 못하는 문제 발생
- 아래 코드로 해결
//@Before - Junit4
@BeforeEach
public void setUp(){
this.eventRepository.deleteAll();
this.accountRepository.deleteAll();
}
ㅇ 문자열을 외부 설정으로 빼내기
- AppProperties 클래스 생성하여,
@ConfigurationProperties(prefix = "my-app")
설정 후, application.properties 에서 my-app.xxx 로 설정함
이후 빈 설정 및 호출하여 필요한 곳에 설정
> 테스트 쪽 application.properties에 my-app 을 적었는데, 이러면 오류남
ㅇ 현재 사용자 조회
- 인증된 사용자를 통해,
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
위에 코드 통해, request의 header에 담긴 토큰에 대한 authentication 정보를 받을 수 있다.
> 이후 User principal = (User) authentication.getPrincipal(); 로 스프링 시큐리티의 User로 객체를 받을 수 있음
> @AuthenticationPrincipal User user 로 아규먼트로도 주입 가능
> UserDetailsService의 구현체에 loadUserByUsername 메소드의 리턴을 내가 원하는 값의 adpator로 만든다 (이것은 시큐리티의 User 상속)
> @AuthenticationPrincipal(expression = "account") Account account 로 만들면 SPel에 의해 adaptor객체에 있는, account가 추출되어 담긴다.
> 위 내용을 아래와 같이 애노테이션을 만들어 적용
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : account")
public @interface CurrentUser {
}
ㅇ 출력값 제한하기
- createEvent 발생시, Account 객체의 password 도 나와 제한 필요
- JsonSerializer 사용
> 이후 Event 객체의 Account manager property에도 @JsonSerialize(using= AccountSerializer.class) 를 통해 ID만 가지게 수정
public class AccountSerializer extends JsonSerializer<Account> {
@Override
public void serialize(Account account, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeNumberField("id",account.getId());
jsonGenerator.writeEndObject();
}
}
ㅇ 깨진 테스트 살펴보기 & 스프링 부트 업그레이드
- DemoInflearnRestApiApplicationTests 가 실패하는 이유?
> 기본적으로 생성된 테스트인데, 프로파일이 테스트로 설정되어있지 않음
> @ActiveProfile(value="test" ) 로 설정하면 application-test.properties 가 application.properties를 오버라이딩하면서 적용됨
- @TestDescription (커스터마이징 ) -> @DisplayName
'Spring' 카테고리의 다른 글
[Spring] 실전! 스프링 데이터 JPA - 2 (0) | 2021.09.03 |
---|---|
[Spring] 실전! 스프링 데이터 JPA - 1 (0) | 2021.09.02 |
[Spring] 스프링 기반 REST API 개발 - 2 (0) | 2021.08.03 |
[Spring] 스프링 기반 REST API 개발 - 1 (0) | 2021.08.03 |
[Spring] 스프링 웹 MVC - 3 (0) | 2021.08.02 |