Oauth jwt example
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
Oauth jwt API WEB 예제 샘플 코드
https://github.com/withccm/oauth-jwt-example
API 서버와 WEB 클라이언트 oauth 로그인 인증 예제로 구성
프로젝트 구성
API
- Oauth 토근으로 인증할 수 있는 프로젝트 구성
- JWT 사용하여 Access Token, Refresh Token 인증 로직 구현
- JWT(Json Web Token)는 사용자 인증을 위한 암호화된 토큰이다.
- Session서버를 구성이 필요없어, 확장성이 있다.
- JWT를 이용하여 Access Token, Refresh Token 각각 토큰을 생성한다.
- Access Token은 만료 시간이 짧은 토큰, 반면 Refresh Token은 만료 시간이 길다.
- Access Token이 탈취될 경우 보안 취약점이 발생할 수 있다. 만료시간을 짧게함으로 피해를 줄일 수 있다.(추가로 구현하면 서버에서 기존 토큰을 강제 로그아웃 시키는 기능도 가능하다.)
Access Token이 만료되는 경우 매번 로그인을 해야하는 번거로움이 생길 수 있는데, 이를 보완하는 것이 Refresh Token이다. Refresh Token은 Access Token이 만료되 시점에 신규 토큰으로 갱신하게 해준다. - 다양한 Client(AOS, IOS등) 상관없이 확장 가능
- 구글 샘플
- 역할 샘플
Web Client
- 구글 로그인 SDK 구현
- Access Token 만료시 Refresh Token 호출 로직
- 샘플 API 호출
토큰 인증 과정 UML
access 토큰과 refresh 토큰
access 토큰은 API 요청시에 헤더에 포함하여 인증값으로 사용한다. access 토큰이 탈퇴당하는 경우 보안에 취약해진다. 이를 방지하기 위해 access 토큰의 유효시간은 짧게 제공한다.
이때 문제점이 발생한다. access 토큰이 만료 될때마다, 사용자에게 로그인을 요구한다면 사용자는 서비스 이용에 번거로움이 생긴다. 이때 사용하는 것이 refresh 토큰이다. (refresh 토큰의 만료시간은 길게 설정하는 편이다.)
서버에서 access 토큰이 만료되었다는 응답을 받았을때, refresh 토큰을 사용하여 access 토큰을 새로 갱신할 수 있다.
API 상세설명
개발 환경
- Java 11
- Gradle 7.1
- Mysql
주요 자바 라이브러리
- Spring Boot 2.5.2
- Spring security
- JPA
- jjwt-api
- Lombok
데이터베이스 접속정보
- host : localhost
- port : 3306
- database : mytest
- username : github
- password : test1234
수정해서 사용하시면 됩니다.
서버 접속 정보
http://localhost:8080/swagger-ui.html
API 목록
- POST /api/v1/auth/login/google
구글 로그인 - POST /api/v1/logout
로그아웃 - POST /api/v1/auth/refresh
토근 리프레시 - GET /api/v1/myProfile
나의 프로필
테이블 스키마
CREATE TABLE `user` (
`userNo` bigint(20) NOT NULL AUTO_INCREMENT,
`oauthType` varchar(50) DEFAULT NULL COMMENT 'ProviderType\n구글, 페이스북 등등',
`oauthId` varchar(2555) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`imageUrl` varchar(255) DEFAULT NULL,
`role` varchar(255) DEFAULT NULL,
PRIMARY KEY (`userNo`)
);
CREATE TABLE `userRefreshToken` (
`refreshTokenSeq` bigint(20) NOT NULL AUTO_INCREMENT,
`userNo` bigint(20) NOT NULL,
`refreshToken` varchar(300) DEFAULT NULL,
`refreshTokenExpires` datetime DEFAULT NULL,
`accessToken` varchar(300) DEFAULT NULL,
`accessTokenExpires` datetime DEFAULT NULL,
`createdDate` datetime DEFAULT NULL,
`updatedDate` datetime DEFAULT NULL,
PRIMARY KEY (`refreshTokenSeq`),
KEY `idx1` (`userNo`,`accessToken`),
KEY `idx2` (`userNo`,`refreshToken`)
);
CREATE TABLE `book` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`bookname` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`)
);
사용하기 전에 확인할 부분
기본적으로 사용가능하지만
- application-oauth.yml
- jwt.secret
- jwt secret key 설정
- app.auth 하위설정
- 토큰 관련 설정
- 토큰 시간 지정
- application-datasource.yml
- 데이터베이스 접속설정
- spring.datasource 하위설정
- url, username, password, hikari.maximum-pool-size
주요 코드
AuthToken 클래스
JWT 라이브러리를 사용해 토큰을 관리하는 AuthToken 클래스를 알아보자.
AuthToken 클래스는 JWT 라이브러리에 생성된 토큰을 쉽게 사용하도록 도와준다.
JWT API로 토큰을 생성할때 인증에 필요한 정보를 함께 암호시킨다.
전송되는데이터(Payload)에 앞에서 언급한 값이 세팅하게 되는데, 이때 key-value형식의 데이터 조각을 Claim라 부른다. 샘플에서는 사용자식별자(userNo)와 역할이 포함되어 있다.
{
"sub" : "321",
"role" : "ROLE_USER"
}
AuthTokenTest.java 파일에서 다양한 예제가 구현되어 있다.
API 공통 응답
API는 다음과 같은 형식으로 응답이 이루어진다.
public class ApiResponse<T> {
private final int code;
private final String message;
private final T data;...
}
오류 코드 정의
public enum ApiResponseCode {
...
INVALID_ACCESS_TOKEN(30001, "Invalid access token."),
INVALID_REFRESH_TOKEN(30002, "Invalid refresh token."),
NOT_EXPIRED_TOKEN_YET(30003, "Not expired token yet."),...
}
API 오류 처리
GlobalAPIExceptionAdvice 클래스가 예외처리를 담당한다.
Spring Security 설정
- RestAuthenticationEntryPoint.java
- 인증 실패(토큰 만료) 응답을 API 에러로 변경
- TokenAccessDeniedHandler.java
- 인가 실패(역할/권한없음) 응답 처리
- AuthTokenProvider.java
- 토큰 생성 역할
- AuthenticationFactory.java
- 토큰에서 사용자 정보 추출
- TokenAuthenticationFilter.java
- 토큰 유효성 확인
CORS 설정
교차 출처 리소스 공유(Cross-Origin Resource Sharing, CORS)는 추가 HTTP 헤더를 사용하여, 한 출처에서 실행 중인 웹 애플리케이션이 다른 출처의 선택한 자원에 접근할 수 있는 권한을 부여하도록 브라우저에 알려주는 체제이다. - MDN
브라우저에서는 기본적으로 같은 출처(Origin)의 자원만 요청을 보안의 이유로 허용하고 있다. 출처은 scheme, host, port 으로 구성되어 있다. 같은 출처는 3가지 요소가 같은 경우를 말한다.
http:// -> scheme
localhost -> host
:8080 -> port
예제는 편의상 모든 출처 허용으로 설정되어 있고 본인의 서비스에 맞게 설정하길 바란다.
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") .allowedOriginPatterns("*").allowedMethods("*").allowCredentials(true);
}
}
WEB 상세 설명
실행 방법
npm start
or yarn start
http://localhost:3000에서 확인 가능합니다.
환경설정
.env 파일
- REACT_APP_API_URL : API 서버 도메인 정의
Oauth 로그인 설정
각각 프로바이더에 맞게 설정하면된다.
구글
사용자 인증 정보를 사용하기 위해 등록을 해야한다.
- 구글 클라우드 접속
- 프로젝트 만들기
- 왼쪽 사용자 인증 정보 선택
- 오른쪽 CREATE CREDENTIALS 선택
- OAuth 클라이언트 ID 선택
- OAuth 동의화면 생성
(OAuth 동의화면이 이미 만들어졌다면 이 부분은 스킵해도 됩니다.)
6-1. 동의 화면 구성 선택
6-2. 외부 선택하고 만들기
6-3. 앱정보 입력 (필수값만 입력하면 됩니다.) - 웹 애플리케이션 선택
구현하고자 하는 유형을 선택해주시면 됩니다. 예시는 웹으로 되어 있기 때문에 웹 애플리케이션을 선택했습니다. - 웹 > 요청할 URL 추가
http://localhost:3000 리액트 프로젝트 추가 - 우측에 있는 클라이언트 ID를 사용하면됩니다.
리액트
- 라이브러리 설치
npm install react-google-login
- clientId 정의
const googleClientId = "구글 클라이언트 ID 설정은 readme 참고";
- 로그인 버튼 정의
<GoogleLogin
clientId={googleClientId}
responseType={"id_token"}
onSuccess={onSuccessGoogle}
onFailure={onFailureGoogle}/> - 성공/실패 함수 구현
const onSuccessGoogle = async(response) => {
alert('로그인에 성공했습니다.')
}
const onFailureGoogle = (error) => {
alert('로그인에 실패했습니다.')
}
주요 코드
로그인 페이지 Login.jsx
로그인 순서
- 프로바이더(예로 구글) OAuth 로그인
(OAuth 토큰을 받은 상태) - OAuth 로그인 성공시 해당 토큰으로 API서버에 로그인시도
const onSuccessGoogle = async(response) => {
구글 로그인 성공시 response.tokenId에서 토큰을 획득한다.
const loginRes = await login(response.tokenId, 'google')
if (loginRes.error) {
alert('로그인에 실패했습니다.')
} else {
window.location.replace('//' + window.location.host + params.redirect_uri)
}
}
해당 토큰으로 API에 로그인 요청한다. - 성공시 Access Token과 Refresh Token 저장
export const login = async (tokenId, type) => {
API에 로그인 성공시 AuthUtils.setToken 함수로 인증값(Access Token과 Refresh Token)을 저장한다.
return await axios.post('/api/v1/auth/login/' + type, {
accessToken : tokenId
}, {
}).then(response => {
AuthUtils.setToken(response.data.data)
return response
}).catch(error => {
console.log(error)
return error.response.data
})
} - redirect_uri로 이동, 없는경우 /로 이동
인증 유틸리티 AuthUtils.js
토큰 관리를 도와준다.
예제 코드로 편의상 localStorage에 저장했는데, 실제 운영환경에서는 Secure Cookie와 HTTP Only를 사용하여 저장하기를 바랍니다.
API 유틸리티 ApiUtils.js
API 요청 편하게 할 수 있도록 기본 설정이 되어 있다.
- 인증(Authorization) 전달
- 인증필요 API 요청시, 로그인 페이지로 이동
- 인증 만료시 토큰 리프레시후 재요청
예시>
apiIClient.get('/api/v1/myProfile').then(response => {
console.log(response)
}).catch(error => {
console.log('error', error)
})
axios 인터셉터
// 요청 인터셉터 추가
axios.interceptors.request.use(
(config) => {
// 요청을 보내기 전에 호출
return config;
},
(error) => {
// 오류 요청을 보내기전 호출
return Promise.reject(error);
});
// 응답 인터셉터 추가
axios.interceptors.response.use(
(response) => {
// 응답 보내기 전에 호출
return response;
},
(error) => {
// 오류 응답 보내기 전에 호출
return Promise.reject(error);
});
공통으로 처리될 부분은 interceptors를 정의한다. ex> 인증, 오류 처리
client.interceptors.response.use(
(response) => {
return response.data;
},
async (error) => {
if (!error.response) { // timeout
return Promise.reject(error);
}
if (error.response.status === 401) {
if (AuthUtils.getToken() && AuthUtils.getRefreshToken()) {
const refreshTokenRes = await refreshToken()
if (refreshTokenRes.error) {
// console.log('토큰 리프레시 실패')
} else {
AuthUtils.setToken(refreshTokenRes.data.data)
return client.request(error.config)
}
}
alert('로그인이 필요합니다.')
goLogin()
}
return Promise.reject(error);
}
);
- 공유 링크 만들기
- X
- 이메일
- 기타 앱
댓글
댓글 쓰기