servlet - web service rest java eclipse
Cómo autenticar usuarios en Jersey (3)
Estoy escribiendo una aplicación RESTful en Java usando Jersey, y necesito autenticar usuarios. Sé que puedo especificar los roles en el recurso utilizando las anotaciones @RolesAllowed, pero no puedo entender cómo un usuario está asociado a un rol específico. El cliente envía nombre de usuario y contraseña de esta manera
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic(user, password);
Client client = ClientBuilder.newClient();
client.register(feature);
WebTarget target = client.target(baseUrl).path(urlString);
Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON);
Response response = invocationBuilder.get();
Suponiendo que algunos usuarios solo puedan usar algunos métodos, ¿cómo puedo distinguirlos cuando el cliente envía el nombre de usuario y la contraseña?
La clase HttpAuthenticationFeature proporciona capacidades de autenticación de cliente HttpBasic y Digest. La función funciona en uno de 4 modos;
BÁSICO: es una forma de autenticación preventiva, es decir, la información se envía siempre con cada solicitud HTTP. Este modo se debe combinar con el uso de SSL / TLS ya que la contraseña solo se envía con codificación BASE64.
BÁSICO NO PREVENTIVO: es una forma de autenticación no preventiva, es decir, la información de autenticación se agrega solo cuando el servidor rechaza la solicitud con el código de estado 401 y luego la solicitud se repite con la información de autenticación.
DIGEST: Http digest authentication. No requiere el uso de SSL / TLS.
UNIVERSAL: Combinación de autenticación básica y digestiva en modo no preventivo, es decir, en el caso de la respuesta 401, se utiliza una autenticación adecuada en función de la autenticación solicitada tal como se define en WWW-Authenticate HTTP header.
Para usar HttpAuthenticationFeature, construya una instancia y regístrese con el cliente. Por ejemplo;
1) Modo de autenticación básica
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("username", "password");
final Client client = ClientBuilder.newClient();
client.register(feature);
2) Autenticación básica: modo no premptivo
HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder()
.nonPreemptive()
.credentials("username", "password")
.build();
final Client client = ClientBuilder.newClient();
client.register(feature);
3) modo universal
//Universal builder having different credentials for different schemes
HttpAuthenticationFeature feature = HttpAuthenticationFeature.universalBuilder()
.credentialsForBasic("username1", "password1")
.credentials("username2", "password2").build();
final Client client = ClientBuilder.newClient();
client.register(feature);
Hay dos cosas que debemos abordar
- Autenticación: verificando si el usuario es realmente el que dice ser
- Autorización: si el usuario autenticado tiene el privilegio de acceder al método proporcionado
Para hacer autenticación y autorización, necesitamos un almacén de datos que almacene la siguiente asignación:
- Mapeo entre el usuario y su contraseña
- Mapeo entre roles y usuarios
- Mapeo entre roles y permisos
Aquí se requiere la primera asignación para la autenticación y las otras dos asignaciones se usan para la autorización.
Además, tenga en cuenta que necesitamos hacer autenticación y autorización para cada llamada API. Entonces haremos muchas operaciones de lectura.
Por lo tanto, generalmente se usa un servidor de directorio o servidor Ldap como Apache DS para almacenar estas asignaciones porque un servidor de directorio es un almacén de datos optimizado para lectura.
En una aplicación RESTful, generalmente se usa un filtro para extraer el nombre de usuario y la contraseña del encabezado de la solicitud, y hacer la autenticación con el servidor Ldap. Si la autenticación es exitosa, el siguiente paso es extraer los permisos del usuario del servidor Ldap al consultar las asignaciones de rol de usuario y permiso de rol. Si el usuario está autorizado, solo en ese caso el control fluye a la lógica de negocio API real.
Consulte esta respuesta para más detalles.
Sé que puedo especificar los roles en el recurso usando las anotaciones @RolesAllowed, pero no puedo entender cómo un usuario está asociado a un rol específico
La información de rol se almacena en la base de datos. Supongamos que tiene un User
que modela la tabla USUARIO y ROLES en la BD
class User {
String username;
List<String> roles;
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public List<String> getRoles() { return roles; }
public void setRoles(List<String> roles) { this.roles = roles; }
}
Tendría al User
dentro de un filtro Jersey. Aquí también es donde se autenticaría.
@Provider
@Priority(Priorities.AUTHENTICATION) // needs to happen before authorization
class AuthenticationFilter implements ContainerRequestFilter {
@Inject
private UserService userService; // this is your own service
@Override
public void filter(ContainerRequestFilter filter) {
// note, this is a lazy implementation of Basic auth.
// it doesn''t do ant error checking. Please see
// link at bottom for better imlementation
String authzHeader = filter.getHeaderString(HttpHeaders.AUTHORIZATION); // (1)
String decoded = Base64.decodeAsString(authzHeader);
String[] split = decoded.split(":");
User user = userService.getUser(split[0]); // (2)
if (user == null || !user.getPassword().equals(someHash(split[1])) { // (3)
throw new UnauthorizedException();
}
SecurityContext oldContext = filter.getSecurityContext(); // (4)
filter.setSecurityContext(new BasicSecurityConext(user, oldContext.isSecure()));
}
}
Lo que estás haciendo aquí es:
- Análisis del encabezado Autorización básica de autenticación
- Obtener el
User
con el nombre de usuario - Haciendo tu autenticación
- Establecer un nuevo
SecurityContext
.
El BasicSecurityContext
se muestra a continuación. Aquí es donde asociarás roles con el usuario.
static class BasicSecurityContext implements SecurityContext {
private final User user;
private final boolean secure;
public BasicSecurityContext(User user, boolean secure) {
this.user = user;
this.secure = secure;
}
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return user.getUsername();
}
};
}
@Override
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
}
@Override
public boolean isSecure() { return secure; }
@Override
public boolean isUserInRole(String role) {
return user.getRoles().contains(role);
}
}
Si miras en la parte inferior en isUserInRole
. Lo que sucederá es que Jersey agarrará la anotación @RolesAllowed
del método o clase de recurso, tomará los valores y luego los pasará a isUserInRole
. Si devuelve true
, entonces el usuario está autorizado. En pseudo-código
@RolesAllowed({"USER", "SUPER_USER"})
public Response get() {}
...
RolesAllowed annotation = resourceMethod.getAnnotation(RolesAllowed.class);
String roles = annotation.value();
SecurityContext context = getSecurityContext();
for (String role: roles) {
if (context.isUserInRole(role)) {
return;
}
}
throw new ForbiddenException();
Esto es solo un pseudocódigo, pero muestra cómo maneja Jersey la autorización, usando @RolesAllowed
, SecurityContext
y cómo implementa isUserInRole
.
Esta función de autorización no se activa automáticamente. Tienes que encenderlo tú mismo. Para hacerlo, simplemente registre RolesAllowedDynamicFeature
public JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(RolesAllowedDynamicFeature.class);
}
}
Una cosa a tener en cuenta aquí es que en todo lo anterior, estamos implementando nuestra autenticación básica y configuración del contexto de seguridad. No hay nada realmente malo con esto. Pero si está utilizando el mecanismo de autenticación del contenedor de servlets, Jersey tomará realmente la información de autenticación de HttpServletRequest
. El HttpServletRequest
tiene un método getUserPrincipal()
y un método isUserInRole
. Jersey los usará para delegar en SecurityContext
. Entonces, si usted es usuario de la autenticación del contenedor, entonces realmente no necesita implementar nada. Solo necesita registrar RolesAllowedDynamicFeature
Si desea utilizar el mecanismo de autenticación de su contenedor, debe consultar la documentación de su servidor. Después de haber configurado un reino con su servidor, necesitará configurar el web.xml
con la información de seguridad. Hay un ejemplo en el siguiente enlace. También debe encontrar esta información en los documentos de Java EE en la sección de seguridad web.
Ver también:
- Filtro e interceptores para aprender más sobre cómo trabajar con filtros.
- Seguridad para obtener un poco más de información sobre cómo trabajar con seguridad en Jersey.
- Una mejor implementación del filtro de autenticación básica