java - ejemplo - spring security rest token authentication
Seguridad de Spring con Oauth2 o autenticación HTTP básica para el mismo recurso (7)
Estoy intentando implementar una API con recursos que están protegidos por la autenticación Oauth2 O Http-Basic.
Cuando cargo el WebSecurityConfigurerAdapter que aplica primero la autenticación básica de http al recurso, no se acepta la autenticación del token Oauth2. Y viceversa.
Configuraciones de ejemplo: Esto aplica la autenticación básica de http a todos los recursos / user / **
@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private LoginApi loginApi;
@Autowired
public void setLoginApi(LoginApi loginApi) {
this.loginApi = loginApi;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new PortalUserAuthenticationProvider(loginApi));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/users/**").authenticated()
.and()
.httpBasic();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Esto se aplica a la protección de token en el recurso / user / **
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/users/**")
.and()
.authorizeRequests()
.antMatchers("/users/**").access("#oauth2.clientHasRole(''ROLE_CLIENT'') and #oauth2.hasScope(''read'')");
}
}
¿Estoy seguro de que hay un pedazo de código mágico que me falta y que le dice a Spring que intente ambos si el primero ha fallado?
Cualquier asistencia será muy apreciada.
¿Y por qué no hacerlo al revés? Simplemente omita el servidor de recursos si no hay un token adjunto, luego vuelva a la cadena de filtro de seguridad normal. Esto es, por cierto, en qué filtro de servidor de recursos se está deteniendo.
@Configuration
@EnableResourceServer
class ResourceServerConfig : ResourceServerConfigurerAdapter() {
@Throws(Exception::class)
override fun configure(resources: ResourceServerSecurityConfigurer) {
resources.resourceId("aaa")
}
/**
* Resources exposed via oauth. As we are providing also local user interface they are also accessible from within.
*/
@Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.requestMatcher(BearerAuthorizationHeaderMatcher())
.authorizeRequests()
.anyRequest()
.authenticated()
}
private class BearerAuthorizationHeaderMatcher : RequestMatcher {
override fun matches(request: HttpServletRequest): Boolean {
val auth = request.getHeader("Authorization")
return auth != null && auth.startsWith("Bearer")
}
}
}
Creo que no es posible tener ambas autenticaciones. Puede tener autenticación básica y autenticación auth2, pero para puntos finales distintos. De la forma en que lo hizo, la primera configuración superará la segunda, en este caso, se utilizará http basic.
Esto puede estar cerca de lo que estabas buscando:
@Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatcher(new OAuthRequestedMatcher())
.authorizeRequests()
.anyRequest().authenticated();
}
private static class OAuthRequestedMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
// Determine if the client request contained an OAuth Authorization
return (auth != null) && auth.startsWith("Bearer");
}
}
Lo único que esto no proporciona es una forma de "retroceder" si la autenticación no es exitosa.
Para mí, este enfoque tiene sentido. Si un usuario proporciona directamente la autenticación a la solicitud a través de la autenticación básica, entonces OAuth no es necesario. Si el Cliente es el que actúa, entonces necesitamos este filtro para intervenir y asegurarnos de que la solicitud esté autenticada correctamente.
La solución @ kca2ply proporcionada funciona muy bien. Noté que el navegador no emitía un desafío, así que ajusté un poco el código a lo siguiente:
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.anonymous().disable()
.requestMatcher(request -> {
String auth = request.getHeader(HttpHeaders.AUTHORIZATION);
return (auth != null && auth.startsWith("Basic"));
})
.antMatcher("/**")
.authorizeRequests().anyRequest().authenticated()
.and()
.httpBasic();
// @formatter:on
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
El uso de requestMatcher()
y antMatcher()
hizo que las cosas funcionaran perfectamente. Los navegadores y los clientes HTTP ahora desafiarán las credenciales básicas primero, si aún no se han proporcionado. Si no se proporcionan credenciales, cae en OAuth2.
Logré obtener este trabajo basado en las sugerencias de la respuesta de Michael Ressler, pero con algunos ajustes.
Mi objetivo era permitir tanto Basic Auth como Oauth en los mismos puntos finales de recursos, por ejemplo, / leafcase / 123. Estuve atrapado durante bastante tiempo debido a la ordenación de las cadenas de filtros (se puede inspeccionar en FilterChainProxy.filterChains); El orden predeterminado es el siguiente:
- El servidor de autenticación de Oauth (si está habilitado en el mismo proyecto) filterChains. orden predeterminado 0 (ver AuthorizationServerSecurityConfiguration)
- FilterChains del servidor de recursos de Oauth. orden predeterminado 3 (ver ResourceServerConfiguration). Tiene una lógica de comparación de solicitudes que coincide con cualquier otra cosa que no sean los puntos finales de autenticación de Oauth (por ejemplo, / oauth / token, / oauth / authorize, etc. Consulte ResourceServerConfiguration $ NotOauthRequestMatcher.matches ()).
- Las cadenas de filtro que corresponden a la configuración (HttpSecurity http) - orden predeterminado 100, consulte WebSecurityConfigurerAdapter.
Dado que las cadenas de cadenas de filtro del servidor de recursos son más altas que las de la cadena de filtros configurada por WebSecurityConfigurerAdapter, y la anterior coincide prácticamente con todos los puntos finales de recursos, entonces la lógica del servidor de recursos de Oauth siempre activa cualquier solicitud de puntos finales de recursos (incluso si la solicitud utiliza la Autorización: encabezado básico). El error que obtendrías es:
{
"error": "unauthorized",
"error_description": "Full authentication is required to access this resource"
}
Hice 2 cambios para conseguir este trabajo:
En primer lugar, ordene el WebSecurityConfigurerAdapter más alto que el servidor de recursos (el orden 2 es mayor que el orden 3).
@Configuration
@Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
En segundo lugar, deje que configure (HttpSecurity) use un RequestMatcher del cliente que solo coincida con "Autorización: Básica".
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.requestMatcher(new BasicRequestMatcher())
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(oAuth2AuthenticationEntryPoint())
.and()
// ... other stuff
}
...
private static class BasicRequestMatcher implements RequestMatcher {
@Override
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
return (auth != null && auth.startsWith("Basic"));
}
}
Como resultado, coincide y maneja una solicitud de recurso de Autenticación básica antes de que filterChain del servidor de recursos tenga la oportunidad de coincidir. También SOLO maneja la Solicitud de Autorización de Autorización: recurso básico, por lo tanto, cualquier solicitud con Autorización: Portador fracasará, y luego será manejada por la cadena de filtros del servidor de recursos (es decir, se activará el filtro de Oauth). Además, ocupa un lugar inferior al AuthenticationServer (en caso de que AuthenticationServer esté habilitado en el mismo proyecto), por lo que no impide que el conjunto de filtros de AuthenticaitonServer maneje la solicitud a / oauth / token, etc.
No puedo proporcionarle un ejemplo completo, pero aquí hay algunos consejos para cavar:
A grandes rasgos, spring auth es solo una combinación del filtro de solicitud que extrae los datos de autenticación de la solicitud (encabezados) y el administrador de autenticación que proporciona el objeto de autenticación para esa autenticación.
Por lo tanto, para obtener Basic y oauth en la misma URL, necesita 2 filtros instalados en la cadena de filtros BasicAuthenticationFilter y OAuth2AuthenticationProcessingFilter.
Creo que el problema es que ConfiguringAdapters es bueno para configuraciones más simples, ya que tienden a anularse entre sí. Así que como primer paso trata de moverte.
.httpBasic();
llame a ResourceServerConfiguration
Tenga en cuenta que también debe proporcionar 2 administradores de autenticación diferentes: uno para autenticación básica y otro para autenticación
Puede agregar un BasicAuthenticationFilter a la cadena de filtros de seguridad para obtener OAuth2 O la seguridad de autenticación básica en un recurso protegido. La configuración de ejemplo está abajo ...
@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManagerBean;
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
final String[] userEndpoints = {
"/v1/api/airline"
};
final String[] adminEndpoints = {
"/v1/api/jobs**"
};
http
.requestMatchers()
.antMatchers(userEndpoints)
.antMatchers(adminEndpoints)
.antMatchers("/secure/**")
.and()
.authorizeRequests()
.antMatchers("/secure/**").authenticated()
.antMatchers(userEndpoints).hasRole("USER")
.antMatchers(adminEndpoints).hasRole("ADMIN");
// @formatter:on
http.addFilterBefore(new BasicAuthenticationFilter(authenticationManagerBean),
UsernamePasswordAuthenticationFilter.class);
}
}