I have working application secured with method-level security:
RestController:
#PreAuthorize("hasPermission(#product, 'WRITE')")
#RequestMapping(value = "/save", method = RequestMethod.POST)
public Product save(#RequestBody Product product) {
return productService.save(product);
}
PermissionEvaluator:
public class SecurityPermissionEvaluator implements PermissionEvaluator {
private Logger log = LoggerFactory.getLogger(SecurityPermissionEvaluator.class);
private final PermissionService permissionService;
public SecurityPermissionEvaluator(PermissionService permissionService) {
this.permissionService = permissionService;
}
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString());
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// almost the same implementation
}
}
And everything works fine until I implemented API which saves collection of objects. The logic of this service is to update existing entities and/or create new entities.
#PreAuthorize("hasPermission(#products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
After this my permission service handles the collection object and looks like this now:
PemissionService:
public class PermissionService {
public boolean isAuthorized(User user, Object targetDomainObject, String permission) {
if (targetDomainObject instanceof TopAppEntity) {
if (((TopAppEntity) targetDomainObject).getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
} else if(targetDomainObject instanceof Collection) {
boolean isAuthorized = false;
Collection targetDomainObjects = (Collection) targetDomainObject;
for (Object targetObject : targetDomainObjects) {
isAuthorized = isAuthorized(user, targetObject, permission);
if (!isAuthorized) break;
}
return isAuthorized;
}
}
}
My question is:
How I can handle collections using #PreAuthorize("hasPermission(#object, '...')") more elegant way? Is there some implementations in Spring Security for handling collections? At least, how can I optimize PemissionService for handling Collections?
I have a couple of workarounds.
1. The first one is to use my own MethodSecurityExpressionHandler and MethodSecurityExpressionRoot.
Creating a CustomMethodSecurityExpressionRoot and define a method which will be our new expression for Collection handling. It will extend SecurityExpressionRoot to include default expressions:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private final PermissionEvaluator permissionEvaluator;
private final Authentication authentication;
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication, PermissionEvaluator permissionEvaluator) {
super(authentication);
this.authentication = authentication;
this.permissionEvaluator = permissionEvaluator;
super.setPermissionEvaluator(permissionEvaluator);
}
public boolean hasAccessToCollection(Collection<Object> collection, String permission) {
for (Object object : collection) {
if (!permissionEvaluator.hasPermission(authentication, object, permission))
return false;
}
return true;
}
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
#Override
public Object getThis() {
return target;
}
}
Create custom expression handler and inject CustomMethodSecurityExpressionRoot:
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final PermissionEvaluator permissionEvaluator;
public CustomMethodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
this.permissionEvaluator = permissionEvaluator;
super.setPermissionEvaluator(permissionEvaluator);
}
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root =
new CustomMethodSecurityExpressionRoot(authentication, permissionEvaluator);
root.setTrustResolver(new AuthenticationTrustResolverImpl());
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
I also injected SecurityPermissionEvaluator used in question, so it will be a single point of entry for custom and default expressions. As an alternate option we could inject and use PermissionService directly.
Configuring our method-level security:
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private PermissionService permissionService;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
PermissionEvaluator permissionEvaluator = new SecurityPermissionEvaluator(permissionService);
return new CustomMethodSecurityExpressionHandler(permissionEvaluator);
}
}
Now we can use new expression in RestController:
#PreAuthorize("hasAccessToCollection(#products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
As a result a part with handling collection in PermissionService could be omitted as we took out this logic to custom expression.
2. The second workaround is to call method directly using SpEL.
Now I'm using PermissionEvaluator as Spring bean (any service could be used here, but I'm preferring single point of entry again)
#Component
public class SecurityPermissionEvaluator implements PermissionEvaluator {
#Autowired
private PermissionService permissionService;
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
if (!(targetDomainObject instanceof TopAppEntity))
throw new IllegalArgumentException();
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return permissionService.isAuthorized(userDetails.getUser(), targetDomainObject, permission.toString());
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
try {
return permissionService.isAuthorized(userDetails.getUser(), targetId,
Class.forName(targetType), String.valueOf(permission));
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("No class found " + targetType);
}
}
public boolean hasPermission(Authentication authentication, Collection<Object> targetDomainObjects, Object permission) {
for (Object targetDomainObject : targetDomainObjects) {
if (!hasPermission(authentication, targetDomainObject, permission))
return false;
}
return true;
}
}
Configuring method security:
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private PermissionEvaluator permissionEvaluator;
#Autowired
private ApplicationContext applicationContext;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
// Pay attention here, or Spring will not be able to resolve bean
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
}
Usage of the service in expression:
#PreAuthorize("#securityPermissionEvaluator.hasPermission(authentication, #products, 'WRITE')")
#RequestMapping(value = "/saveCollection", method = RequestMethod.POST)
public Collection<Product> save(#RequestBody Collection<Product> products) {
return productService.save(products);
}
Spring beans created by default with class name if no other name specified.
Summary: both approaches based on using custom services calling them directly or registering them as expressions and could handle the logic of collection before it will be sent to authority checking service, so we can omit the part of it:
#Service
public class PermissionService {
public boolean isAuthorized(User user, TopAppEntity domainEntity, String permission) {
// removed instanceof checks and can operate on domainEntity directly
if (domainEntity.getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
}
}
Yes, there is a smart way. I can tell you what I did.
#Component("MySecurityPermissionEvaluator ")
#Scope(value = "session")
public class PermissionService {
#Autowired
private PermissionEvaluator permissionEvaluator;
public boolean myPermission(Object obj, String permission) {
boolean isAuthorized = false;
Authentication a = SecurityContextHolder.getContext()
.getAuthentication();
if (null == obj) {
return isAuthorized;
}
if (a.getAuthorities().size() == 0) {
logger.error("For this authenticated object, no authorities could be found !");
return isAuthorized;
} else {
logger.error("Authorities found " + a.getAuthorities());
}
try {
isAuthorized = myPermissionEval
.hasPermission(a, obj, permission);
} catch (Exception e) {
logger.error("exception while analysisng permissions");
}
return isAuthorized;
}
Please do not use hard coded permissions, Use this way instead,
import org.springframework.security.acls.domain.DefaultPermissionFactory;
public class MyPermissionFactory extends DefaultPermissionFactory {
public MyPermissionFactory() {
registerPublicPermissions(MyPermission.class);
}
}
To make custom permissions,
import org.springframework.security.acls.domain.BasePermission;
public class MyPermission extends BasePermission { //use this class for creating custom permissions
private static Map<String, Integer> customPerMap = new HashMap<String, Integer>();
static {
customPerMap.put("READ", 1);
customPerMap.put("WRITE", 2);
customPerMap.put("DELETE", 4);
customPerMap.put("PUT", 8);
}
/**
*Use the function while saving/ getting permission code
**/
public static Integer getCode(String permName) {
return customPerMap.get(permName.toUpperCase());
}
If you need to authenticate urls based on admin users or role hierarchy, use tag in Spring Authentication not Authorization.
Rest, you are using correctly, #PreAuthorize and #PreFilter both are correct and used acco to requirements.
You can use the #PreFilter annotation.
So #PreFilter("hasPermission(filterTarget, '...')") will call your PermissionService for each element of the Collection.
public class PermissionService() {
public boolean isAuthorized(User user, Object targetDomainObject, String permission) {
if (targetDomainObject instanceof TopAppEntity) {
if (((TopAppEntity) targetDomainObject).getId() == null) {
// check authorities and give response
} else {
// check ACL and give response
}
}
}
}
Note: this will not prevent a call of your controller method. It only gets an empty Collection.
In some cases it's enough a default implementation of SecurityExpressionRoot.
If your permission evaluation is based on only analyzing, for example, an Owner of Product you could use the next expressions:
#GetMapping("")
#PostAuthorize("hasPermission(returnObject.![#this.owner],'ProductOwner','READ')")
public Collection<Product> getAllFiltering(<filters>) {...
#PostMapping("/collection")
#PreAuthorize("hasPermission(#products.![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> save(#RequestBody Collection<Product> products) {...
#PutMapping("/collection")
#PreAuthorize("hasPermission(#productRepository.findByIds(#products.![#this.id]).![#this.owner],'ProductOwner','WRITE')")
public Collection<Product> update(#RequestBody Collection<Product> products) {...
In these cases your PermissionEvaluator must be able to process collection.You could also continue using your PermissionEvaluator for a single Product:
#GetMapping("/{id}")
#PostAuthorize("hasPermission({ returnObject.owner },'ProductOwner','READ')")
public Product getById(#PathVariable int id) {...
or make an implementation of PermissionEvaluator which analyzes whether an array or a single value was passed.
#products.![#this.owner] - see "6.5.17 Collection Projection"; { returnObject.owner } - see "6.5.3 Inline lists"
here: https://docs.spring.io/spring/docs/3.0.x/reference/expressions.html
Related
I'm writing a library that uses Spring Security and method security to check whether a user is licensed to perform a certain operation. This is in addition to the usual role-based security, and this is causing a problem.
The annotations look like they do in this test class:
#RestController
class TestController {
#RolesAllowed("ROLE_USER")
#Licensed("a")
public ResponseEntity<String> a() {
return ResponseEntity.ok("a");
}
#RolesAllowed("ROLE_USER")
#Licensed("b")
public ResponseEntity<String> b() {
return ResponseEntity.ok("b");
}
#RolesAllowed("ROLE_USER")
#Licensed("c")
public ResponseEntity<String> c() {
return ResponseEntity.ok("c");
}
}
Having the annotations processed seems simple enough, because you add a customMethodSecurityDataSource:
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
#Configuration
public class LicenceSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new LicensedAnnotationSecurityMetadataSource();
}
// more configurations
}
But the problem is in Spring's implementation:
#Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (this.attributeCache) {
Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey);
// Check for canonical value indicating there is no config attribute,
if (cached != null) {
return cached;
}
// No cached value, so query the sources to find a result
Collection<ConfigAttribute> attributes = null;
for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) {
attributes = s.getAttributes(method, targetClass);
if (attributes != null && !attributes.isEmpty()) {
break;
}
}
// Put it in the cache.
if (attributes == null || attributes.isEmpty()) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
return NULL_CONFIG_ATTRIBUTE;
}
this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes));
this.attributeCache.put(cacheKey, attributes);
return attributes;
}
My custom metadata source is processed first, and as soon as it finds an annotation that it recognises, it stops processing. Specifically, in this if-block:
if (attributes != null && !attributes.isEmpty()) {
break;
}
The result is that my LicenceDecisionVoter votes to abstain; after all, there could be other annotation processors that check roles. And because there are no more attributes to vote upon, only ACCESS_ABSTAIN is returned, and as per Spring's default and recommended configuration, access is denied. The roles are never checked.
Do I have an alternative, other than to implement scanning for Spring's own annotation processors, like the #Secured and JSR-250 annotations?
Or was the mistake to use Spring Security in the first place for this specific purpose?
As promised, the solution. It was more work than I imagined, and the code may have issues because it is partly copied from Spring, and some of that code looks dodgy (or at least, IntelliJ thinks it does).
The key is to remove the GlobalMethodSecurityConfiguration. Leave that to the application itself. The (auto) configuration class looks like the following:
#EnableConfigurationProperties(LicenceProperties.class)
#Configuration
#Import(LicensedMetadataSourceAdvisorRegistrar.class)
public class LicenceAutoConfiguration {
#Bean public <T extends Licence> LicenceChecker<T> licenceChecker(
#Lazy #Autowired final LicenceProperties properties,
#Lazy #Autowired final LicenceFactory<T> factory
) throws InvalidSignatureException, LicenceExpiredException, WrappedApiException,
IOException, ParseException, InvalidKeySpecException {
final LicenceLoader loader = new LicenceLoader(factory.getPublicKey());
final T licence = loader.load(properties.getLicenceFile(), factory.getType());
return factory.getChecker(licence);
}
#Bean MethodSecurityInterceptor licenceSecurityInterceptor(
final LicensedMetadataSource metadataSource,
final LicenceChecker<?> licenceChecker
) {
final MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
interceptor.setAccessDecisionManager(decisionManager(licenceChecker));
interceptor.setSecurityMetadataSource(metadataSource);
return interceptor;
}
#Bean LicenceAccessDecisionManager decisionManager(#Autowired final LicenceChecker<?> licenceChecker) {
return new LicenceAccessDecisionManager(licenceChecker);
}
#Bean LicensedMetadataSource licensedMetadataSource() {
return new LicensedMetadataSource();
}
}
The registrar:
public class LicensedMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
#Override
public void registerBeanDefinitions(final AnnotationMetadata importingClassMetadata,
final BeanDefinitionRegistry registry) {
final BeanDefinitionBuilder advisor = BeanDefinitionBuilder
.rootBeanDefinition(LicensedMetadataSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.addConstructorArgReference("licensedMetadataSource");
registry.registerBeanDefinition("licensedMetadataSourceAdvisor", advisor.getBeanDefinition());
}
}
And finally, the advisor:
public class LicensedMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private final LicenceMetadataSourcePointcut pointcut = new LicenceMetadataSourcePointcut();
private transient LicensedMetadataSource attributeSource;
private transient BeanFactory beanFactory;
private transient MethodInterceptor interceptor;
private transient volatile Object adviceMonitor = new Object();
public LicensedMetadataSourceAdvisor(final LicensedMetadataSource attributeSource) {
this.attributeSource = attributeSource;
}
#Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override public Pointcut getPointcut() {
return pointcut;
}
#Override public Advice getAdvice() {
synchronized (this.adviceMonitor) {
if (this.interceptor == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
this.interceptor = this.beanFactory.getBean("licenceSecurityInterceptor", MethodInterceptor.class);
}
return this.interceptor;
}
}
class LicenceMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
#Override public boolean matches(final Method method, final Class<?> targetClass) {
final LicensedMetadataSource source = LicensedMetadataSourceAdvisor.this.attributeSource;
final Collection<ConfigAttribute> attributes = source.getAttributes(method, targetClass);
return attributes != null && !attributes.isEmpty();
}
}
}
The latter two classes are copied and modified from Spring. The advisor was copied from MethodSecurityMetadataSourceAdvisor, and that's a class that somebody at Spring might have a look at, because of the transient volatile synchronisation object (which I copied, because I can't yet establish if it should be final instead), and because it has a private method that is never used.
I'm studying tutorial how to create custom security expression and I created threes classes but I got error, I tried google everything, may be I am not updated or some. Can you explain what's going on?
Error:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalArgumentException: Failed to evaluate expression 'isComprador()'] with root cause
Method call: Method isComprador() cannot be found on type org.springframework.security.access.expression.method.MethodSecurityExpressionRoot
MethodSecurityConfig:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new CustomMethodSecurityExpressionHandler();
}
}
CustomMethodSecurityExpressionHandler:
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
#Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, MethodInvocation invocation) {
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(authentication);
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(this.trustResolver);
root.setRoleHierarchy(getRoleHierarchy());
return root;
}
}
CustomMethodSecurityExpressionRoot:
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
#Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
#Override
public Object getFilterObject() {
return filterObject;
}
#Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
#Override
public Object getReturnObject() {
return returnObject;
}
void setThis(Object target) {
this.target = target;
}
#Override
public Object getThis() {
return target;
}
//
public boolean isComprador() {
final Usuario usuario = ((UserDetailsImpl) this.getPrincipal()).getUsuario();
return usuario.getPerfil() == Perfil.COMPRADOR;
}
public boolean isVendedor() {
final Usuario usuario = ((UserDetailsImpl) this.getPrincipal()).getUsuario();
return usuario.getPerfil() == Perfil.VENDEDOR;
}
}
Thanks!
Att,
Carlos Oliveira
I'd really recommend using a custom bean rather than trying to integrate into the expression root. This is much easier to configure, decouples your code from Spring Security you just create a simple pojo, and allows your code to be more focused.
To use this approach start by creating a Spring Bean:
#Component
public class Authz {
public boolean isComprador() {
// Authentication is the currently logged in user
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return authentication != null && "comprador".equals(authentication.getName());
}
}
Then you can refer to methods in the Bean using #beanName.methodName. In our case, the Bean name is authz and our method is isComprador so the following would work:
#Service
public class MessageService {
// we pass in the name argument into our custom expression Authz.isComprador
#PreAuthorize("#authz.isComprador()")
String greetForName(String name) {
return "Hello " + name;
}
}
Finally we just enable method security like normal:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
}
You can then write a few unit tests to prove that it works:
#SpringBootTest
class DemoApplicationTests {
#Autowired
MessageService service;
#Test
#WithMockUser // run the test as a user with the default username of user
void secureWhenForbidden() {
assertThatCode(() -> service.greetForName("Rob")).isInstanceOf(AccessDeniedException.class);
}
#Test
#WithMockUser("comprador") // run the test as a user with the username of comprador
void secureWhenGranted() {
assertThatCode(() -> service.greetForName("Rob")).doesNotThrowAnyException();;
}
}
You can find a complete sample at https://github.com/rwinch/spring-security-sample/tree/method-security-bean-expression
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.
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();
}
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.