Short answer
The SecurityContext
injected with the @Context
annotation allows you to access security related information. It has a request scope and hold details about the user who is authenticated.
For other types that can be injected with @Context
, check this answer.
Long answer
You must mind that REST authentication must be stateless. That is, you must not rely on sessions stored on server side, hence the user credentials must be sent in each request. In other words, each request must be authenticated/authorized. To understand this concept, this answer may be insightful.
See below how the SecurityContext
can be used in a couple of approaches to secure your application:
Basic authentication
The Basic authentication scheme described in the RFC 7617 with HTTPS is a common and efficent approach to secure a REST application:
2. The 'Basic' Authentication Scheme
The Basic authentication scheme is based on the model that the client
needs to authenticate itself with a user-id and a password for each
protection space ("realm"). [...] The server will service the request only if it can validate
the user-id and password for the protection space applying to the
requested resource.
[...]
To receive authorization, the client
obtains the user-id and password from the user,
constructs the user-pass by concatenating the user-id, a single
colon (:
) character, and the password,
encodes the user-pass into an octet sequence,
and obtains the basic-credentials by encoding this octet sequence
using Base64 into a sequence of US-ASCII
characters.
[...]
If the user agent wishes to send the user-id "Aladdin" and password
"open sesame", it would use the following header field:
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
[...]
You could use a ContainerRequestFilter
to extract the user credentials from the Authorization
request header, authenticate them against your authentication provider and then set a SecurityContext
with the username for the request. The AuthenticationService
implementation is up to you:
@Provider
@Priority(Priorities.AUTHENTICATION)
class AuthenticationFilter implements ContainerRequestFilter {
@Inject
private AuthenticationService authenticationService;
@Override
public void filter(ContainerRequestFilter requestContext) {
// Get the HTTP Authorization header from the request
String authorizationHeader =
requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
// Check if the HTTP Authorization header is present and formatted correctly
if (authorizationHeader == null || !authorizationHeader.startsWith("Basic ")) {
throw new NotAuthorizedException("Authorization header must be provided");
}
// Extract the Basic authentication token from the HTTP Authorization header
String token = authorizationHeader.substring("Basic".length()).trim();
// Decodes the token
String credentials = Base64.getDecoder().decode(token);
String[] split = decoded.split(":");
try {
// Authenticate the credentials against in your authentication provider
authenticationService.authenticate(split[0], split[1]);
} catch (Exception e) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
// Updates the security context for the request
SecurityContext securityContext = requestContext.getSecurityContext();
requestContext.setSecurityContext(
new CustomSecurityContext(split[0], securityContext.isSecure()));
}
}
A custom SecurityContext
implementation could be like:
public class CustomSecurityContext implements SecurityContext {
private final String username;
private final boolean secure;
public BasicSecurityContext(String username, boolean secure) {
this.username = username;
this.secure = secure;
}
@Override
public Principal getUserPrincipal() {
return new Principal() {
@Override
public String getName() {
return username;
}
};
}
@Override
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
}
@Override
public boolean isSecure() {
return secure;
}
@Override
public boolean isUserInRole(String role) {
return true;
}
}
Then the SecurityContext
can be injected in any resource class using the @Context
annotation:
@Path("/example")
public class MyResource {
@Context
private SecurityContext securityContext;
...
}
It also can be injected in a resource method parameter:
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response myResourceMethod(@PathParam("id") Long id,
@Context SecurityContext securityContext) {
...
}
And then get the Principal
from the SecurityContext
:
Principal principal = securityContext.getUserPrincipal();
String username = principal.getName();
Token based authentication
In an authentication scheme based on tokens, the token becomes a credential of the user.
Hard credentials such as username and password are exchanged for a token that must be sent in each request then the server can perform authentication/authorization. Tokens can be valid for a short amount of time, can be revoked, can carry scope details (what can be requested with the token), etc.
For more details, have a look at this answer. The SecurityContext
can be used in the same way as explained above.