I have a Rest based service using a ContianerRequestFilter (AuthFilter below) to validate a user or their token. Everything at that level works fine as the user is authorized or not authorized as expected. The question is how to do get the user info in the resource layer? For instance if a user requests a list of areas in AreasResource (below), how can I get the user info and use that to constrain the results return to him/her?
AuthFilter:
#Provider
#PreMatching
public class AuthFilter implements ContainerRequestFilter
{
#Autowired
IAuthenticator authenticator;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException
{
//PUT, POST, GET, DELETE...
String method = requestContext.getMethod();
String path = requestContext.getUriInfo().getPath(true);
UserWrapper authenticationResult = null;
Date expireTime = new Date(new Date().getTime() + 60 * 1000);
if (!"init".equals(path))
{
if ("GET".equals(method) && ("application.wadl".equals(path) || "application.wadl/xsd0.xsd".equals(path)))
{
return;
}
String auth = requestContext.getHeaderString("authorization");
if(auth == null)
{
throw new WebApplicationException(Status.UNAUTHORIZED);
}
if (auth.startsWith("Bearer"))
{
String token = auth.substring("Bearer".length()).trim();
try
{
authenticationResult = validateToken(token);
}
catch (Exception e)
{
throw new WebApplicationException(Status.UNAUTHORIZED);
}
}
else
{
//lap: loginAndPassword
String[] lap = BasicAuth.decode(auth);
if (lap == null || lap.length != 2)
{
throw new WebApplicationException(Status.UNAUTHORIZED);
}
// Handle authentication validation here
authenticationResult = authenticator.authenticatUser(lap);
// if null then user can't be found or user name and password failed
if (authenticationResult == null)
{
throw new WebApplicationException(Status.UNAUTHORIZED);
}
}
}
else
{
authenticationResult = new UserWrapper(new User(), expireTime.getTime());
}
// We passed so we put the user in the security context here
String scheme = requestContext.getUriInfo().getRequestUri().getScheme();
requestContext.setSecurityContext(new ApplicationSecurityContext(authenticationResult, scheme));
}
private UserWrapper validateToken(String token) throws Exception
{
UserWrapper userWrapper = AuthenticatorCache.getInstance().getObj(token);
if (userWrapper == null)
{
throw new Exception("No session found");
}
return userWrapper;
}
}
Areas Resource:
#Path("/areas")
#Component
#Api(value = "/areas" )
public class AreasResource implements IAreas
{
#Override
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response listActiveAreas() {
return Response.ok('woo hoo it worked').build();
}
}
Overriding the SecurityContext
One possible way to achieve it is overriding the SecurityContext of the ContainerRequestContext in your ContainerRequestFilter implementation. It could be something as following:
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return new Principal() {
#Override
public String getName() {
return username;
}
};
}
#Override
public boolean isUserInRole(String role) {
return true;
}
#Override
public boolean isSecure() {
return false;
}
#Override
public String getAuthenticationScheme() {
return null;
}
});
Then the SecurityContext can be injected in any resource class using the #Context annotation:
#Path("/example")
public class MyResource {
#Context
private SecurityContext securityContext;
...
}
It alson 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();
I have initially described this approach in this answer.
Alternatives
If you don't want to override the SecurityContext for some reason, you could consider other approaches, depending on what you have available in your application:
With CDI, you could create a bean annotated with #RequestScoped to hold the name of the authenticated user. After performing the authentication, set the name of the user in the request scoped bean and inject it into your resource classes using #Inject.
Since you are using Spring, you could consider using Spring Security on the top of your JAX-RS application for authentication and authorization.
I guess you need to add your SecurityContext as parameter to the method annotated as #Context like that:
#Override
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response listActiveAreas(#Context SecurityContext securityCtx) {
// Do something with data in securityCtx...
return Response.ok("woo hoo it worked").build();
}
If it will not work (I did not try it and use other way):
You may set your securityContext as HttpRequest/HttpServletRequest or (Session) attribute with some name (as example user.security.ctx) and inject Request same way. i.e.
#Override
#GET
#Produces(MediaType.APPLICATION_JSON)
public Response listActiveAreas(#Context HttpServletRequest request) {
SecurityContext securityCtx = (SecurityContext )request.getAttribute("user.security.ctx");
// Do something with data in securityCtx...
return Response.ok("woo hoo it worked").build();
}
Related
Question: is my implementation secure (by API Key standards) OR as secure as using Spring Boot Security?
I have produced a Spring Boot API, but rather than using Spring Boot Security to implement Api Key security, I have written my own API key implementation. The API Key is passed as a #RequestHeader in each 'secured' request (see /booking/cancel below).
Controller:
#RequestMapping(value = "/booking/cancel",
consumes = { "application/json" },
method = RequestMethod.POST)
public ResponseEntity<Void> cancelOrder(#RequestBody Cancellation cancellation,
#RequestHeader String apiKey) {
if(apiKey == null) {
return new ResponseEntity<Void>(HttpStatus.NOT_ACCEPTABLE);
}
long bookingProviderId;
try {
bookingProviderId = bookingService.getIdFromApiKey(apiKey);
if (bookingProviderId < 0) {
return new ResponseEntity<Void>(HttpStatus.NOT_ACCEPTABLE);
}
} catch (ApplicationException e) {
e.printStackTrace();
return new ResponseEntity<Void>(HttpStatus.INTERNAL_SERVER_ERROR);
}
//More code here...
}
Service layer:
The getIdFromApiKey function exists in my service layer and calls the Dao object. It returns a long (Id) which I can subsequently use to manage access in the controller (e.g. prevent a user from cancelling someone else's order).
public long getIdFromApiKey(String apiKey) throws ApplicationException {
return apiKeyDao.selectId(apiKey);
}
Dao Layer:
public long getApiKey (String apiKey) throws DataAccessException {
BookingProvider bp = jdbcTemplate.queryForObject("SELECT * FROM BookingProvider WHERE apiKey = ?", BeanPropertyRowMapper.newInstance(BookingProvider.class), apiKey);
if(bp == null)
return -1;
else
return bp.getId();
}
Late answer: The approach behind your code is good, but you don't need to write so much code. It is better to configure this in one place with Spring Security. It will allow to add security on only on your controllers as their are the entry point of your application. See this link for more details and below is the code I am using:
application.yml
application:
http:
authentication:
header-name: X-API-KEY-TEST
api-key: X-API-KEY-VALUE-TEST
ApiKeyProperties.java
#Getter
#Setter
#NoArgsConstructor
#Configuration
#ConfigurationProperties(prefix = "application.http.authentication")
public class ApiKeyProperties {
private String headerName;
private String apiKey;
}
ApiKeyAuthenticationFilter.java
public class ApiKeyAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
private final String headerName;
public ApiKeyAuthenticationFilter(String headerName) {
this.headerName = headerName;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(headerName);
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return null;
}
}
ApiSecurityConfiguration.java
#Configuration
#EnableWebSecurity
public class ApiSecurityConfiguration {
private final ApiKeyProperties apiKeyProperties;
#Autowired
public ApiSecurityConfiguration(ApiKeyProperties apiKeyProperties) {
this.apiKeyProperties = apiKeyProperties;
}
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity security) throws Exception {
var filter = new ApiKeyAuthenticationFilter(apiKeyProperties.getHeaderName());
filter.setAuthenticationManager(authentication -> {
var principal = (String) authentication.getPrincipal();
if (!apiKeyProperties.getApiKey().equals(principal)) {
throw new UnauthorizedException("The API key was not found or not the expected value");
}
authentication.setAuthenticated(true);
return authentication;
});
security.antMatcher("/**")
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.addFilter(filter)
.authorizeRequests()
.anyRequest()
.authenticated();
return security.build();
}
}
Postman
In a simple Web Application I could retrieve a user (from session attributes) and set it as a parameter for all servlets, using a filter:
Inside Filter:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
checkUser((HttpServletRequest) request);
chain.doFilter(request, response);
}
private void checkUser(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
request.setAttribute("user", session.getAttribute("user"));
}
}
Then I could use it in my JSP files (<c:if test="${not empty user}"> blablabla):
${user.getDisplayName}
In a Spring application I have to Inject principal in every Controller function. Even in that case a Principal is not a User, so I need to use the UserService every time:
#Controller
#RequestMapping("/")
public class IndexController {
private final UserService userService;
#Autowired
IndexController(UserService userService) {
this.userService = userService;
}
private static final String VIEW = "index";
#RequestMapping(method = RequestMethod.GET)
ModelAndView index(Principal principal) {
User user = null;
if (principal != null) {
user = userService.findByUsername(principal.getName());
}
return new ModelAndView(VIEW, "user", user);
}
}
I need the user (entity class) object because it has different functions I use. E.g. getDisplayName() which I use for my navbar (on every page):
User class:
public String getDisplayName() {
if (firstname == null && lastname == null) {
return username;
}
if (firstname != null && lastname != null) {
return String.format("%s %s", firstname, lastname);
}
if (firstname != null) {
return firstname;
}
return lastname;
}
I cannot use this specific user-function (getDisplayName) in Spring Framework using Security tag library functions:
<security:authentication property="name"/> <%-- So, this is not what I am looking for --%>
Ok, I even tried to make a custom interceptor and register it (what I don't want to do, because I have to read my user every time from database again). But then I have problems with Autowiring the UserService inside an interceptor.
What's the best way to solve this problem? Is it possible to find the User from database (findByUsername) once after a successful login, then put it in a session, then make some kind of interceptor that can do the same I did with filters before or retrieve user every time from database for every page but avoid this devious repeting code?
You can either provide a login success handler like
http.formLogin().successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest arg0,
HttpServletResponse arg1, Authentication arg2) throws IOException,
ServletException {
// here you can put your logic to save User object into session then forward/redirect to where ever you want
}
})
Or alternatively you can forward a login success to some end point like
http.formLogin().successForwardUrl("/loginsuccess")
and provide endpont implementation like
#RequestMapping({ "/loginsuccess" })
public ResponseEntity<?> loginSuccess(Principal user) {
// here you can put your logic to save User object into session then forward/redirect to where ever you want
return "";
}
Choose whichever way you find more convenient.
Solved:
I made a custom interceptor, injected an autowired User Service into it, and registered that custom interceptor. It works!
public class MyWebConfig extends WebMvcConfigurerAdapter {
#Bean
public UsernameInjectionInterceptor usernameInjectionInterceptor() {
return new UsernameInjectionInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(usernameInjectionInterceptor());
}
}
public class UsernameInjectionInterceptor extends HandlerInterceptorAdapter {
#Autowired
private UserService userService;
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
Principal principal = request.getUserPrincipal();
if (principal != null) {
HttpSession session = request.getSession(false);
if (session != null) {
String displayName = (String) session.getAttribute("displayName");
if (displayName == null) {
User user = userService.findByUsername(principal.getName());
if (user != null) {
displayName = user.getDisplayName();
session.setAttribute("displayName", displayName);
}
}
modelAndView.addObject("displayName", displayName);
}
}
}
}
I want to pass the user object I use for authentication in a filter to the resource. Is it possible?
I'm using wildfly 10 (resteasy 3)
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Inject
private UserDao userDao;
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
logger.warn("Filter");
String uid = requestContext.getHeaderString("Authorization");
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private User validateUser(String uid) {
return userDao.getById(uid);
}
}
There are two ways I could see to do this. The first is, perhaps, the more standard way but is also more code. Ultimately you'll inject the user as part of the request. However, the first thing you need for this solution is a Principal. A very simple one might be:
import java.security.Principal;
...
public class UserPrinicipal implements Prinicipal {
// most of your existing User class but needs to override getName()
}
Then, in your filter:
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return user;
}
#Override
public boolean isUserInRole(String role) {
// whatever works here for your environment
}
#Override
public boolean isSecure() {
return containerRequestContext.getUriInfo().getAbsolutePath().toString().startsWith("https");
}
#Override
public String getAuthenticationScheme() {
// again, whatever works
}
});
In the class where you want the User, you could do something like:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething() {
User user = (User)securityContext.getUserPrincipal();
}
}
I've implemented it this way and it works pretty well. However, an arguably simpler way is to just store the user in the session:
#Context
private HttpServletRequest request;
...
User user;
if((user = validateUser(uid)) == null) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
request.getSession().setAttribute("user", user);
Then, in your service:
#Path("/myservice")
public class MyService {
#Context
private SecurityContext securityContext;
#Path("/something")
#GET
public Response getSomething(#Context HttpServletRequest request) {
User user = (User)request.getSession().getAttribute("user");
}
}
The downside of the second method is that you are really no longer a stateless service as you're storing state somewhere. But the HttpSession is there even if you don't use it.
Using Dropwizard 0.9.1 I have created a custom AuthFilter to check session cookie as below:
Priority(Priorities.AUTHENTICATION)
public class SessionAuthFilter extends AuthFilter<String /*session key*/, SessionUser /*principal*/> {
private SessionAuthFilter() {
}
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Cookie sessionKey = requestContext.getCookies().get("sessionKey");
if (sessionKey != null) {
try {
Optional<SessionUser> principal = new SessionAuthenticator().authenticate(sessionKey.getValue());
requestContext.setSecurityContext(new SecurityContext() {
#Override
public Principal getUserPrincipal() {
return principal.get();
}
#Override
public boolean isUserInRole(String role) {
return false;
}
#Override
public boolean isSecure() {
return requestContext.getSecurityContext().isSecure();
}
#Override
public String getAuthenticationScheme() {
return SecurityContext.FORM_AUTH;
}
});
return;
} catch (AuthenticationException e) {
throw new InternalServerErrorException(e.getMessage(), e);
}
}
throw new NotAuthorizedException("Please log in!", "realm="+realm);
}
And registered it as below:
environment.jersey().register(new AuthDynamicFeature(new SessionAuthFilter.Builder().setAuthenticator(new
SessionAuthenticator()).setRealm("Login").buildAuthFilter()));
environment.jersey().register(RolesAllowedDynamicFeature.class);
The problem is I can not use #Permitall annotation on class level in Resource classes. It works fine If I use on method, but not filtering on class.
Resource class:
#Path("/")
#PermitAll //Doesn't work here
#Produces(MediaType.APPLICATION_JSON)
public class HomeResource {
#GET
#PermitAll //Works fine if here
#Path("/about")
public Response get() {
}
}
Any idea anyone?
Authz annotations at the class level is not supported in DW 9.x. You can see in the source code of AuthDynamicFeature, only method level annotations are checked, ultimately only registering the auth filter to methods with the Authz annotations.
This limitiation has been fixed in this pull request (to 1.0.0), where #RolesAllowed and #PermitAll at the class level will be supported.
Using Jersey 1.14 and Spring 3.1.2
I want to create a filter like this: https://gist.github.com/3031495
but in that filter I want access to a provider I created.
I'm getting an IllegalStateException. I suspect something in my lifecycle is hosed up. I can access #Context private HttpServletRequest and pull the session info I need from there, but then two classes have to know about where/how to get my "AuthUser" object.
Any help is appreciated!
My Provider:
#Component
#Provider
public class AuthUserProvider extends AbstractHttpContextInjectable<AuthUser> implements
InjectableProvider<Context, Type> {
private static final Logger LOG = LoggerFactory.getLogger(AuthUserProvider.class);
#Context
HttpServletRequest req;
public void init() {
LOG.debug("created");
}
#Override
// this may return a null AuthUser, which is what we want....remember, a
// null AuthUser means the user hasn't authenticated yet
public AuthUser getValue(HttpContext ctx) {
return (AuthUser) req.getSession().getAttribute(AuthUser.KEY);
}
// InjectableProvider implementation:
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
public Injectable<AuthUser> getInjectable(ComponentContext ic, Context ctx, Type c) {
if (AuthUser.class.equals(c)) {
return this;
}
return null;
}
}
My Filter:
#Component
public class TodoFilter implements ResourceFilter {
private static final Logger LOG = LoggerFactory.getLogger(TodoFilter.class);
#Autowired
private JdbcTemplate todoTemplate;
// this works
#Context
private HttpServletRequest servletRequest;
// this throws a java.lang.IllegalStateException
// #Context
// private AuthUser authUser;
public void init() throws Exception {
LOG.debug("created");
LOG.debug(todoTemplate.getDataSource().getConnection().getMetaData()
.getDatabaseProductName());
}
#Override
public ContainerRequestFilter getRequestFilter() {
return new ContainerRequestFilter() {
#Override
public ContainerRequest filter(ContainerRequest request) {
LOG.debug("checking if {} is authorized to use {}", "my authenticated user",
request.getPath());
// String name = request.getUserPrincipal().getName();
// String[] admins = settings.getAdminUsers();
// for (String adminName : admins) {
// if (adminName.equals(name))
// return request;
// }
// if (authUser.getUsername().equals("jberk")) {
// return request;
// }
// return HTTP 403 if name is not found in admin users
throw new WebApplicationException(Response.status(Response.Status.FORBIDDEN)
.entity("You are not authorized!").build());
}
};
}
#Override
public ContainerResponseFilter getResponseFilter() {
return new ContainerResponseFilter() {
#Override
public ContainerResponse filter(ContainerRequest request,
ContainerResponse response) {
// do nothing
return response;
}
};
}
}
My Service (aka Resource):
#Component
#Path("/rs/todo")
#Produces(MediaType.APPLICATION_JSON)
#ResourceFilters(TodoFilter.class)
public class TodoService {
#GET / #POST methods
}
so I think I figured this out....
I added this to my ResourceFilter:
#Context
private HttpContext ctx;
#Autowired
private AuthUserProvider provider;
then I can do this in the filter method:
public ContainerRequest filter(ContainerRequest request) {
AuthUser authUser = provider.getValue(ctx);
// use authuser in some way
}
this might not be "correct"...but it's working and I don't have code duplication
public ComponentScope getScope() {
return ComponentScope.Singleton;
}
It should be
public ComponentScope getScope() {
return ComponentScope.PerRequest;
}