Indagamos por internet y así brindarte la solución a tu duda, en caso de dificultades deja tu comentario y contestamos porque estamos para ayudarte.
Solución:
Cómo funciona la autenticación basada en tokens
En la autenticación basada en token, el cliente intercambia credenciales duras (como nombre de usuario y contraseña) para un dato llamado simbólico. Para cada solicitud, en lugar de enviar las credenciales físicas, el cliente enviará el token al servidor para realizar la autenticación y luego la autorización.
En pocas palabras, un esquema de autenticación basado en tokens sigue estos pasos:
- El cliente envía sus credenciales (nombre de usuario y contraseña) al servidor.
- El servidor autentica las credenciales y, si son válidas, genera un token para el usuario.
- El servidor almacena el token generado previamente en algún almacenamiento junto con el identificador de usuario y una fecha de vencimiento.
- El servidor envía el token generado al cliente.
- El cliente envía el token al servidor en cada solicitud.
- El servidor, en cada solicitud, extrae el token de la solicitud entrante. Con el token, el servidor busca los detalles del usuario para realizar la autenticación.
- Si el token es válido, el servidor acepta la solicitud.
- Si el token no es válido, el servidor rechaza la solicitud.
- Una vez realizada la autenticación, el servidor realiza la autorización.
- El servidor puede proporcionar un punto final para actualizar los tokens.
Nota: El paso 3 no es necesario si el servidor ha emitido un token firmado (como JWT, que le permite realizar apátrida autenticación).
Qué puede hacer con JAX-RS 2.0 (Jersey, RESTEasy y Apache CXF)
Esta solución utiliza solo la API JAX-RS 2.0, evitando cualquier solución específica del proveedor. Por lo tanto, debería funcionar con implementaciones JAX-RS 2.0, como Jersey, RESTEasy y Apache CXF.
Vale la pena mencionar que si está utilizando la autenticación basada en token, no está confiando en los mecanismos de seguridad de la aplicación web Java EE estándar que ofrece el contenedor de servlets y que se puede configurar a través de la aplicación web.xml
descriptor. Es una autenticación personalizada.
Autenticar a un usuario con su nombre de usuario y contraseña y emitir un token
Cree un método de recursos JAX-RS que reciba y valide las credenciales (nombre de usuario y contraseña) y emita un token para el usuario:
@Path("/authentication")
public class AuthenticationEndpoint
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response authenticateUser(@FormParam("username") String username,
@FormParam("password") String password)
try
// Authenticate the user using the credentials provided
authenticate(username, password);
// Issue a token for the user
String token = issueToken(username);
// Return the token on the response
return Response.ok(token).build();
catch (Exception e)
return Response.status(Response.Status.FORBIDDEN).build();
private void authenticate(String username, String password) throws Exception
// Authenticate against a database, LDAP, file or whatever
// Throw an Exception if the credentials are invalid
private String issueToken(String username)
// Issue a token (can be a random String persisted to a database or a JWT token)
// The issued token must be associated to a user
// Return the issued token
Si se lanza alguna excepción al validar las credenciales, una respuesta con el estado 403
(Prohibido) será devuelto.
Si las credenciales se validan con éxito, una respuesta con el estado 200
Se devolverá (OK) y el token emitido se enviará al cliente en la carga útil de respuesta. El cliente debe enviar el token al servidor en cada solicitud.
Al consumir application/x-www-form-urlencoded
, el cliente debe enviar las credenciales en el siguiente formato en la carga útil de la solicitud:
username=admin&password=123456
En lugar de parámetros de formulario, es posible envolver el nombre de usuario y la contraseña en una clase:
public class Credentials implements Serializable
private String username;
private String password;
// Getters and setters omitted
Y luego consumirlo como JSON:
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public Response authenticateUser(Credentials credentials)
String username = credentials.getUsername();
String password = credentials.getPassword();
// Authenticate the user, issue a token and return a response
Con este enfoque, el cliente debe enviar las credenciales en el siguiente formato en la carga útil de la solicitud:
"username": "admin",
"password": "123456"
Extraer el token de la solicitud y validarlo
El cliente debe enviar el token en el HTTP estándar. Authorization
encabezado de la solicitud. Por ejemplo:
Authorization: Bearer
El nombre del encabezado HTTP estándar es desafortunado porque lleva autenticación información, no autorización. Sin embargo, es el encabezado HTTP estándar para enviar credenciales al servidor.
JAX-RS proporciona @NameBinding
, una metaanotación utilizada para crear otras anotaciones para vincular filtros e interceptores a clases de recursos y métodos. Definir un @Secured
anotación de la siguiente manera:
@NameBinding
@Retention(RUNTIME)
@Target(TYPE, METHOD)
public @interface Secured
La anotación de enlace de nombre definida anteriormente se utilizará para decorar una clase de filtro, que implementa ContainerRequestFilter
, lo que le permite interceptar la solicitud antes de que sea manejada por un método de recurso. los ContainerRequestContext
se puede usar para acceder a los encabezados de solicitud HTTP y luego extraer el token:
@Secured
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter
private static final String REALM = "example";
private static final String AUTHENTICATION_SCHEME = "Bearer";
@Override
public void filter(ContainerRequestContext requestContext) throws IOException
// Get the Authorization header from the request
String authorizationHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Validate the Authorization header
if (!isTokenBasedAuthentication(authorizationHeader))
abortWithUnauthorized(requestContext);
return;
// Extract the token from the Authorization header
String token = authorizationHeader
.substring(AUTHENTICATION_SCHEME.length()).trim();
try
// Validate the token
validateToken(token);
catch (Exception e)
abortWithUnauthorized(requestContext);
private boolean isTokenBasedAuthentication(String authorizationHeader)
// Check if the Authorization header is valid
// It must not be null and must be prefixed with "Bearer" plus a whitespace
// The authentication scheme comparison must be case-insensitive
return authorizationHeader != null && authorizationHeader.toLowerCase()
.startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
private void abortWithUnauthorized(ContainerRequestContext requestContext)
// Abort the filter chain with a 401 status code response
// The WWW-Authenticate header is sent along with the response
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED)
.header(HttpHeaders.WWW_AUTHENTICATE,
AUTHENTICATION_SCHEME + " realm="" + REALM + """)
.build());
private void validateToken(String token) throws Exception
// Check if the token was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
Si ocurre algún problema durante la validación del token, una respuesta con el estado 401
(No autorizado) será devuelto. De lo contrario, la solicitud procederá a un método de recurso.
Asegurar sus puntos finales REST
Para vincular el filtro de autenticación a métodos de recursos o clases de recursos, anótelos con el @Secured
anotación creada arriba. Para los métodos y / o clases que se anotan, se ejecutará el filtro. Significa que tales puntos finales solamente se alcanzará si la solicitud se realiza con un token válido.
Si algunos métodos o clases no necesitan autenticación, simplemente no los anote:
@Path("/example")
public class ExampleResource
@GET
@Path("id")
@Produces(MediaType.APPLICATION_JSON)
public Response myUnsecuredMethod(@PathParam("id") Long id)
// This method is not annotated with @Secured
// The authentication filter won't be executed before invoking this method
...
@DELETE
@Secured
@Path("id")
@Produces(MediaType.APPLICATION_JSON)
public Response mySecuredMethod(@PathParam("id") Long id)
// This method is annotated with @Secured
// The authentication filter will be executed before invoking this method
// The HTTP request must be performed with a valid token
...
En el ejemplo que se muestra arriba, el filtro se ejecutará solamente Para el mySecuredMethod(Long)
método porque está anotado con @Secured
.
Identificación del usuario actual
Es muy probable que necesite conocer al usuario que está realizando la solicitud contra su API REST. Se pueden utilizar los siguientes enfoques para lograrlo:
Anulando el contexto de seguridad de la solicitud actual
Dentro de tu ContainerRequestFilter.filter(ContainerRequestContext)
método, un nuevo SecurityContext
La instancia se puede configurar para la solicitud actual. Luego anule el SecurityContext.getUserPrincipal()
, devolviendo un Principal
ejemplo:
final SecurityContext currentSecurityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(new SecurityContext()
@Override
public Principal getUserPrincipal()
return () -> username;
@Override
public boolean isUserInRole(String role)
return true;
@Override
public boolean isSecure()
return currentSecurityContext.isSecure();
@Override
public String getAuthenticationScheme()
return AUTHENTICATION_SCHEME;
);
Utilice el token para buscar el identificador de usuario (nombre de usuario), que será el Principal
‘nombre de.
Inyectar el SecurityContext
en cualquier clase de recurso JAX-RS:
@Context
SecurityContext securityContext;
Lo mismo se puede hacer en un método de recursos JAX-RS:
@GET
@Secured
@Path("id")
@Produces(MediaType.APPLICATION_JSON)
public Response myMethod(@PathParam("id") Long id,
@Context SecurityContext securityContext)
...
Y luego obtén el Principal
:
Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();
Uso de CDI (inyección de contexto y dependencia)
Si, por alguna razón, no desea anular la SecurityContext
, puede utilizar CDI (inyección de contexto y dependencia), que proporciona funciones útiles como eventos y productores.
Cree un calificador CDI:
@Qualifier
@Retention(RUNTIME)
@Target( METHOD, FIELD, PARAMETER )
public @interface AuthenticatedUser
En tus AuthenticationFilter
creado arriba, inyecte un Event
anotado con @AuthenticatedUser
:
@Inject
@AuthenticatedUser
Event userAuthenticatedEvent;
Si la autenticación tiene éxito, active el evento pasando el nombre de usuario como parámetro (recuerde, el token se emite para un usuario y el token se utilizará para buscar el identificador de usuario):
userAuthenticatedEvent.fire(username);
Es muy probable que haya una clase que represente a un usuario en su aplicación. Llamemos a esta clase User
.
Cree un bean CDI para manejar el evento de autenticación, busque un User
instancia con el nombre de usuario correspondiente y asígnelo al authenticatedUser
campo productor:
@RequestScoped
public class AuthenticatedUserProducer
@Produces
@RequestScoped
@AuthenticatedUser
private User authenticatedUser;
public void handleAuthenticationEvent(@Observes @AuthenticatedUser String username)
this.authenticatedUser = findUser(username);
private User findUser(String username)
// Hit the the database or a service to find a user by its username and return it
// Return the User instance
los authenticatedUser
campo produce un User
instancia que se puede inyectar en beans gestionados por contenedor, como servicios JAX-RS, beans CDI, servlets y EJB. Utilice el siguiente fragmento de código para inyectar un User
instancia (de hecho, es un proxy CDI):
@Inject
@AuthenticatedUser
User authenticatedUser;
Tenga en cuenta que el CDI @Produces
la anotación es diferente desde el JAX-RS @Produces
anotación:
- CDI:
javax.enterprise.inject.Produces
- JAX-RS:
javax.ws.rs.Produces
Asegúrese de usar el CDI @Produces
anotación en tu AuthenticatedUserProducer
frijol.
La clave aquí es el frijol anotado con @RequestScoped
, lo que le permite compartir datos entre los filtros y sus beans. Si no desea utilizar eventos, puede modificar el filtro para almacenar el usuario autenticado en un bean con alcance de solicitud y luego leerlo de sus clases de recursos JAX-RS.
En comparación con el enfoque que anula el SecurityContext
, el enfoque CDI le permite obtener el usuario autenticado de beans distintos de los recursos y proveedores JAX-RS.
Apoyo a la autorización basada en roles
Consulte mi otra respuesta para obtener detalles sobre cómo admitir la autorización basada en roles.
Emitir tokens
Un token puede ser:
- Opaco: No revela más detalles que el valor en sí (como una cadena aleatoria)
- Autónomo: Contiene detalles sobre el token en sí (como JWT).
Vea los detalles abajo:
Cadena aleatoria como token
Se puede emitir un token generando una cadena aleatoria y manteniéndola en una base de datos junto con el identificador de usuario y una fecha de vencimiento. Aquí se puede ver un buen ejemplo de cómo generar una cadena aleatoria en Java. También puedes usar:
Random random = new SecureRandom();
String token = new BigInteger(130, random).toString(32);
JWT (token web JSON)
JWT (JSON Web Token) es un método estándar para representar reclamaciones de forma segura entre dos partes y está definido por el RFC 7519.
Es un token autónomo y le permite almacenar detalles en reclamación (es. Estas afirmaciones se almacenan en la carga útil del token, que es un JSON codificado como Base64. Aquí hay algunas afirmaciones registradas en el RFC 7519 y lo que significan (lea el RFC completo para obtener más detalles):
iss
: Principal que emitió el token.sub
: Principal que es el tema del JWT.exp
: Fecha de vencimiento del token.nbf
: Hora en la que el token comenzará a ser aceptado para su procesamiento.iat
: Hora a la que se emitió el token.jti
: Identificador único del token.
Tenga en cuenta que no debe almacenar datos confidenciales, como contraseñas, en el token.
El cliente puede leer la carga útil y la integridad del token puede comprobarse fácilmente verificando su firma en el servidor. La firma es lo que evita que se manipule el token.
No necesitará conservar los tokens JWT si no necesita rastrearlos. No obstante, al persistir los tokens, tendrás la posibilidad de invalidar y revocar el acceso a los mismos. Para realizar un seguimiento de los tokens JWT, en lugar de conservar el token completo en el servidor, puede conservar el identificador del token (jti
reclamo) junto con algunos otros detalles, como el usuario para el que emitió el token, la fecha de vencimiento, etc.
Al conservar los tokens, siempre considere eliminar los antiguos para evitar que su base de datos crezca indefinidamente.
Usando JWT
Hay algunas bibliotecas de Java para emitir y validar tokens JWT como:
- jjwt
- java-jwt
- jose4j
Para encontrar otros recursos excelentes para trabajar con JWT, eche un vistazo a http://jwt.io.
Manejo de la revocación de tokens con JWT
Si desea revocar tokens, debe realizar un seguimiento de ellos. No necesita almacenar el token completo en el lado del servidor, almacene solo el identificador del token (que debe ser único) y algunos metadatos si lo necesita. Para el identificador de token, puede usar UUID.
los jti
reclamo debe usarse para almacenar el identificador de token en el simbólico. Al validar el token, asegúrese de que no haya sido revocado comprobando el valor del jti
reclamar contra los identificadores de token que tiene en el lado del servidor.
Por motivos de seguridad, revoque todos los tokens de un usuario cuando cambie su contraseña.
Información adicional
- No importa qué tipo de autenticación decida utilizar. Siempre hágalo en la parte superior de una conexión HTTPS para evitar el ataque man-in-the-middle.
- Eche un vistazo a esta pregunta de Seguridad de la información para obtener más información sobre los tokens.
- En este artículo encontrará información útil sobre la autenticación basada en token.
Esta respuesta tiene que ver con autorización y es un complemento de mi respuesta anterior sobre autenticación
Por qué otro ¿respuesta? Intenté expandir mi respuesta anterior agregando detalles sobre cómo admitir anotaciones JSR-250. Sin embargo, la respuesta original se convirtió en el camino demasiado largo y superó la longitud máxima de 30.000 caracteres. Así que moví todos los detalles de la autorización a esta respuesta, manteniendo la otra respuesta enfocada en realizar la autenticación y emitir tokens.
Apoyar la autorización basada en roles con el @Secured
anotación
Además del flujo de autenticación que se muestra en la otra respuesta, la autorización basada en roles se puede admitir en los puntos finales REST.
Crea una enumeración y define los roles según tus necesidades:
public enum Role
ROLE_1,
ROLE_2,
ROLE_3
Cambiar el @Secured
anotación de enlace de nombre creada antes para admitir roles:
@NameBinding
@Retention(RUNTIME)
@Target(TYPE, METHOD)
public @interface Secured
Role[] value() default ;
Y luego anote las clases de recursos y métodos con @Secured
para realizar la autorización. Las anotaciones del método anularán las anotaciones de la clase:
@Path("/example")
@Secured(Role.ROLE_1)
public class ExampleResource
@GET
@Path("id")
@Produces(MediaType.APPLICATION_JSON)
public Response myMethod(@PathParam("id") Long id)
// This method is not annotated with @Secured
// But it's declared within a class annotated with @Secured(Role.ROLE_1)
// So it only can be executed by the users who have the ROLE_1 role
...
@DELETE
@Path("id")
@Produces(MediaType.APPLICATION_JSON)
@Secured(Role.ROLE_1, Role.ROLE_2)
public Response myOtherMethod(@PathParam("id") Long id)
// This method is annotated with @Secured(Role.ROLE_1, Role.ROLE_2)
// The method annotation overrides the class annotation
// So it only can be executed by the users who have the ROLE_1 or ROLE_2 roles
...
Cree un filtro con el AUTHORIZATION
prioridad, que se ejecuta después de la AUTHENTICATION
filtro de prioridad definido previamente.
los ResourceInfo
se puede utilizar para obtener el recurso Method
y recurso Class
que manejará la solicitud y luego extraerá el @Secured
anotaciones de ellos:
@Secured
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException
// Get the resource class which matches with the requested URL
// Extract the roles declared by it
Class> resourceClass = resourceInfo.getResourceClass();
List classRoles = extractRoles(resourceClass);
// Get the resource method which matches with the requested URL
// Extract the roles declared by it
Method resourceMethod = resourceInfo.getResourceMethod();
List methodRoles = extractRoles(resourceMethod);
try
// Check if the user is allowed to execute the method
// The method annotations override the class annotations
if (methodRoles.isEmpty())
checkPermissions(classRoles);
else
checkPermissions(methodRoles);
catch (Exception e)
requestContext.abortWith(
Response.status(Response.Status.FORBIDDEN).build());
// Extract the roles from the annotated element
private List extractRoles(AnnotatedElement annotatedElement)
if (annotatedElement == null)
return new ArrayList();
else
Secured secured = annotatedElement.getAnnotation(Secured.class);
if (secured == null)
return new ArrayList();
else
Role[] allowedRoles = secured.value();
return Arrays.asList(allowedRoles);
private void checkPermissions(List allowedRoles) throws Exception
// Check if the user contains one of the allowed roles
// Throw an Exception if the user has not permission to execute the method
Si el usuario no tiene permiso para ejecutar la operación, la solicitud se cancela con un 403
(Prohibido).
Para conocer al usuario que está realizando la solicitud, consulte mi respuesta anterior. Puede obtenerlo del SecurityContext
(que ya debería estar configurado en el ContainerRequestContext
) o inyéctelo usando CDI, dependiendo del enfoque que elija.
Si un @Secured
La anotación no tiene roles declarados, puede asumir que todos los usuarios autenticados pueden acceder a ese punto final, sin tener en cuenta los roles que tienen los usuarios.
Admite la autorización basada en roles con anotaciones JSR-250
Alternativamente a definir los roles en el @Secured
anotación como se muestra arriba, podría considerar anotaciones JSR-250 como @RolesAllowed
, @PermitAll
y @DenyAll
.
JAX-RS no admite este tipo de anotaciones listas para usar, pero se podría lograr con un filtro. Aquí hay algunas consideraciones que debe tener en cuenta si desea admitirlas todas:
@DenyAll
en el método tiene prioridad sobre@RolesAllowed
y@PermitAll
en la clase.@RolesAllowed
en el método tiene prioridad sobre@PermitAll
en la clase.@PermitAll
en el método tiene prioridad sobre@RolesAllowed
en la clase.@DenyAll
no se puede adjuntar a clases.@RolesAllowed
en la clase tiene prioridad sobre@PermitAll
en la clase.
Entonces, un filtro de autorización que verifica las anotaciones JSR-250 podría ser como:
@Provider
@Priority(Priorities.AUTHORIZATION)
public class AuthorizationFilter implements ContainerRequestFilter
@Context
private ResourceInfo resourceInfo;
@Override
public void filter(ContainerRequestContext requestContext) throws IOException
Method method = resourceInfo.getResourceMethod();
// @DenyAll on the method takes precedence over @RolesAllowed and @PermitAll
if (method.isAnnotationPresent(DenyAll.class))
refuseRequest();
// @RolesAllowed on the method takes precedence over @PermitAll
RolesAllowed rolesAllowed = method.getAnnotation(RolesAllowed.class);
if (rolesAllowed != null)
performAuthorization(rolesAllowed.value(), requestContext);
return;
// @PermitAll on the method takes precedence over @RolesAllowed on the class
if (method.isAnnotationPresent(PermitAll.class))
// Do nothing
return;
// @DenyAll can't be attached to classes
// @RolesAllowed on the class takes precedence over @PermitAll on the class
rolesAllowed =
resourceInfo.getResourceClass().getAnnotation(RolesAllowed.class);
if (rolesAllowed != null)
performAuthorization(rolesAllowed.value(), requestContext);
// @PermitAll on the class
if (resourceInfo.getResourceClass().isAnnotationPresent(PermitAll.class))
// Do nothing
return;
// Authentication is required for non-annotated methods
if (!isAuthenticated(requestContext))
refuseRequest();
/**
* Perform authorization based on roles.
*
* @param rolesAllowed
* @param requestContext
*/
private void performAuthorization(String[] rolesAllowed,
ContainerRequestContext requestContext)
if (rolesAllowed.length > 0 && !isAuthenticated(requestContext))
refuseRequest();
for (final String role : rolesAllowed)
if (requestContext.getSecurityContext().isUserInRole(role))
return;
refuseRequest();
/**
* Check if the user is authenticated.
*
* @param requestContext
* @return
*/
private boolean isAuthenticated(final ContainerRequestContext requestContext)
// Return true if the user is authenticated or false otherwise
// An implementation could be like:
// return requestContext.getSecurityContext().getUserPrincipal() != null;
/**
* Refuse the request.
*/
private void refuseRequest()
throw new AccessDeniedException(
"You don't have permissions to perform this action.");
Nota: La implementación anterior se basa en Jersey RolesAllowedDynamicFeature
. Si usa Jersey, no necesita escribir su propio filtro, solo use la implementación existente.
Sección de Reseñas y Valoraciones
Si piensas que ha resultado de ayuda este post, te agradeceríamos que lo compartas con otros programadores de esta manera nos ayudas a extender nuestro contenido.