How to configure Apache CXF client and server to pass additional classes to JAXBContext when it is serializing DTO to XML?
I can't use #XmlSeeAlso annotations because those classes are not known at compile time of jar with data contracts, but known when client compiles.
On client side I tried using:
Service service = Service.create(wsdlURL, serviceName, new UsesJAXBContextFeature(MyFactory.class));
T client = service.getPort(clazz);
But I got exception telling me that CXF doesn't support this feature.
You can do it with annotations also.
Works with Spring Boot CXF starter
#Autowired
private Bus bus;
#Bean
public Endpoint createMyEndpoint() {
JaxWsServerFactoryBean factory = new JaxWsServerFactoryBean();
Map<String, Object> properties = new HashMap<>();
properties.put("jaxb.additionalContextClasses", getExtraClasses());
factory.setProperties(properties);
Endpoint endpoint = new EndpointImpl(bus, new MyWebService(),factory);
endpoint.setProperties(new HashMap<>());
endpoint.publish("/v1/service");
return endpoint;
}
#SuppressWarnings("rawtypes")
private Class[] getExtraClasses() {
List<Class> extraClassList = new ArrayList<>();
extraClassList.add(A.class);
extraClassList.add(B.class);
return extraClassList.toArray(new Class[extraClassList.size()]);
}
...
#javax.jws.WebService
public class MyWebService implements MyPortType {
//...
}
I figured it out with
https://issues.apache.org/jira/browse/CXF-340
https://github.com/apache/cxf/blob/5578e0b82bcd4ea19c1de5b4a008af35f9c8451b/rt/frontend/jaxws/src/main/java/org/apache/cxf/jaxws/EndpointImpl.java#L164
if you configure cxf with cxf.xml (spring-xml) you can use the following:
<jaxws:endpoint/client>
<jaxws:properties>
<entry key="jaxb.additionalContextClasses">
<array value-type="java.lang.Class">
<value type="java.lang.Class">fullQualifiedClassName</value>
</array>
</entry>
</jaxws:properties>
</jaxws:endpoint>
or any other way to write the org.apache.cxf.jaxb.JAXBDataBinding property "extraClass" (a Class[]) like . See http://cxf.apache.org/docs/jaxb.html
Related
I am using spring webservices for consuming a service. I am following this article https://spring.io/guides/gs/consuming-web-service/, I have created a client by extending WebServiceGatewaySupport and using that to invoke the service.
The first doubt I have is that I haven't generated any java classes from wsdl (other than jaxb objects for the types mentioned in wsdl). Don't I have to generate the stub (endpoint) classes on the client side? If not, then how does spring know which operation to be invoked as my wsdl has multiple operations?
Here is my code
public class SOAPConnector extends WebServiceGatewaySupport {
public Object callWebService(String url, Object request){
return getWebServiceTemplate().marshalSendAndReceive(url, request);
}
}
#Configuration
public class Config {
#Bean
public Jaxb2Marshaller marshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
// this is the package name specified in the <generatePackage> specified in
// pom.xml
marshaller.setContextPath("com.example.howtodoinjava.schemas.school");
return marshaller;
}
#Bean
public SOAPConnector soapConnector(Jaxb2Marshaller marshaller) {
SOAPConnector client = new SOAPConnector();
client.setDefaultUri("https://www.example.com/ExampleServiceBean");
client.setMarshaller(marshaller);
client.setUnmarshaller(marshaller);
return client;
}
}
The second issue is that on executing above code I am getting following error message, can someone please help me how to fix it:
org.springframework.ws.soap.client.SoapFaultClientException: Message part Request was not recognized. (Does it exist in service WSDL?)
at org.springframework.ws.soap.client.core.SoapFaultMessageResolver.resolveFault(SoapFaultMessageResolver.java:38)
at org.springframework.ws.client.core.WebServiceTemplate.handleFault(WebServiceTemplate.java:830)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:624)
at org.springframework.ws.client.core.WebServiceTemplate.sendAndReceive(WebServiceTemplate.java:555)
at org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:506)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:446)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:436)
at org.springframework.ws.client.core.WebServiceTemplate.sendSourceAndReceiveToResult(WebServiceTemplate.java:424)
I might be a missing a very basic thing as I am using spring webservices for the first time. Thanks in advance.
I try to implement a Security Token Service (Server side) with a requested UsernameToken and my service should response a token, which is generated by cxf.
I use JAVA, Spring Boot and a java-based configuration.
I have some problems to implement a custom Token Provider and using the default tokenstore from cxf.
My custom SCTProvider:
public class BiPROTokenProvider extends SCTProvider{
private static final String WSC_IDENTIFIER = "wsc:Identifier";
private static final String BIPRO_PRAEFIX = "bipro:";
#Override
public TokenProviderResponse createToken(TokenProviderParameters tokenParameters) {
TokenProviderResponse response = super.createToken(tokenParameters);
String biproId = BIPRO_PRAEFIX + response.getTokenId().split(":")[1];
//NodeList identifier = ((Element) response.getToken()).getElementsByTagName(WSC_IDENTIFIER);
//identifier.item(0).setTextContent(biproId);
//Element identifier = response.getTokenId().getElementsByTagName(WSC_IDENTIFIER);
//super.createToken(tokenParameters).setTokenId(biproId);
response.setTokenId(biproId);
return response;
}
}
My first problem is, that I do not know where I should include my custom SCT Provider? - is it possible do to it at my endpoint publish?
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), securityTokenServicePortType());
endpoint.setServiceName(securityTokenService26010().getServiceName());
endpoint.setWsdlLocation(securityTokenService26010().getWSDLDocumentLocation().toString());
endpoint.publish("/SecurityTokenService-2.6.0.1.0");
endpoint.getOutFaultInterceptors().add(soapInterceptor());
return endpoint;
}
#Bean
public DefaultInMemoryTokenStore defaulttokenStore(){
return new DefaultInMemoryTokenStore();
}
#Bean
SCTProvider customSCTProvider(){
return new BiPROTokenProvider();
}
Second problem:
I want to store my generated token in a default tokenstore from cxf. I read something about a tokenstore. http://cxf.apache.org/docs/ws-securitypolicy.html
In my opinion I have to include the tokenstore in the enpointproperties from service implementation.
#WebService
(
portName = "wst:UserPasswordLogin",
serviceName = "SecurityTokenService_2.6.0.1.0",
wsdlLocation = "src/main/resources/wsdl/SecurityTokenService- 2.6.0.1.0.wsdl",
endpointInterface = "net.bipro.namespace.SecurityTokenServicePortType"
)
#EndpointProperties({
#EndpointProperty(key = "ws-security.callback-handler", value="com.muki.endpoint.STSCallbackHandler"),
//#EndpointProperty(key = "ws-security.add.inclusive.prefixes", value="false"),
#EndpointProperty(key = "org.apache.cxf.ws.security.tokenstore.TokenStore", value="TOKEN_STORE_CACHE_INSTANCE"),
})
public class SecurityTokenEndpoint implements SecurityTokenServicePortType {
...
}
But if I include the tokenstore via the endpoint properties, I receive the following error.
<faultstring>java.lang.String cannot be cast to org.apache.cxf.ws.security.tokenstore.TokenStore</faultstring>
Can anybody help how I include a tokenstore and my custom SCT Provider?
I had similar issue but I use xml configuration. Instead of value I used value-ref and passed bean there:
<jaxws:endpoint
id="endpointId"
address="/foo/bar"
...
serviceName="ns1:ServiceName">
<jaxws:properties>
...
<entry key="org.apache.cxf.ws.security.tokenstore.TokenStore" value-ref="tokenStore" />
</jaxws:properties>
</jaxws:endpoint>
<bean id="tokenStore" class="org.apache.cxf.ws.security.tokenstore.MemoryTokenStore"/>
The error was gone but it wasn't working correctly - TokenStore wasn't set. So I tried another approach. Instead of editing endpoint I added that entry to bus config:
<cxf:bus>
<cxf:properties>
<entry key="org.apache.cxf.ws.security.tokenstore.TokenStore" value-ref="tokenStore" />
</cxf:properties>
</cxf:bus>
<bean id="tokenStore" class="org.apache.cxf.ws.security.tokenstore.MemoryTokenStore"/>
As for your question, I believe your syntax would look like:
#EndpointProperty(key = "org.apache.cxf.ws.security.tokenstore.TokenStore", ref="bean-name")
is it possible to list all REST services when using cxf with spring-boot? I've created ApplicationListener<ContextRefreshedEvent> and in there I would like to list all REST service urls which were registered for my cxf servlet. I've tried to poke around CXFServlet, ServletContext, cxf Endpoint and cxf Server classes but I can't figure it out. I've also tried to review wadl generator (feature) and swagger2 feature but they create url and content when request comes. Is it possible?
Thanks.
I would scan the #WebService annotations on the classpath, maybe it will help you:
#Autowired
private ClassPathScanningCandidateComponentProvider annotationScanner;
public List<ClassDocument> generate(String basePackage) throws ClassNotFoundException {
Set<BeanDefinition> candidateComponents = annotationScanner.findCandidateComponents(basePackage);
List<ClassDocument> classDocuments = new ArrayList<>();
for (BeanDefinition component : candidateComponents) {
ClassDocument classDocument = new ClassDocument();
Class<?> beanClass = Class.forName(component.getBeanClassName());
classDocument.setClassName(beanClass.getName());
String[] baseUrl = beanClass.getAnnotation(javax.jws.WebService.class).value();
addMethods(classDocument, beanClass, baseUrl);
classDocuments.add(classDocument);
}
return classDocuments;
}
I am trying to implement a RESTful web service client using Jersey/JAX-RS:
public class MyClient implements Closeable {
private Client client;
private FizzResource fizzResource;
// Several other resources omitted for brevity.
// Ctor, getters and setters, etc.
#Override
public void close() throws Exception {
client.destroy();
client.getExecutorService().shutdown();
}
}
public class FizzResource {
private Client client;
public Fizz saveFizz(Fizz fizz) {
WebResource webResource = client.resource("whatever");
ClientResponse response = webResource.accept(???).post(???);
if(response.getStatus() != 200) {
// do something...
} else {
// do something else...
}
}
}
My problem is that I do not want to work with JSON; instead I want to work directly with my entities (e.g. Fizz). I would like to use Jackson to automagically do the serialization between JSON and my entities (without me having to explicitly do the conversion inside each method), but I'm not seeing how this is possible/doable. Ideally my saveFizz method might look like:
public Fizz saveFizz(Fizz fizz) {
WebResource webResource = client.resource("whatever");
ClientResponse response = webResource.accept("application/json").post(fizz);
if(response.getStatus() != 200) {
throw new RuntimeException("Errors are bad, mkay?");
}
Fizz fizz = response.extractSomehow();
return fizz;
}
Assume my Fizz class is already annotated with the correct Jackson annotations (JsonProperty, etc.).
Any ideas?
You're using Jersey 1.x, so have a look at the user guide for JSON/POJO support
First thing: We need to make sure you have the jersey-json module
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-json</artifactId>
<version>${jersey-version}</version>
</dependency>
This module will have the needed MessageBodyReader and MessageBodyWriter that will read and write you POJOs to and from JSON
Second thing: We need to make sure we enable the POJO mapping support feature. Both with the server/application and with the client
Server with web.xml
<init-param>
<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
<param-value>true</param-value>
</init-param>
Server programmatic
public class MyApplication extends PackagesResourceConfig {
public MyApplication() {
getFeatures()..put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
}
}
See other Deployment Options
Client Config
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING,
Boolean.TRUE);
Client client = Client.create(clientConfig);
Third thing: We just need to make sure our resource method are annotated properly and we make the client call properly (to allow the correct writers/readers to be discovered).
For methods accepting JSON, it should annotated with #Consumed("application/json") and if the method also produces a response in JSON, it should also be annotated with #Produces("application/json"). So it depends on the semantics of your method, which annotations to include, it could be one or both.
For the client, as long as we have to correct configuration, extracting the Java Object, is just a matter of calling a getXxx with the Java type.
public void testGetFizz() {
// Directly extact
Fizz fizz = r.path("fizz").accept("application/json").get(Fizz.class);
System.out.println(fizz);
// Extract from ClientResponse
ClientResponse response = r.path("fizz").
accept("application/json").get(ClientResponse.class);
Fizz fizz1 = response.getEntity(Fizz.class);
System.out.println(fizz1);
}
Here are other pieces of code I used for my test
#Path("/fizz")
public class FizzResource {
#POST
#Consumes("application/json")
public Response postFizz(Fizz fizz) {
System.out.println("==== Created Fizz ===");
System.out.println(fizz);
System.out.println("=====================");
return Response.created(null).build();
}
#GET
#Produces("application/json")
public Response getFizz() {
Fizz fizz = new Fizz(1, "fizz");
return Response.ok(fizz).build();
}
}
Server config
ResourceConfig resourceConfig = new PackagesResourceConfig("test.json.pojo");
resourceConfig.getFeatures().put(
JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
Client config
ClientConfig clientConfig = new DefaultClientConfig();
clientConfig.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING,
Boolean.TRUE);
Client client = Client.create(clientConfig);
r = client.resource(Main.BASE_URI);
// r = WebResource
You could use Jackson's ObjectMapper:
final ObjectMapper mapper = new ObjectMapper();
mapper.readValue(response.getEntity(String.class), Fizz.class);
As long as Fizz is correctly annotated, this should do what you are looking for.
There are other options as well, which usually involve implementing custom providers.
If you include (supposing you use maven)
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>${jersey.version}</version>
</dependency>
then you will have automagically conversion without setting up anything. You can write functions like:
#POST
#Consumes(APPLICATION_JSON)
#Produces(APPLICATION_JSON)
public Activity createActivity(#Valid Activity activity) {
return activityDAO.createActivity(vuser,activity);
}
When working with Spring Security + CAS I keep hitting a small road block with the callback URL that is sent to CAS, ie the service property. I've looked at a bunch of examples such as this and this but they all use hard coded URLs (even Spring's CAS docs). A typical snip looks something like this...
<bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
<property name="service" value="http://localhost:8080/click/j_spring_cas_security_check" />
</bean>
First, I don't want to hard code the server name or the port since I want this WAR to be deployable anywhere and I don't want my application tied to a particular DNS entry at compile time. Second, I don't understand why Spring can't auto detect my application's context and the request's URL to automagically build the URL. The first part of that statement still stand but As Raghuram pointed out below with this link, we can't trust the HTTP Host Header from the client for security reasons.
Ideally I would like service URL to be exactly what the user requested (as long as the request is valid such as a sub domain of mycompany.com) so it is seamless or at the very least I would like to only specify some path relative my applications context root and have Spring determine the service URL on the fly. Something like the following...
<bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
<property name="service" value="/my_cas_callback" />
</bean>
OR...
<bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
<property name="service" value="${container.and.app.derived.value.here}" />
</bean>
Is any of this possible or easy or have I missed the obvious?
I know this is a bit old but I just had to solve this very problem and couldn't really find anything in the newer stacks.
We have multiple environments sharing the same CAS service (think dev, qa, uat and local development environments); we have the ability to hit each environment from more than one url (via the client side web server over a reverse proxy and directly to the back-end server itself). This means that specifying a single url is difficult at best. Maybe there's a way to do this but being able to use a dynamic ServiceProperties.getService(). I'll probably add some kind of server suffix check to ensure that the url isn't hijacked at some point.
Here's what I did to get the basic CAS flow working regardless of the URL used to access the secured resource...
Override the CasAuthenticationFilter.
Override the CasAuthenticationProvider.
setAuthenticateAllArtifacts(true) on the ServiceProperties.
Here's the long form of my spring configuration bean:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class CasSecurityConfiguration extends WebSecurityConfigurerAdapter {
Just the usual spring configuration bean.
#Value("${cas.server.url:https://localhost:9443/cas}")
private String casServerUrl;
#Value("${cas.service.validation.uri:/webapi/j_spring_cas_security_check}")
private String casValidationUri;
#Value("${cas.provider.key:whatever_your_key}")
private String casProviderKey;
Some externalized configuration parameters.
#Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(casValidationUri);
serviceProperties.setSendRenew(false);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
The key thing above is the setAuthenticateAllArtifacts(true) call. This will make the service ticket validator use the AuthenticationDetailsSource implementation rather than a hard-coded ServiceProperties.getService() call
#Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(casServerUrl);
}
Standard ticket validator..
#Resource
private UserDetailsService userDetailsService;
#Bean
public AuthenticationUserDetailsService authenticationUserDetailsService() {
return new AuthenticationUserDetailsService() {
#Override
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
String username = (token.getPrincipal() == null) ? "NONE_PROVIDED" : token.getName();
return userDetailsService.loadUserByUsername(username);
}
};
}
Standard hook to an existing UserDetailsService
#Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey(casProviderKey);
return casAuthenticationProvider;
}
Standard authentication provider
#Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setServiceProperties(serviceProperties());
casAuthenticationFilter.setAuthenticationDetailsSource(dynamicServiceResolver());
return casAuthenticationFilter;
}
Key here is the dynamicServiceResolver() setting..
#Bean
AuthenticationDetailsSource<HttpServletRequest,
ServiceAuthenticationDetails> dynamicServiceResolver() {
return new AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails>() {
#Override
public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
final String url = makeDynamicUrlFromRequest(serviceProperties());
return new ServiceAuthenticationDetails() {
#Override
public String getServiceUrl() {
return url;
}
};
}
};
}
Dynamically creates the service url from the makeDynamicUrlFromRequest() method. This bit is used upon ticket validation.
#Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint() {
#Override
protected String createServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
return CommonUtils.constructServiceUrl(null, response, makeDynamicUrlFromRequest(serviceProperties())
, null, serviceProperties().getArtifactParameter(), false);
}
};
casAuthenticationEntryPoint.setLoginUrl(casServerUrl + "/login");
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
This part uses the same dynamic url creator when CAS wants to redirect to the login screen.
private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){
return "https://howeverYouBuildYourOwnDynamicUrl.com";
}
This is whatever you make of it. I only passed in the ServiceProperties to hold the URI of the service that we're configured for. We use HATEAOS on the back-side and have an implementation like:
return UriComponentsBuilder.fromHttpUrl(
linkTo(methodOn(ExposedRestResource.class)
.aMethodOnThatResource(null)).withSelfRel().getHref())
.replacePath(serviceProperties.getService())
.build(false)
.toUriString();
Edit: here's what I did for the list of valid server suffixes..
private List<String> validCasServerHostEndings;
#Value("${cas.valid.server.suffixes:company.com,localhost}")
private void setValidCasServerHostEndings(String endings){
validCasServerHostEndings = new ArrayList<>();
for (String ending : StringUtils.split(endings, ",")) {
if (StringUtils.isNotBlank(ending)){
validCasServerHostEndings.add(StringUtils.trim(ending));
}
}
}
private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){
UriComponents url = UriComponentsBuilder.fromHttpUrl(
linkTo(methodOn(ExposedRestResource.class)
.aMethodOnThatResource(null)).withSelfRel().getHref())
.replacePath(serviceProperties.getService())
.build(false);
boolean valid = false;
for (String validCasServerHostEnding : validCasServerHostEndings) {
if (url.getHost().endsWith(validCasServerHostEnding)){
valid = true;
break;
}
}
if (!valid){
throw new AccessDeniedException("The server is unable to authenticate the requested url.");
}
return url.toString();
}
In Spring 2.6.5 spring you could extend org.springframework.security.ui.cas.ServiceProperties
In spring 3 the method is final you could get around this by subclassing the CasAuthenticationProvider and CasEntryPoint and then use with your own version of ServiceProperties and override the getService() method with a more dynamic implementation.
You could use the host header to calculate the the required domain and make it more secure by validating that only domains/subdomains under your control are used. Then append to this some configurable value.
Of course you would be at risk that your implementation was insecure though... so be careful.
It could end up looking like:
<bean id="serviceProperties" class="my.ServiceProperties">
<property name="serviceRelativeUrl" value="/my_cas_callback" />
<property name="validDomainPattern" value="*.mydomain.com" />
</bean>
use maven, add a property placeholder, and configure it in your build process
I tried to subclass CasAuthenticationProvider as Pablojim suggest, but solution is very easier! with Spring Expression Language (SPEL) you can obtain the url dinamically.
Example: <property name="service"
value="https://#{T(java.net.InetAddress).getLocalHost().getHostName()}:${application.port}${cas.service}/login/cascheck"/>
I have not tried this myself, but it seems Spring Security has a solution to this with the SavedRequestAwareAuthenticationSuccessHandler shown in the update of Bob's blog.