OAuth2 para uma API REST do Spring - manipule o token de atualização no AngularJS
1. Visão geral
Neste tutorial, continuaremos explorando o fluxo de senha OAuth que começamos a reunir em maisour previous articlee nos concentraremos em como lidar com o token de atualização em um aplicativo AngularJS.
2. Expiração de token de acesso
Primeiro, lembre-se de que o cliente estava obtendo um token de acesso quando o usuário estava efetuando login no aplicativo:
function obtainAccessToken(params) {
var req = {
method: 'POST',
url: "oauth/token",
headers: {"Content-type": "application/x-www-form-urlencoded; charset=utf-8"},
data: $httpParamSerializer(params)
}
$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");
window.location.href = "login";
});
}
Observe como nosso token de acesso é armazenado em um cookie que expira com base em quando o próprio token expira.
O que é importante entender é quethe cookie itself is only used for storagee não direciona mais nada no fluxo de OAuth. Por exemplo, o navegador nunca envia automaticamente o cookie ao servidor com solicitações.
Observe também como realmente chamamos esta funçãoobtainAccessToken():
$scope.loginData = {
grant_type:"password",
username: "",
password: "",
client_id: "fooClientIdPassword"
};
$scope.login = function() {
obtainAccessToken($scope.loginData);
}
3. O Proxy
Agora teremos um proxy Zuul em execução no aplicativo front-end e basicamente entre o cliente front-end e o servidor de autorização.
Vamos configurar as rotas do proxy:
zuul:
routes:
oauth:
path: /oauth/**
url: http://localhost:8081/spring-security-oauth-server/oauth
O que é interessante aqui é que estamos apenas fazendo proxy do tráfego para o servidor de autorização e nada mais. Nós realmente precisamos que o proxy entre quando o cliente estiver obtendo novos tokens.
Se você quiser repassar o básico do Zuul, faça uma leitura rápida dethe main Zuul article.
4. Um filtro Zuul que faz autenticação básica
O primeiro uso do proxy é simples - em vez de revelar nosso aplicativo “client secret” em javascript, usaremos um pré-filtro Zuul para adicionar o cabeçalho de autorização para acessar solicitações de token:
@Component
public class CustomPreZuulFilter extends ZuulFilter {
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if (ctx.getRequest().getRequestURI().contains("oauth/token")) {
byte[] encoded;
try {
encoded = Base64.encode("fooClientIdPassword:secret".getBytes("UTF-8"));
ctx.addZuulRequestHeader("Authorization", "Basic " + new String(encoded));
} catch (UnsupportedEncodingException e) {
logger.error("Error occured in pre filter", e);
}
}
return null;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public int filterOrder() {
return -2;
}
@Override
public String filterType() {
return "pre";
}
}
Agora, lembre-se de que isso não adiciona nenhuma segurança extra e a única razão de estarmos fazendo isso é porque o ponto de extremidade do token é protegido com autenticação básica usando credenciais de cliente.
Do ponto de vista da implementação, vale a pena notar o tipo de filtro. Estamos usando um tipo de filtro "pré" para processar a solicitação antes de encaminhá-la.
5. Coloque o token de atualização em um cookie
Para as coisas divertidas.
O que estamos planejando fazer aqui é que o cliente obtenha o token de atualização como um cookie. Não apenas um cookie normal, mas um cookie seguro, somente HTTP, com um caminho muito limitado (/oauth/token).
Vamos configurar um pós-filtro Zuul para extrair o token de atualização do corpo JSON da resposta e defini-lo no cookie:
@Component
public class CustomPostZuulFilter extends ZuulFilter {
private ObjectMapper mapper = new ObjectMapper();
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
try {
InputStream is = ctx.getResponseDataStream();
String responseBody = IOUtils.toString(is, "UTF-8");
if (responseBody.contains("refresh_token")) {
Map responseMap = mapper.readValue(
responseBody, new TypeReference
Algumas coisas interessantes para entender aqui:
-
Usamos um pós-filtro Zuul para ler a resposta eextract refresh token
-
Removemos o valor derefresh_token da resposta JSON para garantir que nunca fique acessível para o front end fora do cookie
-
Definimos a idade máxima do cookie para30 days - uma vez que corresponde ao tempo de expiração do token
6. Obtenha e use o token de atualização do cookie
Agora que temos o token de atualização no cookie, quando o aplicativo AngularJS de front-end tentar acionar uma atualização de token, ele enviará a solicitação em/oauth/tokene o navegador, é claro, enviará esse cookie.
Portanto, agora teremos outro filtro no proxy que extrairá o token de atualização do cookie e o enviará como um parâmetro HTTP - para que a solicitação seja válida:
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
...
HttpServletRequest req = ctx.getRequest();
String refreshToken = extractRefreshToken(req);
if (refreshToken != null) {
Map param = new HashMap();
param.put("refresh_token", new String[] { refreshToken });
param.put("grant_type", new String[] { "refresh_token" });
ctx.setRequest(new CustomHttpServletRequest(req, param));
}
...
}
private String extractRefreshToken(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if (cookies != null) {
for (int i = 0; i < cookies.length; i++) {
if (cookies[i].getName().equalsIgnoreCase("refreshToken")) {
return cookies[i].getValue();
}
}
}
return null;
}
E aqui está nossoCustomHttpServletRequest - usado parainject our refresh token parameters:
public class CustomHttpServletRequest extends HttpServletRequestWrapper {
private Map additionalParams;
private HttpServletRequest request;
public CustomHttpServletRequest(
HttpServletRequest request, Map additionalParams) {
super(request);
this.request = request;
this.additionalParams = additionalParams;
}
@Override
public Map getParameterMap() {
Map map = request.getParameterMap();
Map param = new HashMap();
param.putAll(map);
param.putAll(additionalParams);
return param;
}
}
Novamente, muitas notas importantes de implementação aqui:
-
O proxy está extraindo o token de atualização do cookie
-
Em seguida, ele o define no parâmetrorefresh_token
-
Também está definindogrant_type pararefresh_token
-
Se não houver cookierefreshToken (expirado ou primeiro login) - então a solicitação do token de acesso será redirecionada sem alteração
7. Atualizando o token de acesso do AngularJS
Por fim, vamos modificar nosso aplicativo front-end simples e usar a atualização do token:
Aqui está nossa funçãorefreshAccessToken():
$scope.refreshAccessToken = function() {
obtainAccessToken($scope.refreshData);
}
E aqui nosso$scope.refreshData:
$scope.refreshData = {grant_type:"refresh_token"};
Observe como estamos simplesmente usando a funçãoobtainAccessToken existente - e apenas passando diferentes entradas para ela.
Observe também que não estamos adicionandorefresh_token nós mesmos - já que isso será tratado pelo filtro Zuul.
8. Conclusão
Neste tutorial do OAuth, aprendemos como armazenar o token de atualização em um aplicativo cliente AngularJS, como atualizar um token de acesso expirado e como aproveitar o proxy Zuul para tudo isso.
Ofull implementation deste tutorial pode ser encontrado emthe github project - este é um projeto baseado em Eclipse, portanto, deve ser fácil de importar e executar como está.