Kerberos broken after upgrading from Java6 to Java7 - spring-security

I have a working application using the spring-security kerberos extension, running on jboss, running java 6.
I'm in the process of upgrading my jvm from java 6 to java 7. When I do that, using the same codebase and the same keytab that worked on java 6, I now receive an error when using java 7.
I consistently receive:
java.security.PrivilegedActionException: GSSException: Failure unspecified at GSS-API level
(Mechanism level: Invalid argument (400) -
Cannot find key of appropriate type to decrypt AP REP - RC4 with HMAC)
I've tried to regenerate the keytab with the different /crypto options that have been described in other forums to no avail.
I have debugged the java 7 code and indeed, the classes that deal with reading the keytab on startup changed from 6 to 7. Could it be that my keytab isn't being read into the app correctly anymore? Some of the debug messages that I see on startup using Java6 don't appear anymore in 7, but I can't tell if that's by design or if that indicates something else is in play? Has anybody else had problems upgrading from 6 to 7 and had their kerberos integration break on them? Any advice?
With spnego and kerberos debug logging on for startup, my log shows:
2012-12-10 10:29:30,886 Debug is true storeKey true useTicketCache false useKeyTab true doNotPrompt true ticketCache is null isInitiator false KeyTab is jndi:/localhost/docfinity/WEB-INF/classes/config/common/security/http-docfinity.keytab refreshKrb5Config is false principal is HTTP/VMMSSDEV.TESTING.LOCAL#TESTING.LOCAL tryFirstPass is false useFirstPass is false storePass is false clearPass is false
2012-12-10 10:30:26,322 principal is HTTP/VMMSSDEV.TESTING.LOCAL#TESTING.LOCAL
2012-12-10 10:30:29,794 Will use keytab
2012-12-10 10:30:29,807 Ordering keys wrt default_tkt_enctypes list
2012-12-10 10:30:29,821 Config name: C:\Windows\krb5.ini
2012-12-10 10:30:29,827 Using builtin default etypes for default_tkt_enctypes
2012-12-10 10:30:29,832 default etypes for default_tkt_enctypes:
2012-12-10 10:30:29,837 17 aes128-cts-hmac-sha1-96
2012-12-10 10:30:29,839 16 des3-cbc-sha1-kd
2012-12-10 10:30:29,842 23 rc4-hmac
2012-12-10 10:30:29,846 1 des-cbc-crc
2012-12-10 10:30:29,849 3 des-cbc-md5
2012-12-10 10:30:29,851 .
2012-12-10 10:30:29,855 Commit Succeeded
One other question - you'll see it's trying to read C:\Windows\krb5.ini. I don't have such a file on my server. Do I need one? I didn't have one with java 6 either and that worked.
aaron

Yes! We patched SunJaasKerberosTicketValidator to look like this and it worked:
String keyTabPath = this.keyTabLocation.getURL().toExternalForm();
String runtimeVersion = System.getProperty("java.version");
if (runtimeVersion.startsWith("1.7"))
{
LOG.info("Detected jdk 7. Modifying keytabpath");
if (keyTabPath != null)
{
if (keyTabPath.startsWith("file:"))
{
keyTabPath = keyTabPath.substring(5);
}
}
}
LOG.info("KeyTabPath: " + keyTabPath);
LoginConfig loginConfig = new LoginConfig(keyTabPath, this.servicePrincipal,
this.debug);

Here are two potential issues that might be affecting you:
Java 7 appears to switch the default encryption type order. Details:
Java 7 Kerberos Issue - AES128 Corrupt checksum
You did't say what specific version of JDK 7 you are using, but there was a bug in earlier versions of JDK 7 that prevented loading keytab files via "file:" URLs:
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=2221392
Another user on SO worked around the last issue by modifying Spring source:
BadCredentialsException: Kerberos validation not succesfull

