Je me rends compte que j'arrive un peu tard sur ce point, mais j'ai également réussi à faire fonctionner les clés API avec Spring Boot en tandem avec l'authentification par nom d'utilisateur/mot de passe. Je n'étais pas très enthousiaste à l'idée d'utiliser AbstractPreAuthenticatedProcessingFilter
car en lisant la JavaDoc, cela semblait être une mauvaise utilisation de cette classe particulière.
J'ai fini par créer un nouveau ApiKeyAuthenticationToken
ainsi qu'un filtre de servlet brut assez simple pour y parvenir :
import java.util.Collection;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.Transient;
@Transient
public class ApiKeyAuthenticationToken extends AbstractAuthenticationToken {
private String apiKey;
public ApiKeyAuthenticationToken(String apiKey, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.apiKey = apiKey;
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return apiKey;
}
}
Et le filtre
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
public class ApiKeyAuthenticationFilter implements Filter {
static final private String AUTH_METHOD = "api-key";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
if(request instanceof HttpServletRequest && response instanceof HttpServletResponse) {
String apiKey = getApiKey((HttpServletRequest) request);
if(apiKey != null) {
if(apiKey.equals("my-valid-api-key")) {
ApiKeyAuthenticationToken apiToken = new ApiKeyAuthenticationToken(apiKey, AuthorityUtils.NO_AUTHORITIES);
SecurityContextHolder.getContext().setAuthentication(apiToken);
} else {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setStatus(401);
httpResponse.getWriter().write("Invalid API Key");
return;
}
}
}
chain.doFilter(request, response);
}
private String getApiKey(HttpServletRequest httpRequest) {
String apiKey = null;
String authHeader = httpRequest.getHeader("Authorization");
if(authHeader != null) {
authHeader = authHeader.trim();
if(authHeader.toLowerCase().startsWith(AUTH_METHOD + " ")) {
apiKey = authHeader.substring(AUTH_METHOD.length()).trim();
}
}
return apiKey;
}
}
Il ne reste plus qu'à injecter le filtre au bon endroit dans la chaîne. Dans mon cas, je voulais que l'authentification par clé API soit évaluée avant toute authentification par nom d'utilisateur ou mot de passe, afin d'authentifier la demande avant que l'application ne tente de rediriger vers une page de connexion :
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilterBefore(new ApiKeyAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.anyRequest()
.fullyAuthenticated()
.and()
.formLogin();
}
Une autre chose à laquelle vous devez faire attention est que vos requêtes authentifiées par une clé API ne créent pas et n'abandonnent pas un tas d'informations de type HttpSession
sur votre serveur.
1 votes
La seule différence est que vous l'appelez API key au lieu de username ou y a-t-il autre chose ?
0 votes
Est-ce que la réponse ci-dessous a résolu votre problème ? Comment avez-vous géré vos utilisateurs et la clé api pour chaque utilisateur ?