Внедрить информацию SecurityContext и тело запроса в @BeanParam

В настоящее время я визуализирую объект команды в MessageBodyReader, но я хотел бы сделать это в @BeanParam:

  1. Вставьте поле, полученное из SecurityContext (есть ли что-то, что можно зацепить в преобразовании?).

  2. есть ввод поля, материализованный MessageBodyReader.

Это возможно ?


person Hassan Syed    schedule 27.10.2015    source источник


Ответы (1)


Примечание. Перейдите к ОБНОВЛЕНИЮ. Я думаю, можно использовать @BeanParam. Хотя вам нужно внедрить SecurityContext в bean-компонент и извлечь информацию об имени.


Невозможно добиться этого с @BeanParam исправленным. Вы могли использовать MessageBodyReader так, как вы это делаете, но, по моему мнению, это скорее хак, чем что-либо еще. Вместо этого я бы достиг этого, используя компоненты фреймворка так, как они должны использоваться, что включает в себя внедрение пользовательских параметров.

Для этого вам нужны две вещи: ValueFactoryProvider для предоставления значений параметров и InjectionResolver с вашей собственной пользовательской аннотацией. Я не буду много объяснять приведенный ниже пример, но вы можете найти хорошее объяснение в

Вы можете запустить приведенный ниже пример, как и любой тест JUnit. Все включено в один класс. Это зависимости, которые я использовал.

<dependency>
    <groupId>org.glassfish.jersey.test-framework.providers</groupId>
    <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
    <version>2.19</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-json-jackson</artifactId>
    <version>2.19</version>
    <scope>test</scope>
</dependency>

А вот и тест

import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.security.Principal;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.internal.inject.AbstractContainerRequestValueFactory;
import org.glassfish.jersey.server.internal.inject.AbstractValueFactoryProvider;
import org.glassfish.jersey.server.internal.inject.MultivaluedParameterExtractorProvider;
import org.glassfish.jersey.server.internal.inject.ParamInjectionResolver;
import org.glassfish.jersey.server.model.Parameter;
import org.glassfish.jersey.server.spi.internal.ValueFactoryProvider;
import org.glassfish.jersey.test.JerseyTest;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class CustomInjectionTest extends JerseyTest {

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    public static @interface CustomParam {
    }

    public static class CustomModel {

        public String name;
        public RequestBody body;
    }

    public static class RequestBody {

        public String message;
    }

    public static class CustomParamValueFactory
            extends AbstractContainerRequestValueFactory<CustomModel> {

        @Override
        public CustomModel provide() {
            ContainerRequest request = getContainerRequest();
            String name = request.getSecurityContext().getUserPrincipal().getName();
            RequestBody body = request.readEntity(RequestBody.class);
            CustomModel model = new CustomModel();
            model.body = body;
            model.name = name;
            return model;
        }
    }
    
    public static class CustomValueFactoryProvider extends AbstractValueFactoryProvider {
        
        @Inject
        public CustomValueFactoryProvider(MultivaluedParameterExtractorProvider multiProvider,
                                          ServiceLocator locator) {
            super(multiProvider, locator, Parameter.Source.UNKNOWN);
        }

        @Override
        protected Factory<?> createValueFactory(Parameter parameter) {
            if (CustomModel.class == parameter.getType()
                    && parameter.isAnnotationPresent(CustomParam.class)) {
                return new CustomParamValueFactory();
            }
            return null;
        }  
    }
    
    public static class CustomParamInjectionResolver extends ParamInjectionResolver<CustomParam> {
        
        public CustomParamInjectionResolver() {
            super(CustomValueFactoryProvider.class);
        }
    }
    
    private static class CustomInjectBinder extends AbstractBinder {

        @Override
        protected void configure() {
            bind(CustomValueFactoryProvider.class)
                    .to(ValueFactoryProvider.class)
                    .in(Singleton.class);
            bind(CustomParamInjectionResolver.class)
                    .to(new TypeLiteral<InjectionResolver<CustomParam>>(){})
                    .in(Singleton.class);
        } 
    }
    
    private static final String PRINCIPAL_NAME = "peeskillet";
    
    @PreMatching
    public static class SecurityContextFilter implements ContainerRequestFilter {

        @Override
        public void filter(ContainerRequestContext requestContext) throws IOException {
            requestContext.setSecurityContext(new SecurityContext(){
                public Principal getUserPrincipal() {
                    return new Principal() { 
                        public String getName() { return PRINCIPAL_NAME; }
                    };
                }
                public boolean isUserInRole(String role) { return false; }
                public boolean isSecure() { return true;}
                public String getAuthenticationScheme() { return null; }
            });   
        }  
    }
    
    @Path("test")
    public static class TestResource {
        @POST
        @Produces(MediaType.TEXT_PLAIN)
        @Consumes(MediaType.APPLICATION_JSON)
        public String post(@CustomParam CustomModel model) {
            return model.name + ":" + model.body.message;
        }
    }
    
    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(TestResource.class)
                .register(SecurityContextFilter.class)
                .register(new CustomInjectBinder());
    }
    
    @Test
    public void should_return_name_with_body() {
        RequestBody body = new RequestBody();
        body.message = "Hello World";
        Response response = target("test").request()
                .post(Entity.json(body));
        assertEquals(200, response.getStatus());
        String responseBody = response.readEntity(String.class);
        assertEquals(PRINCIPAL_NAME + ":" + body.message, responseBody);
        System.out.println(responseBody);
    }
}