Change the keyTabLocation object to a string.
So private String keyTabLocaiton.
#Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.servicePrincipal, "servicePrincipal must be specified");
Assert.notNull(this.keyTabLocation, "keyTab must be specified");
// if (keyTabLocation instanceof ClassPathResource) {
// LOG.warn("Your keytab is in the classpath. This file needs special protection and shouldn't be in the classpath. JAAS may also not be able to load this file from classpath.");
// }
LoginConfig loginConfig = new LoginConfig(this.keyTabLocation, this.servicePrincipal,
this.debug);
Set<Principal> princ = new HashSet<Principal>(1);
princ.add(new KerberosPrincipal(this.servicePrincipal));
Subject sub = new Subject(false, princ, new HashSet<Object>(), new HashSet<Object>());
LoginContext lc = new LoginContext("", sub, null, loginConfig);
lc.login();
this.serviceSubject = lc.getSubject();
}
Also where the LoginConfig guy, set the isInitiator flag to true.
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
HashMap<String, String> options = new HashMap<String, String>();
options.put("useKeyTab", "true");
options.put("keyTab", this.keyTabLocation);
options.put("principal", this.servicePrincipalName);
options.put("storeKey", "true");
options.put("doNotPrompt", "true");
if (this.debug) {
options.put("debug", "true");
}
options.put("isInitiator", "true");
//options.put("isInitiator", "false");
return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), };
}
Hopefully this helps you fix your issue.

Related

Configuration of embedded Neo4j to run APOC procedures

I have cypher queries that make use of APOC functions. It works without problem if running the app directly but I would also like to test those queries. I tried to use following approach but getting an exception Unknown function 'apoc.coll.toSet'
My sample test class:
public class ApocTest {
private static Neo4j neo4j;
private static Driver driver;
#BeforeAll
static void initializeNeo4j() {
// Make sure that the plugins folder is listed in -cp
Path pluginDirContainingApocJar = Paths.get("src/main/resources/neo4j-plugins/");
if (!Files.exists(pluginDirContainingApocJar)) {
throw new IllegalArgumentException("Invalid path to plugins directory");
}
neo4j = Neo4jBuilders
.newInProcessBuilder()
.withDisabledServer()
.withFixture("CREATE (p1:Person)-[:knows]->(p2:Person)-[:knows]->(p3:Person)")
.withConfig(GraphDatabaseSettings.plugin_dir, pluginDirContainingApocJar)
.withConfig(GraphDatabaseSettings.procedure_unrestricted, List.of("apoc.*"))
.build();
driver = GraphDatabase.driver(neo4j.boltURI(), AuthTokens.none());
}
#AfterAll
static void stopNeo4j() {
driver.close();
neo4j.close();
}
#Test
public void testApoc(){
String query = "MATCH path=()-[:knows*2]->()\n" +
"RETURN apoc.coll.toSet(nodes(path)) AS nodesSet";
List<Object> nodesSet = driver.session()
.beginTransaction()
.run(query)
.single()
.get("nodesSet")
.asList();
assertEquals(3, nodesSet.size());
}
}
Any idea how to fix that?
This sample project on the github
Versions:
neo4j-java-driver: 4.1.1
neo4j-harness 4.1.6
org.neo4j.procedure: 4.1.0.5
Update:
So I tried to update:
Path pluginDirContainingApocJar = new File(
ApocConfig.class.getProtectionDomain().getCodeSource().getLocation().toURI())
.getParentFile().toPath();
That means that I don't need to manipulate with apoc jars, right?
But I'm still getting error:
Caused by: org.neo4j.kernel.lifecycle.LifecycleException: Component 'org.neo4j.procedure.impl.GlobalProceduresRegistry#27dc627a' was successfully initialized, but failed to start. Please see the attached cause exception "Unable to set up injection for procedure `CypherProcedures`, the field `cypherProceduresHandler` has type `class apoc.custom.CypherProceduresHandler` which is not a known injectable component.".
at org.neo4j.kernel.lifecycle.LifeSupport$LifecycleInstance.start(LifeSupport.java:463)
at org.neo4j.kernel.lifecycle.LifeSupport.start(LifeSupport.java:110)
at org.neo4j.graphdb.facade.DatabaseManagementServiceFactory.startDatabaseServer(DatabaseManagementServiceFactory.java:189)
... 58 more
Caused by: org.neo4j.kernel.api.exceptions.ComponentInjectionException: Unable to set up injection for procedure `CypherProcedures`, the field `cypherProceduresHandler` has type `class apoc.custom.CypherProceduresHandler` which is not a known injectable component.
at org.neo4j.procedure.impl.FieldInjections.createInjector(FieldInjections.java:98)
at org.neo4j.procedure.impl.FieldInjections.setters(FieldInjections.java:81)
at org.neo4j.procedure.impl.ProcedureCompiler.compileProcedure(ProcedureCompiler.java:264)
at org.neo4j.procedure.impl.ProcedureCompiler.compileProcedure(ProcedureCompiler.java:226)
at org.neo4j.procedure.impl.ProcedureJarLoader.loadProcedures(ProcedureJarLoader.java:114)
at org.neo4j.procedure.impl.ProcedureJarLoader.loadProceduresFromDir(ProcedureJarLoader.java:85)
at org.neo4j.procedure.impl.GlobalProceduresRegistry.start(GlobalProceduresRegistry.java:342)
at org.neo4j.kernel.lifecycle.LifeSupport$LifecycleInstance.start(LifeSupport.java:442)
... 60 more
Update 2 - working on 4.0:
For some reason downgrade to Neo4j 4.0, same version as in recommended, was enough to make it working. Now I won't spend more time to try to run it on Neo4j 4.1/4.2.
My code
Probably the way the path to the plugin directory was created. There's an example from Michael Simons here that explains using the neo4j classloader: https://github.com/michael-simons/neo4j-examples-and-tips/blob/master/examples/testing-ogm-against-embedded-with-apoc/src/test/java/org/neo4j/tips/testing/testing_ogm_against_embedded_with_apoc/ApplicationTests.java#L53

