로그인한 이용자만 주문내역을 볼 수 있어야 한다.
/api/orders 접근은 로그인한 이용자만 접근 가능.
백엔드
1. Okta Spring Boot Starter를 Maven pom.xml에 추가
2. Okta developer 사이트에서 App 생성
3. application properties를 수정
4. Spring security configuration class에서 엔드포인트 보호
1.
pom.xml에 추가하가 Maven Update를 한다.
Okta는 OAuth2와 OpenID Connect를 위해 Spring Boot Starter를 제공한다.
Spring security 와 Okta의 설정과 통합을 단순화한 것이다.
2.
Application에서 생성할 때 OIDC와 Web Application인 걸 선택하면 된다.
Login redirect URI는 http://localhost:8080/login/oauth2/code/okta
Spring boot의 Okta spring boot starter에 엔드포인트가 만들어져있기 때문에 우리가 따로 엔드포인트를 만들 필요가 없다. 이 uri가 바로 만들어져 있는 uri이다.
3.
앱 설정에서 해당 정보를 찾아서 집어넣는다.
Resource server는 리소스를 호스팅하는 앱이다.
여기서는 Spring boot가 리소스 서버이다.
리소스 서버는 access tokens인 JWT를 사용하여 보안을 관리한다.
Access token은 Authorization Server인 Okta에 의해 검증된다.
유저는 Okta에게 출입증인 토큰을 받고
유저는 받은 토큰으로 스프링부트에게 제시한다.
스프링부트는 Okta에게 유저에게 받은 이 토큰이 맞는 건지 물어본다.
Okta가 맞다고 하면 스프링부트는 유저에게 접근을 허용한다.
4.
configuration으로 쓸 클래스 하나 만들어 준다.
.antMatchers는 보호할 엔드포인트 경로를 설정한다.
.oauth2ResourceServer는 OAuth2 Resource Server support를 설정한다.
.jwt는 JWT-encoded bearer token support를 실행한다.
cors는 CORS 필터를 추가한다.
Postman으로 쏴보면 인가받지 않아서 안되는 걸 알 수 있다.
아까 맏는 SecurityConfiguration에서 다음 줄을 추가하면 response body에서 내용을 볼 수 있다.
Okta.configureResourceServer401ResponseBody(http);
프론트엔드
클라이언트앱이 리소스서버에 토큰을 보내는 Sends access tokens 과정과 관련된 내용이다.
Angular interceptor는 HTTP request/response를 가로채서 access token을 넣고 Resource Server에 보내는데
이 때 interceptor를 이용한다.
Angular Interceptors, Promises, async/await 관련 정보
개발순서
1. Interceptor 생성
2. Interceptor 관련 내용 app.module.ts 수정
1.
ng generate service services/AuthInterceptor
import { Inject, Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { from, Observable } from 'rxjs';
import { OktaAuth } from '@okta/okta-auth-js';
import { OktaAuthStateService, OKTA_AUTH } from '@okta/okta-angular';
@Injectable({
providedIn: 'root'
})
export class AuthInterceptorService implements HttpInterceptor {
constructor(@Inject(OKTA_AUTH)private oktaAuth: OktaAuth) { }
intercept(request: HttpRequest<any>, next:HttpHandler): Observable<HttpEvent<any>> {
return from(this.handleAccess(request, next));
}
private async handleAccess(request: HttpRequest<any>, next: HttpHandler): Promise<HttpEvent<any>>{
//access token을 secured endpoint에 넣는다.
const securedEndpoints = ['http://localhost:8080/api/orders'];
if(securedEndpoints.some(url => request.urlWithParams.includes(url))){
//access token을 얻는다 .
const accessToken = await this.oktaAuth.getAccessToken();
request = request.clone({
setHeaders: {
Authorization: 'Bearer ' + accessToken
}
});
}
return next.handle(request).toPromise();
}
}
request 내용은 변할 수 없기에 사본을 만들고 헤더에 access token을 추가해서 보낸다.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { ProductListComponent } from './components/product-list/product-list.component';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { ProductService } from './services/product.service';
import { Routes, RouterModule, Router} from '@angular/router';
import { ProductCategoryMenuComponent } from './components/product-category-menu/product-category-menu.component';
import { SearchComponent } from './components/search/search.component';
import { ProductDetailsComponent } from './components/product-details/product-details.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { CartStatusComponent } from './components/cart-status/cart-status.component';
import { CartDetailsComponent } from './components/cart-details/cart-details.component';
import { CheckoutComponent } from './components/checkout/checkout.component';
import { ReactiveFormsModule } from '@angular/forms';
import { LoginComponent } from './components/login/login.component';
import { LoginStatusComponent } from './components/login-status/login-status.component';
import {
OKTA_CONFIG,
OktaAuthModule,
OktaCallbackComponent,
OktaAuthGuard
} from '@okta/okta-angular';
import myAppConfig from './config/my-app-config';
import { OktaAuth } from '@okta/okta-auth-js';
import { MembersPageComponent } from './components/members-page/members-page.component';
import { OrderHistoryComponent } from './components/order-history/order-history.component';
import { AuthInterceptorService } from './services/auth-interceptor.service';
const oktaConfig = Object.assign({
onAuthRequired:(oktaAuth:any, injector:any)=>{
const router=injector.get(Router);
router.navigate(['/login']);
}
}, myAppConfig.oidc);
const oktaAuth = new OktaAuth(oktaConfig);
const routes: Routes = [
{path: 'order-history', component: OrderHistoryComponent, canActivate: [OktaAuthGuard]},
{path: 'members', component: MembersPageComponent, canActivate: [OktaAuthGuard]},
{path: 'login/callback', component: OktaCallbackComponent},
{path: 'login', component: LoginComponent},
{path: 'checkout', component: CheckoutComponent},
{path: 'cart-details', component: CartDetailsComponent},
{path: 'products/:id', component: ProductDetailsComponent},
{path: 'search/:keyword', component: ProductListComponent},
{path: 'category/:id/:name', component: ProductListComponent},
{path: 'category', component: ProductListComponent},
{path: 'products', component: ProductListComponent},
{path: '', redirectTo:'/products',pathMatch:'full'},
{path: '**', redirectTo:'/products',pathMatch:'full'}
];
@NgModule({
declarations: [
AppComponent,
ProductListComponent,
ProductCategoryMenuComponent,
SearchComponent,
ProductDetailsComponent,
CartStatusComponent,
CartDetailsComponent,
CheckoutComponent,
LoginComponent,
LoginStatusComponent,
MembersPageComponent,
OrderHistoryComponent
],
imports: [
RouterModule.forRoot(routes),
BrowserModule,
HttpClientModule,
NgbModule,
ReactiveFormsModule,
OktaAuthModule
],
providers: [ProductService,{provide: OKTA_CONFIG, useValue:{oktaAuth}},
{provide: HTTP_INTERCEPTORS, useClass: AuthInterceptorService, multi: true}],
bootstrap: [AppComponent]
})
export class AppModule { }
HTTP_INTERCEPTORS는 HTTP interceptors에 관련 토큰
AuthInterceptorService를 HTTP intercepter로 등록.
multi: true는 Angular에게 HTTP_INTERCEPTORS는 배열에 주입된 토큰이란 걸 알려준다.
추가
하지만 새로운 주문을 하게 되면 작동하지 않게 된다.
CSRF는 쿠키를 이용한 POST를 확인해서 작동되는데
우리는 세션을 위해서 쿠키를 사용하지 않기 때문에
CSRF가 인가받지 않았다고 말한다.
이것을 해결하기 위해서 CSRF를 끈다.
'컴퓨터공학 > Boot & Angular' 카테고리의 다른 글
Angular> Environments (0) | 2022.04.10 |
---|---|
Angular> HTTPS (0) | 2022.04.09 |
Angular> 주문 내역 (0) | 2022.03.31 |
Angular> 리팩토링 (0) | 2022.03.30 |
Angular> 유저 등록, 회원 전용, 세션 저장 (0) | 2022.03.29 |