tutorial - spring security validate token
Spring Security-Autenticación API basada en tokens y autenticación de usuario/contraseña (1)
Estoy tratando de crear una aplicación web que proporcionará principalmente una API REST utilizando Spring, y estoy tratando de configurar el lado de la seguridad.
Estoy intentando implementar este tipo de patrón: https://developers.google.com/accounts/docs/MobileApps (Google ha cambiado por completo esa página, por lo que ya no tiene sentido; consulte la página a la que me refería aquí: http://web.archive.org/web/20130822184827/https://developers.google.com/accounts/docs/MobileApps )
Esto es lo que necesito lograr:
- La aplicación web tiene formularios simples de inicio de sesión / registro que funcionan con la autenticación de usuario / contraseña de primavera normal (ya se ha hecho este tipo de cosas antes con dao / authenticationmanager / userdetailsservice, etc.)
- Los puntos finales de API REST que son sesiones sin estado y cada solicitud autenticada en base a un token proporcionado con la solicitud
(por ejemplo, inicios de sesión / registros de usuarios usando formularios normales, webapp proporciona una cookie segura con token que luego puede usarse en las siguientes solicitudes de la API)
Tuve una configuración de autenticación normal como a continuación:
@Override protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/mobile/app/sign-up").permitAll()
.antMatchers("/v1/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/loginprocess")
.failureUrl("/?loginFailure=true")
.permitAll();
}
Estaba pensando en agregar un filtro de preajuste, que busca el token en la solicitud y luego establece el contexto de seguridad (¿eso significaría que se omitiría la siguiente autenticación normal), sin embargo, más allá del usuario / contraseña normal que tengo no se hizo demasiado con la seguridad basada en tokens, pero en base a algunos otros ejemplos se me ocurrió lo siguiente:
Configuración de seguridad:
@Override protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.addFilter(restAuthenticationFilter())
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint()).and()
.antMatcher("/v1/**")
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/mobile/app/sign-up").permitAll()
.antMatchers("/v1/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/")
.loginProcessingUrl("/loginprocess")
.failureUrl("/?loginFailure=true")
.permitAll();
}
Mi filtro de descanso personalizado:
public class RestAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public RestAuthenticationFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
private final String HEADER_SECURITY_TOKEN = "X-Token";
private String token = "";
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
this.token = request.getHeader(HEADER_SECURITY_TOKEN);
//If we have already applied this filter - not sure how that would happen? - then just continue chain
if (request.getAttribute(FILTER_APPLIED) != null) {
chain.doFilter(request, response);
return;
}
//Now mark request as completing this filter
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//Attempt to authenticate
Authentication authResult;
authResult = attemptAuthentication(request, response);
if (authResult == null) {
unsuccessfulAuthentication(request, response, new LockedException("Forbidden"));
} else {
successfulAuthentication(request, response, chain, authResult);
}
}
/**
* Attempt to authenticate request - basically just pass over to another method to authenticate request headers
*/
@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
AbstractAuthenticationToken userAuthenticationToken = authUserByToken();
if(userAuthenticationToken == null) throw new AuthenticationServiceException(MessageFormat.format("Error | {0}", "Bad Token"));
return userAuthenticationToken;
}
/**
* authenticate the user based on token, mobile app secret & user agent
* @return
*/
private AbstractAuthenticationToken authUserByToken() {
AbstractAuthenticationToken authToken = null;
try {
// TODO - just return null - always fail auth just to test spring setup ok
return null;
} catch (Exception e) {
logger.error("Authenticate user by token error: ", e);
}
return authToken;
}
Lo anterior en realidad da como resultado un error en el inicio de la aplicación que dice: authenticationManager must be specified
¿Puede alguien decirme cómo hacer esto mejor? ¿Es un filtro pre_auth la mejor manera de hacerlo?
EDITAR
Escribí lo que encontré y cómo lo hice con Spring-security (incluido el código) implementando una implementación de token estándar (no OAuth)
Descripción general del problema y enfoque / solución
Implementando la solución con Spring-security
Espero que ayude a otros ...
Creo que el error que menciona es solo porque la clase base AbstractAuthenticationProcessingFilter
que está utilizando requiere un AuthenticationManager
. Si no va a utilizarlo, puede configurarlo en modo no operativo o simplemente implementar Filter
directamente. Si su Filter
puede autenticar la solicitud y configura el SecurityContext
, normalmente se omitirá el procesamiento en sentido descendente (depende de la implementación de los filtros descendentes, pero no veo nada raro en su aplicación, por lo que probablemente todos se comporten de esa manera )
Si yo fuera usted, podría considerar poner los puntos finales API en una cadena de filtros completamente separada (otro bean WebSecurityConfigurerAdapter
). Pero eso solo hace que las cosas sean más fáciles de leer, no necesariamente cruciales.
Es posible que encuentre (como se sugiere en los comentarios) que termina reinventando la rueda, pero no hay ningún problema al intentarlo, y probablemente obtendrá más información acerca de Spring and Security en el proceso.
ADICIÓN : el enfoque github es bastante interesante: los usuarios solo usan el token como contraseña en autenticación básica, y el servidor no necesita un filtro personalizado ( BasicAuthenticationFilter
está bien).