How can I disable the defaul aspnet core config change watcher?

I have an ASPNET core application running in a Docker container. I currently cannot start more than 64 containers (initially described here) of the image I have created.
By running the container interactively I found this error during the startup phase.
Unhandled Exception: System.IO.IOException: The configured user limit (128) on the number of inotify instances has been reached.
at System.IO.FileSystemWatcher.StartRaisingEvents()
at System.IO.FileSystemWatcher.StartRaisingEventsIfNotDisposed()
at System.IO.FileSystemWatcher.set_EnableRaisingEvents(Boolean value)
at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.TryEnableFileSystemWatcher()
at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.CreateFileChangeToken(String filter)
at Microsoft.Extensions.FileProviders.PhysicalFileProvider.Watch(String filter)
at Microsoft.Extensions.Configuration.FileConfigurationProvider.<.ctor>b__0_0()
at Microsoft.Extensions.Primitives.ChangeToken.OnChange(Func`1 changeTokenProducer, Action changeTokenConsumer)
at Microsoft.Extensions.Configuration.FileConfigurationProvider..ctor(FileConfigurationSource source)
at Microsoft.Extensions.Configuration.Json.JsonConfigurationSource.Build(IConfigurationBuilder builder)
at Microsoft.Extensions.Configuration.ConfigurationBuilder.Build()
at
Microsoft.AspNetCore.Hosting.WebHostBuilder.BuildCommonServices(AggregateException& hostingStartupErrors)
at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
at MyProjectName.Program.Main(String[] args) in /src/MyProjectName/Program.cs:line 10
Program.cs only has the default content
1 using Microsoft.AspNetCore;
2 using Microsoft.AspNetCore.Hosting;
3
4 namespace CmcIiiCgiSim
5 {
6 public class Program
7 {
8 public static void Main(string[] args)
9 {
10 CreateWebHostBuilder(args).Build().Run();
11 }
12
13 public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
14 WebHost.CreateDefaultBuilder(args)
15 .UseStartup<Startup>();
16 }
17 }
It appears that watching for configuration changes is a default setting of ASP or rather any ASPNET core app (see MS documentation)
My question is: How can I disable this option in the settings for the existing configuration files?
In the hope that this will also disable the filesystem watcher and get rid of the error.
I have found quite a few examples that state how it can be done programmatically for additional setting files. But in my case the configuration will not change and I'd rather have an image that is not limited to 64 containers.
The default builder is extremely opinionated and loads the appsettings with the reload already set. There isn't a way to replace the configuration that was set up in the default builder. As of .NET Core 3.1.7, this is the CreateDefaultBuilder method with the flags changed to stop the reload. I've run a server with this configuration and confirmed that no FileSystemWatchers are created.
public static IHostBuilder CreateDefaultBuilder(string[] args)
{
HostBuilder hostBuilder = new HostBuilder();
hostBuilder.UseContentRoot(Directory.GetCurrentDirectory());
hostBuilder.ConfigureHostConfiguration((Action<IConfigurationBuilder>) (config =>
{
config.AddEnvironmentVariables("DOTNET_");
config.AddCommandLine(args);
}));
hostBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
IHostEnvironment hostingEnvironment = hostingContext.HostingEnvironment;
config.AddJsonFile("appsettings.json", true, false)
.AddJsonFile("appsettings." + hostingEnvironment.EnvironmentName + ".json", true, false);
if (hostingEnvironment.IsDevelopment() &&
!string.IsNullOrEmpty(hostingEnvironment.ApplicationName))
{
Assembly assembly = Assembly.Load(new AssemblyName(hostingEnvironment.ApplicationName));
if (assembly != null)
config.AddUserSecrets(assembly, true);
}
config.AddEnvironmentVariables();
config.AddCommandLine(args);
})
.ConfigureLogging((hostingContext, logging) =>
{
int num = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 1 : 0;
if (num != 0)
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
if (num == 0)
return;
logging.AddEventLog();
})
.UseDefaultServiceProvider((Action<HostBuilderContext, ServiceProviderOptions>) ((context, options) =>
{
bool flag = context.HostingEnvironment.IsDevelopment();
options.ValidateScopes = flag;
options.ValidateOnBuild = flag;
}));
return hostBuilder;
}
The key part to this is the calls to AddJsonFile. The third parameter being passed as false, disabling the file system watcher.
I've managed to disable watching on json files (on linux) using the default builder with the following snippet:
IWebHostBuilder builder = WebHost.CreateDefaultBuilder();
builder.ConfigureAppConfiguration((context, config) =>
{
foreach (var s in config.Sources)
{
if (s is FileConfigurationSource)
((FileConfigurationSource)s).ReloadOnChange = false;
}
});
Now the system is not running out of inotify handles anymore.
For dotnet 5+ (for sure 5 and 6), you can use the config key hostBuilder:reloadConfigOnChange to disable the file watchers. You can see where it's used in dotnet 6.0.5.
I use environment variables for config, so I added this env var:
ASPNETCORE_hostBuilder__reloadConfigOnChange=false
I have an amd64 linux ubuntu docker container that runs a dotnet 6 app built with an x64 runtime identifier. I'm running this x86 container on macos aarch64 (apple silicon). The default host builder sets up a bunch of file watchers. Those file watchers use a file watching api that isn't implemented on my hacky cross-platform setup, and so they fail. The stack trace I get is like:
at System.IO.FileSystemWatcher.StartRaisingEvents()
at System.IO.FileSystemWatcher.StartRaisingEventsIfNotDisposed()
at System.IO.FileSystemWatcher.set_EnableRaisingEvents(Boolean value)
at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.TryEnableFileSystemWatcher()
at Microsoft.Extensions.FileProviders.Physical.PhysicalFilesWatcher.CreateFileChangeToken(String filter)
at Microsoft.Extensions.FileProviders.PhysicalFileProvider.Watch(String filter)
at Microsoft.Extensions.Configuration.FileConfigurationProvider.<.ctor>b__1_0()
at Microsoft.Extensions.Primitives.ChangeToken.OnChange(Func`1 changeTokenProducer, Action changeTokenConsumer)
at Microsoft.Extensions.Configuration.FileConfigurationProvider..ctor(FileConfigurationSource source)
at Microsoft.Extensions.Configuration.Json.JsonConfigurationSource.Build(IConfigurationBuilder builder)
at Microsoft.Extensions.Configuration.ConfigurationManager.AddSource(IConfigurationSource source)
at Microsoft.Extensions.Configuration.ConfigurationManager.Microsoft.Extensions.Configuration.IConfigurationBuilder.Add(IConfigurationSource source)
at Microsoft.Extensions.Configuration.ConfigurationExtensions.Add[TSource](IConfigurationBuilder builder, Action`1 configureSource)
at Microsoft.Extensions.Configuration.JsonConfigurationExtensions.AddJsonFile(IConfigurationBuilder builder, IFileProvider provider, String path, Boolean optional, Boolean reloadOnChange)
at Microsoft.Extensions.Configuration.JsonConfigurationExtensions.AddJsonFile(IConfigurationBuilder builder, String path, Boolean optional, Boolean reloadOnChange)
at Microsoft.Extensions.Hosting.HostingHostBuilderExtensions.<>c__DisplayClass11_0.<ConfigureDefaults>b__1(HostBuilderContext hostingContext, IConfigurationBuilder config)
at Microsoft.AspNetCore.Hosting.BootstrapHostBuilder.RunDefaultCallbacks(ConfigurationManager configuration, HostBuilder innerBuilder)
at Microsoft.AspNetCore.Builder.WebApplicationBuilder..ctor(WebApplicationOptions options, Action`1 configureDefaults)
at Microsoft.AspNetCore.Builder.WebApplication.CreateBuilder(WebApplicationOptions options)
More about:
The reloadConfigOnChange config key
Runtime identifiers