Обратите внимание, что я прочитал тело запроса из ContainerRequest внутри CustomParamValueFactory. Это тот самый RequestBody, который я отправил в формате JSON из запроса в файле @Test.


ОБНОВИТЬ

Так что, к моему удивлению, можно использовать @BeanParam. Вот следующий компонент, который я использовал для тестирования

public static class CustomModel {

    @Context
    public SecurityContext securityContext;
    public RequestBody body;
}

public static class RequestBody {

    public String message;
}

Отличие от предыдущего теста в том, что вместо name из SecurityContext.Principal нам нужно ввести все SecurityContext. У инжекта просто нет возможности получить имя из Principal, поэтому мы просто сделаем это вручную.

Но больше всего меня удивило то, что мы можем внедрить сущность RequestBody. Я не знал, что это возможно.

Вот полный тест

import java.io.IOException;
import java.security.Principal;
import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.client.Entity;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class CustomInjectTestTake2 extends JerseyTest {
    
    private static final String PRINCIPAL_NAME = "peeskillet";
    private static final String MESSAGE = "Hello World";
    private static final String RESPONSE = PRINCIPAL_NAME + ":" + MESSAGE;
    
    public static class CustomModel {

        @Context
        public SecurityContext securityContext;
        public RequestBody body;
    }

    public static class RequestBody {

        public String message;
    }
    
    @PreMatching
    public static class SecurityContextFilter implements ContainerRequestFilter {

        @Override
        public void filter(ContainerRequestContext requestContext) throws IOException {
            requestContext.setSecurityContext(new SecurityContext(){
                public Principal getUserPrincipal() {
                    return new Principal() { 
                        public String getName() { return PRINCIPAL_NAME; }
                    };
                }
                public boolean isUserInRole(String role) { return false; }
                public boolean isSecure() { return true;}
                public String getAuthenticationScheme() { return null; }
            });   
        }  
    }
    
    @Path("test")
    public static class TestResource {
        @POST
        @Produces(MediaType.TEXT_PLAIN)
        @Consumes(MediaType.APPLICATION_JSON)
        public String post(@BeanParam CustomModel model) {
            return model.securityContext.getUserPrincipal().getName() 
                    + ":"  + model.body.message;
        }
    }
    
    @Override
    public ResourceConfig configure() {
        return new ResourceConfig(TestResource.class)
                .register(SecurityContextFilter.class);
    }
    
    @Test
    public void should_return_name_with_body() {
        RequestBody body = new RequestBody();
        body.message = "Hello World";
        Response response = target("test").request()
                .post(Entity.json(body));
        assertEquals(200, response.getStatus());
        String responseBody = response.readEntity(String.class);
        assertEquals(RESPONSE, responseBody);
        System.out.println(responseBody);
    }
}

См. также:

person Paul Samsotha    schedule 27.10.2015
comment
Большое спасибо за то, что поставили меня на место :D Я изначально собирался использовать этот подход (2 недели назад), но в то время я был довольно рано на этапе повышения квалификации, и это казалось касательным или чрезмерным проектированием с возможностью найти более подходящее решение. - person Hassan Syed; 27.10.2015
comment
Да, это кажется чересчур, но на самом деле именно так Джерси обрабатывает все @BeanParam, @FormParam, @PathParam и т. д. Так что мы можем следовать этому шаблону... или нет :-) - person Paul Samsotha; 28.10.2015
comment
@HassanSyed Пожалуйста, смотрите мое ОБНОВЛЕНИЕ. Думаю, это возможно с @BeanParam :-) - person Paul Samsotha; 29.10.2015