1. 개요
이 기사에서는 OAuth 2 새로 고침 토큰을 활용하여 OAuth 2 Security 애플리케이션에 "Remember Me"기능을 추가합니다.
이 기사는 AngularJS 클라이언트를 통해 액세스되는 Spring REST API를 보호하기 위해 OAuth 2를 사용하는 방법에 대한 시리즈의 연속입니다. 권한 부여 서버, 리소스 서버 및 프런트 엔드 클라이언트를 설정하려면 소개 문서를 참조하십시오 .
참고 :이 기사는 Spring OAuth 레거시 프로젝트를 사용하고 있습니다.
2. OAuth 2 액세스 토큰 및 새로 고침 토큰
먼저 OAuth 2 토큰과 토큰 사용 방법 에 대해 간단히 요약 해 보겠습니다 .
암호 부여 유형을 사용하는 첫 번째 인증 시도 에서 사용자는 유효한 사용자 이름과 암호, 클라이언트 ID 및 암호를 보내야합니다. 인증 요청이 성공하면 서버는 다음 형식의 응답을 다시 보냅니다.
{
"access_token": "2e17505e-1c34-4ea6-a901-40e49ba786fa",
"token_type": "bearer",
"refresh_token": "e5f19364-862d-4212-ad14-9d6275ab1a62",
"expires_in": 59,
"scope": "read write",
}
서버 응답에 액세스 토큰과 새로 고침 토큰이 모두 포함되어 있음을 알 수 있습니다. 액세스 토큰은 인증이 필요한 후속 API 호출에 사용되는 반면 새로 고침 토큰의 목적은 새 유효한 액세스 토큰을 얻 거나 이전 토큰을 취소하는 것입니다.
refresh_token 부여 유형을 사용하여 새 액세스 토큰을 받기 위해 사용자는 더 이상 자격 증명을 입력 할 필요가 없으며 클라이언트 ID, 암호 및 새로 고침 토큰 만 입력하면됩니다.
두 가지 유형의 토큰을 사용하는 목적은 사용자 Security을 강화하는 것입니다. 일반적으로 액세스 토큰은 유효 기간이 짧기 때문에 공격자가 액세스 토큰을 얻을 경우 사용 시간이 제한됩니다. 반면 새로 고침 토큰이 손상되면 클라이언트 ID와 시크릿도 필요하므로 쓸모가 없습니다.
새로 고침 토큰의 또 다른 이점은 사용자가 새 IP에서 로그인하는 것과 같은 비정상적인 동작을 표시하는 경우 액세스 토큰을 취소하고 다른 토큰을 다시 보내지 않을 수 있다는 것입니다.
3. 새로 고침 토큰을 사용한 Remember-Me 기능
사용자는 일반적으로 애플리케이션에 액세스 할 때마다 자격 증명을 입력 할 필요가 없기 때문에 세션을 보존 할 수있는 옵션이 있으면 유용합니다.
액세스 토큰의 유효 시간이 짧기 때문에 대신 새로 고침 토큰을 사용하여 새 액세스 토큰을 생성하고 액세스 토큰이 만료 될 때마다 사용자에게 자격 증명을 요청하지 않아도됩니다.
다음 섹션에서는이 기능을 구현하는 두 가지 방법에 대해 설명합니다.
- 먼저 401 상태 코드를 반환하는 사용자 요청을 가로 채서 액세스 토큰이 유효하지 않습니다. 이 경우 사용자가 "remember me"옵션을 선택한 경우 refresh_token 권한 부여 유형을 사용하여 새 액세스 토큰에 대한 요청을 자동으로 발행 한 다음 초기 요청을 다시 실행합니다.
- 둘째, 액세스 토큰을 사전에 새로 고칠 수 있습니다. 만료되기 몇 초 전에 토큰 새로 고침 요청을 보냅니다.
두 번째 옵션은 사용자의 요청이 지연되지 않는다는 장점이 있습니다.
4. 새로 고침 토큰 저장
Refresh Tokens에 대한 이전 기사에서 OAuth 서버에 대한 요청을 가로 채고 인증시 다시 전송 된 새로 고침 토큰을 추출하여 서버 측 쿠키에 저장 하는 CustomPostZuulFilter 를 추가했습니다 .
@Component
public class CustomPostZuulFilter extends ZuulFilter {
@Override
public Object run() {
//...
Cookie cookie = new Cookie("refreshToken", refreshToken);
cookie.setHttpOnly(true);
cookie.setPath(ctx.getRequest().getContextPath() + "/oauth/token");
cookie.setMaxAge(2592000); // 30 days
ctx.getResponse().addCookie(cookie);
//...
}
}
다음으로 loginData.remember 변수에 데이터 바인딩이있는 로그인 양식에 확인란을 추가해 보겠습니다 .
<input type="checkbox" ng-model="loginData.remember" id="remember"/>
<label for="remember">Remeber me</label>
이제 로그인 양식에 추가 확인란이 표시됩니다.
loginData의 가 포함 있도록 개체가 인증 요청과 함께 전송되는 기억 매개 변수를. 인증 요청이 전송되기 전에 매개 변수에 따라 remember 라는 쿠키를 설정합니다 .
function obtainAccessToken(params){
if (params.username != null){
if (params.remember != null){
$cookies.put("remember","yes");
}
else {
$cookies.remove("remember");
}
}
//...
}
결과적으로 우리는이 쿠키를 확인하여 사용자가 기억되기를 원하는지 여부에 따라 액세스 토큰 새로 고침을 시도해야하는지 여부를 결정합니다.
5. 401 응답을 가로 채서 토큰 새로 고침
401 응답으로 돌아 오는 요청을 가로 채기 위해 AngularJS 애플리케이션을 수정 하여 responseError 함수가 있는 인터셉터를 추가해 보겠습니다 .
app.factory('rememberMeInterceptor', ['$q', '$injector', '$httpParamSerializer',
function($q, $injector, $httpParamSerializer) {
var interceptor = {
responseError: function(response) {
if (response.status == 401){
// refresh access token
// make the backend call again and chain the request
return deferred.promise.then(function() {
return $http(response.config);
});
}
return $q.reject(response);
}
};
return interceptor;
}]);
우리의 함수는 상태가 401인지 확인합니다. 즉, 액세스 토큰이 유효하지 않은 경우 새 유효한 액세스 토큰을 얻기 위해 새로 고침 토큰을 사용하려고 시도합니다.
이것이 성공하면 함수는 401 오류가 발생한 초기 요청을 계속 재 시도합니다. 이것은 사용자에게 원활한 경험을 보장합니다.
액세스 토큰을 새로 고치는 프로세스를 자세히 살펴 보겠습니다. 먼저 필요한 변수를 초기화합니다.
var $http = $injector.get('$http');
var $cookies = $injector.get('$cookies');
var deferred = $q.defer();
var refreshData = {grant_type:"refresh_token"};
var req = {
method: 'POST',
url: "oauth/token",
headers: {"Content-type": "application/x-www-form-urlencoded; charset=utf-8"},
data: $httpParamSerializer(refreshData)
}
매개 변수 grant_type = refresh_token 과 함께 / oauth / token 엔드 포인트로 POST 요청을 보내는 데 사용할 req 변수를 볼 수 있습니다 .
다음으로 요청을 보내기 위해 삽입 한 $ http 모듈을 사용하겠습니다 . 요청이 성공 하면 새 액세스 토큰 값과 access_token 쿠키에 대한 새 값으로 새 인증 헤더를 설정합니다 . 요청이 실패하면 (새로 고침 토큰도 결국 만료되면 발생할 수 있음) 사용자는 로그인 페이지로 리디렉션됩니다.
$http(req).then(
function(data){
$http.defaults.headers.common.Authorization= 'Bearer ' + data.data.access_token;
var expireDate = new Date (new Date().getTime() + (1000 * data.data.expires_in));
$cookies.put("access_token", data.data.access_token, {'expires': expireDate});
window.location.href="index";
},function(){
console.log("error");
$cookies.remove("access_token");
window.location.href = "login";
}
);
새로 고침 토큰은 이전 기사에서 구현 한 CustomPreZuulFilter에 의해 요청에 추가됩니다 .
@Component
public class CustomPreZuulFilter extends ZuulFilter {
@Override
public Object run() {
//...
String refreshToken = extractRefreshToken(req);
if (refreshToken != null) {
Map<String, String[]> param = new HashMap<String, String[]>();
param.put("refresh_token", new String[] { refreshToken });
param.put("grant_type", new String[] { "refresh_token" });
ctx.setRequest(new CustomHttpServletRequest(req, param));
}
//...
}
}
인터셉터를 정의하는 것 외에도 $ httpProvider 에 등록해야합니다 .
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('rememberMeInterceptor');
}]);
6. 사전에 토큰 새로 고침
"remember-me"기능을 구현하는 또 다른 방법은 현재 토큰이 만료되기 전에 새 액세스 토큰을 요청하는 것입니다.
액세스 토큰을받을 때 JSON 응답에는 토큰이 유효한 시간 (초)을 지정 하는 expires_in 값이 포함 됩니다.
각 인증에 대해이 값을 쿠키에 저장해 보겠습니다.
$cookies.put("validity", data.data.expires_in);
그런 다음 새로 고침 요청을 보내려면 AngularJS $ timeout 서비스를 사용 하여 토큰이 만료되기 10 초 전에 새로 고침 호출을 예약 해 보겠습니다 .
if ($cookies.get("remember") == "yes"){
var validity = $cookies.get("validity");
if (validity >10) validity -= 10;
$timeout( function(){ $scope.refreshAccessToken(); }, validity * 1000);
}
7. 결론
이 예제에서 우리는 OAuth2 애플리케이션과 AngularJS 프런트 엔드로 “Remember Me”기능을 구현할 수있는 두 가지 방법을 살펴 보았습니다 .
예제의 전체 소스 코드는 GitHub 에서 찾을 수 있습니다 . URL / login_remember 에서 "기억하기"기능을 사용하여 로그인 페이지에 액세스 할 수 있습니다 .