How to customize an existing Grails plugin functionality, modifying behavior of doWithSpring method

I am new to grails and while working with Spring Security LDAP plugin it was identified that it accepts the ldap server password in plain text only. The task in hand is to pass an encrypted password which is decrypted before it is consumed by the plugin during its initialization phase.
I have already searched for all possible blogs and stackoverflow questions but could not find a way to extend the main plugin class to simply override the doWithSpring() method so that i can simply add the required decryption logic for the Ldap server password. Any help here will be appreciated.
I have already seen and tried jasypt plugin but it also does not work well if the password is stored in some external file and not application yml. So I am looking for a solution to extend the Spring security plugin main class, add the required behavior and register the custom class.
EDIT
Adding the snippet from Grails LDAP Security plugin, which I am trying to override. So If i am successfully able to update the value of securityConfig object before the plugin loads, the purpose is solved.
Some snippet from the plugin:
def conf = SpringSecurityUtils.securityConfig
...
...
contextSource(DefaultSpringSecurityContextSource, conf.ldap.context.server) { // 'ldap://localhost:389'
authenticationSource = ref('ldapAuthenticationSource')
authenticationStrategy = ref('authenticationStrategy')
userDn = conf.ldap.context.managerDn // 'cn=admin,dc=example,dc=com'
**password = conf.ldap.context.managerPassword // 'secret'**
contextFactory = contextFactoryClass
dirObjectFactory = dirObjectFactoryClass
baseEnvironmentProperties = conf.ldap.context.baseEnvironmentProperties // none
cacheEnvironmentProperties = conf.ldap.context.cacheEnvironmentProperties // true
anonymousReadOnly = conf.ldap.context.anonymousReadOnly // false
referral = conf.ldap.context.referral // null
}
ldapAuthenticationSource(SimpleAuthenticationSource) {
principal = conf.ldap.context.managerDn // 'cn=admin,dc=example,dc=com'
**credentials = conf.ldap.context.managerPassword // 'secret'**
}
You don't need to override the doWithSpring() method in the existing plugin. You can provide your own plugin which loads after the one you want to affect and have your doWithSpring() add whatever you want to the context. If you add beans with the same name as the ones added by the other plugin, yours will replace the ones provided by the other plugin as long as you configure your plugin to load after the other one. Similarly, you could do the same think in resources.groovy of the app if you don't want to write a plugin for this.
You have other options too. You could write a bean post processor or bean definition post processor that affects the beans created by the other plugin. Depending on the particulars, that might be a better idea.
EDIT:
After seeing your comment below I created a simple example that shows how you might use a definition post processor. See the project at https://github.com/jeffbrown/postprocessordemo.
The interesting bits:
https://github.com/jeffbrown/postprocessordemo/blob/master/src/main/groovy/demo/SomeBean.groovy
package demo
class SomeBean {
String someValue
}
https://github.com/jeffbrown/postprocessordemo/blob/master/src/main/groovy/demo/SomePostProcessor.groovy
package demo
import org.springframework.beans.BeansException
import org.springframework.beans.MutablePropertyValues
import org.springframework.beans.PropertyValue
import org.springframework.beans.factory.config.BeanDefinition
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory
import org.springframework.beans.factory.support.BeanDefinitionRegistry
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor
class SomePostProcessor implements BeanDefinitionRegistryPostProcessor{
#Override
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition definition = registry.getBeanDefinition('someBean')
MutablePropertyValues values = definition.getPropertyValues()
PropertyValue value = values.getPropertyValue('someValue')
def originalValue = value.getValue()
// this is where you could do your decrypting...
values.addPropertyValue('someValue', "MODIFIED: ${originalValue}".toString())
}
#Override
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
https://github.com/jeffbrown/postprocessordemo/blob/master/grails-app/conf/spring/resources.groovy
beans = {
someBean(demo.SomeBean) {
someValue = 'Some Value'
}
somePostProcessor demo.SomePostProcessor
}
https://github.com/jeffbrown/postprocessordemo/blob/master/grails-app/init/postprocessordemo/BootStrap.groovy
package postprocessordemo
import demo.SomeBean
class BootStrap {
SomeBean someBean
def init = { servletContext ->
log.info "The Value: ${someBean.someValue}"
}
def destroy = {
}
}
At application startup you will see log output that looks something like this...
2017-10-23 19:04:54.356 INFO --- [ main] postprocessordemo.BootStrap : The Value: MODIFIED: Some Value
The "MODIFIED" there is evidence that the bean definition post processor modified the property value in the bean. In my example I am simply prepending some text to the string. In your implementation you could decrypt a password or do whatever you want to do there.
I hope that helps.
After trying Jasypt plugin and BeanPostProcessor solutions unsuccessfully for my use case, I found below solution to work perfectly.
To describe again the problem statement here,
a) we had to keep the passwords in an encrypted format inside properties files
b) and given we were packaging as a war file so the properties must not be kept inside the war to allow automated deployment scripts update the encrypted passwords depending on the environment
Jasypt plugin was a perfect solution for the use case a), but it was not able to cover the b) scenario
Moreover, the Grails LDAP Security plugin was getting loaded quite early hence Bean Post processors were also not helping out here.
Solution:
Created a new class by implementing the interface SpringApplicationRunListener. Extended its methods and parsed the properties file using YamlPropertySourceLoader
Sample code:
YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
PropertySource<?> applicationYamlPropertySource = loader.load(
"application.yml", new ClassPathResource("application.yml"),"default");
return applicationYamlPropertySource;
Once the properties were loaded inside the MapPropertySource object, parsed them for the encrypted values and applied the decryption logic.
This whole implementation was executed before any plugins were initialized during Grails bootup process solving the purpose.
Hope it will help others.

