I'm not sure whether this is possible or not but I'm trying to setup a EJB + JAX-RS (Jersey) test project and use the #RolesAllowed annotation.
I'm currently getting the following error logs:
Warning: WEB9102: Web Login Failed: com.sun.enterprise.security.auth.login.common.LoginException: Login failed: Security Exception
Severe: ejb.stateless_ejbcreate_exception
Warning: A system exception occurred during an invocation on EJB TestSB, method: public java.util.List rest.sb.TestSB.findAll()
Warning: javax.ejb.EJBException: javax.ejb.EJBException: javax.ejb.CreateException: Could not create stateless EJB
Relevant classes:
ApplicationConfig.java
#ApplicationPath("rest")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("rest");
register(RolesAllowedDynamicFeature.class);
}
}
TestSBFacade.java
#Local
public interface TestSBFacade {
public List<Test> findAll();
}
TestSB.java
#Stateless
#Path("secured/test")
public class TestSB implements TestSBFacade {
#DAO #Inject
private TestDAOFacade dao;
#Context
SecurityContext securityContext;
#Secured
#RolesAllowed({"READ"})
#Path("all")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Override
public List<Test> findAll() {
//this works without the #RolesAllowed so it is a possible workaroud for now.
System.out.println(securityContext.isUserInRole("READ")); //output: true
return dao.findAll();
}
}
AuthFilter.java
#Provider
#Secured //NameBinding
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
String token = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
try {
verifyToken();
createSecurityContext();
} catch (Exception e) {
Logger.getLogger(AuthenticationFilter.class.getName()).log(Level.SEVERE, null, "Invalid or Expired JWT");
requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
}
}
}
My SecurityContext is set and working, the #RolesAllowed seems to be to problem since I get no errors if I remove it and a JSON is properly returned to the front-end. Keeping the #RolesAllowed results in the errors mentioned at the start.
However I'd like to use the handy annotation instead of embedding every method inside isUserInRole IF Statements. Any help and insights are much appreciated.
So apparently due to both EJB and JAX-RS implementations using#RolesAllowed they don't do well together. So I decided to create my own Annotation instead and register my own DynamicFeature in the ApplicationConfig.java.
Authorized.java
#Documented
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Authorized {
public String[] value() default "";
}
AuthorizationDynamicFeature.java
public class AuthorizationDynamicFeature implements DynamicFeature {
#Override
public void configure(final ResourceInfo resourceInfo, final FeatureContext featureContext) {
Authorized auth = new AnnotatedMethod(resourceInfo.getResourceMethod()).getAnnotation(Authorized.class);
if (auth != null) {
featureContext.register(new AuthorizationRequestFilter(auth.value()));
}
}
#Priority(Priorities.AUTHORIZATION)
private static class AuthorizationRequestFilter implements ContainerRequestFilter {
private final String[] roles;
AuthorizationRequestFilter() {
this.roles = null;
}
AuthorizationRequestFilter(final String[] roles) {
this.roles = roles;
}
#Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
if (!this.roles[0].isEmpty()) {
for (final String role : this.roles) {
if (requestContext.getSecurityContext().isUserInRole(role)) {
return;
}
}
throw new ForbiddenException(LocalizationMessages.USER_NOT_AUTHORIZED());
}
}
}
}
Huge thanks to #PaulSamsotha for leading me to a more suitable solution.
Related
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'm currently making a RESTapi using jersey 2.27 and jetty 9.4.
In this server I'm trying to apply a filter:
#AuthenticationEndpoint.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 {
//Authentication code
}
private boolean isTokenBasedAuthentication(String authorizationHeader) {
}
private void abortWithUnauthorized(ContainerRequestContext requestContext) {
}
private void validateToken(String token) throws Exception {
}
}
However, this filter isn't triggered.
This is my endpoint:
#Path("/authenticate")
public class AuthenticationEndpoint {
Machine machine = Machine.getInstance();
#NameBinding
#Retention(RUNTIME)
#Target({TYPE, METHOD})
public #interface Secured { }
#POST
#Path("/authenticate")
#Secured
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response authenticateUser(
AuthenticationRequest authenticationRequest){
}
I don't have a web.xml and I wouldn't know how to actually get this filter to trigger. Anyone have some advice for this? I'm having a hard time understanding this server filter configuration.
PS: i left out the content of the methods since I thought it would be too chaotic, I will of course add it if it is deemed necessary.
You have to register the filter when you create the Application, something like
public class MyApplication extends ResourceConfig {
register(AuthenticationFilter.class)
// yada yada
}
I wanted to use a interceptor(custom annotation) inside a jax-rs service.
1.First,I wrote an annotation class:
BasicAuthentication.java:
#NameBinding
#Target( {ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface BasicAuthentication {
}
2.Then I added the BasicAuthenticationInterceptor implements javax.ws.rs.ext.ReaderInterceptor
BasicAuthenticationInterceptor.java:
#Provider
#Priority(Priorities.AUTHENTICATION)
#BasicAuthentication
public class BasicAuthenticationInterceptor extends Dumpable implements ReaderInterceptor {
#Override
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
//log.info("authentication here")
String authHeader = context.getHeaders().getFirst(AUTHORIZATION);
if (authHeader == null) {
error("\"authorization\" is not found from the request header.");
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
return context.proceed();
}
}
3.At last,I add a test service with annotation #BasicAuthentication.
TestRestfulService.java
#Stateless
#Path("/api")
#Produces({MediaType.APPLICATION_JSON})
#Consumes({MediaType.APPLICATION_JSON})
#BasicAuthentication
public class TestRestfulService extends Dumpable{
#EJB
LocalService localService;
#Path("/test/{id}")
#GET
public Response test(#PathParam("id")String id) {
try {
localService.findUser(id);
} catch (Exception e) {
error(e);
return Response.serverError().build();
}
return Response.ok().build();
}
}
But every time I request /api/test/1 with empty header,I can get the correct response,the interceptor seems not work at all.
I'm using Wildfly 10.
Thanks in advance.
Finally I worked out.Change the Interceptor to filter:
public class BasicAuthenticationInterceptor implements javax.ws.rs.container.ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext context){
...
}
}
Then it works as expected.
Form JAX-WS API:
Interface for message body reader interceptors that wrap around calls to MessageBodyReader.readFrom(java.lang.Class, java.lang.reflect.Type, java.lang.annotation.Annotation[], javax.ws.rs.core.MediaType, javax.ws.rs.core.MultivaluedMap, java.io.InputStream).
javax.ws.rs.ReaderInterceptor
This could be the reason.
I am trying to create a filter for a REST API I have developed following these question Best practice for REST token-based authentication with JAX-RS and Jersey.
The problem is whatever of the methods I am invoking the filter doesnt appear to work.
These are my classes:
Secured.java
#NameBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
public #interface Secured {
}
AuthenticationFilter.java
#Secured
#Provider
#Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter{
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
// 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("Bearer ")) {
throw new NotAuthorizedException("Authorization header must be provided");
}
// Extract the token from the HTTP Authorization header
String token = authorizationHeader.substring("Bearer".length()).trim();
try {
// Validate the token
validateToken(token);
} catch (Exception e) {
requestContext.abortWith(
Response.status(Response.Status.UNAUTHORIZED).build());
}
}
private void validateToken(String token) throws Exception {
// Check if it was issued by the server and if it's not expired
// Throw an Exception if the token is invalid
}
}
RestService.java
#Path("/test")
public class RestService {
TestDAO testDAO;
#GET
#Secured
#Path("/myservice")
#Produces("application/json")
public List<Test> getEverisTests() {
testDAO=(TestDAO) SpringApplicationContext.getBean("testDAO");
long start = System.currentTimeMillis();
List<Test> ret = testDAO.getTests();
long end = System.currentTimeMillis();
System.out.println("TIEMPO TOTAL: " + (end -start));
return ret;
}
}
RestApplication.java
public class RestApplication extends Application{
private Set<Object> singletons = new HashSet<Object>();
public RestApplication() {
singletons.add(new RestService());
singletons.add(new AuthenticationFilter());
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
I am missing something? Thanks in advance.
Your AuthenticationFilter may not be registered.
It's very likely you have an Application subclass somewhere in your application. Use it to register the filter:
#ApplicationPath("api")
public class ApiConfig extends Application {
#Override
public Set<Class<?>> getClasses() {
HashSet<Class<?>> classes = new HashSet<>();
classes.add(AuthenticationFilter.class);
...
return classes;
}
}
The solution was to update Jboss modules of resteasy following this page resteasy and selecting the version of resteasy that I was using.
Thanks for the answers by the way!
I can't yet comment so this goes into an answer:
I don't understand how the #Secured mechanism works. Did you try to remove all #Secured annotations? The filter should then be active for all endpoints.
If it still does not work most probably you will have to register it manually in your application.
If it does work afterwards you have at least a hint on where to look for the problem ...
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.