Spring Boot 1.3 WebSocket JSON converter Produces Invalid JSON

After upgrading to Spring Boot 1.3 (via Grails 3.1), the JSON output is rendered incorrectly. I believe it is because of the new auto-configured WebSocket JSON converter.
For example, with previous versions of Spring Boot (via Grails 3.0), using the following code:
#MessageMapping("/chat")
#SendTo("/sub/chat")
protected String chatMessage() {
def builder = new groovy.json.JsonBuilder()
builder {
type("message")
text("foobar")
}
builder.toString()
}
This would produce:
{"type": "message", "text": "foobar"}
With Spring Boot 1.3 (via Grails 3.1), that web socket produces the following:
"{\"type\":\"message\",\"text\":\"foobar\"}"
This is not valid JSON. How can I get rid of this new behavior and have it render the JSON as it was before? Please let me know if you have any suggestions.
I tried overriding the new configureMessageConverters method, but it did not have any effect.
looks like you are right. referenced commit shows questionable autoconfiguration imho.
especially b/c in the past, the converter ordering was intentionally changed to that StringMessageConverter takes precedence before MappingJackson2MessageConverter: https://github.com/spring-projects/spring-framework/commit/670c216d3838807fef46cd28cc82165f9abaeb45
for now, you can either disable that autoconfiguration:
#EnableAutoConfiguration(exclude = [WebSocketMessagingAutoConfiguration])
class Application extends GrailsAutoConfiguration { ... }
or, you add yet another StringMessageConverter to the top of the configured converters (maybe because you do want the boot autoconfiguration behavior because it is using the jackson ObjectMapper bean instead of creating a new one):
#Configuration
#EnableWebSocketMessageBroker
class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
boolean configureMessageConverters(List<MessageConverter> messageConverters) {
messageConverters.add 0, new StringMessageConverter()
return true
}
...
}
hope that helps.
I don't know how to do it in Grails but in Java you have to now pass the object instead of an object in the String class. I believe the old behavior was actually incorrect as it was returning a string as an object so there was no way to return a String that had JSON inside it as a String. So create an object with the structure you want and return it and you should be fine. I went through the same issue when upgrading from 1.2.X to 1.3.X. I am not exactly sure what change caused this but I think in the long run it is the correct thing to do.

WhatDoIHave and AssertConfigurationIsValid missing in StructureMap v3

I'm getting the dreaded "No default Instance is registered and cannot be automatically determined for type" even though I have set a registry for the default conventions.
To debug I am trying the WhatDoIHave and Asset... methods but these no longer exist on ObjectFactory. Am I missing something or have these been removed in the latest version?
Jeremy Miller explains new improved error reporting in SM3 3.0 here.
"WhatDoIHave" remains in SM 3:
(taken from the above blog entry)
[Test]
public void what_do_I_have()
{
var container = new Container(x => {
x.For<IDevice>().AddInstances(o => {
o.Type<ADevice>().Named("A");
o.Type<BDevice>().Named("B").LifecycleIs<ThreadLocalStorageLifecycle>();
o.Type<CDevice>().Named("C").Singleton();
});
x.For<IDevice>().UseIfNone<DefaultDevice>();
});
Debug.WriteLine(container.WhatDoIHave());
Debug.WriteLine(container.WhatDoIHave(pluginType:typeof(IDevice)));
}

Resources