In projects I’ve been on, it’s very common to have a Common or Shared project/module where shared code is kept. For example, you might have a IngestData project and a DigestData project and they both require a Common project where some common business logic lives. In this post, I’ll show how to use Spring to load properties not only from your application project, but your common project as well, and honour Spring profiles at the same time.
First, why does this not work out of the box?
Spring automatically picks up your application.properties or application.yml file from your main application, and will even parse that file for Profiles, or load other Profile-specific properties files that share the same root (such as application-UAT.properties). But if you put an application.properties file in your common project, it will be ignored.
Your first thought might be to use @PropertySource or @PropertySources, but this annotation loads an exact file name only, and will not load your property files with Profiles in the names. There is an open issue to make this a feature request at https://github.com/spring-projects/spring-boot/issues/24688, but the Spring team has some understable concerns about this. @PropertySource is discouraged for this use anyway. The official documents says:
While using @PropertySource on your @SpringBootApplication may seem to be a convenient way to load a custom resource in the Environment, we do not recommend it. Such property sources are not added to the Environment until the application context is being refreshed. This is too late to configure certain properties such as logging.* and spring.main.*which are read before refresh begins.
But the docs do mention how to accomplish this with a custom EnvironmentPostProcessor, and combining this code with a custom YAML PropertySourceFactory (https://www.baeldung.com/spring-yaml-propertysource) we can accomplish our desired behaviour:
public MyEnvironmentPostProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(final ConfigurableEnvironment, final SpringApplication application) {
environment.getPropertySources().addLast(createYamlResource("common.yml");
environment.getPropertySources().addLast(createPropertiesResource("common.properties");
environment.getPropertySources().addLast(createPropertiesResource("common-UAT.properties");
}
private Resource createYamlResource(String name) {
Resource path = new ClassPathResource(name);
EncodedResource encodedResource = new EncodedResource(path);
YamlPropertySourceFactory factory = new YamlPropertySourceFactory();
return factory.createPropertySource("name", encodedResource);
}
private Resource createPropertiesResource(String name) {
return new ClassPathResource(name);
}
private Class YamlPropertySourceFactory {
@Override
public PropertySource<?> createPropertySource(@Nullable final String name, final EncodedResource resource) throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(resource.getResource());
factory.setResources(resource.getResource());
factory.afterPropertiesSet();
Properties properties = factory.getObject();
String sourceName = name != null ? name : resource.getResource().getFilename();
return new PropertiesPropertySource(sourceName, properties);
}
}
Make sure the properties file exist: common.yml, common.properties, common-UAT.properties because the code above will not fail gracefully.
I finally decided to give up my Apple AirPort Extreme base station. This device has performed fabulously for over ten years. However it is starting to drop from the LAN occasionally.
Over the holidays I purchased an Ubiquiti Edgerouter ER-X to take over routing duties from the Airport Extreme. However I can no longer receive VoIP calls!
I have a typical home/SMB LAN setup. NAT allows my private internal devices to communicate with the Internet, so I shouldn’t need to have any custom firewall rules or port forwarding. Turned out my problem was SIP ALG.
An ALG, or Application Layer Gateway, helps traffic move across a NAT. But most SIP traffic doesn’t need help, and ironically, SIP ALG can end up corrupting SIP traffic routing instead of helping it route properly.
My AirPort Extreme didn’t support SIP ALG, because that’s a fairly advanced NAT function for some very specific setups (like STUN with symmetric NAT).
A quick look at my Edgerouter’s configuration confirmed that SIP ALG is enabled:
Linux ubnt 4.14.54-UBNT #1 SMP Wed Oct 28 16:53:18 UTC 2020 mips
Welcome to EdgeOS
ubnt@ubnt:~$ lsmod | grep sip
nf_nat_sip 7152 0
nf_conntrack_sip 17597 1 nf_nat_sip
nf_nat 14044 8 nf_nat_pptp,nf_nat_proto_gre,nf_nat_h323,nf_nat_sip,nf_nat_ftp,nf_nat_masquerade_ipv4,nf_nat_ipv4,nf_nat_tftp
nf_conntrack 62887 18 nf_nat_pptp,nf_conntrack_sip,nf_nat_h323,nf_conntrack_ftp,nf_nat_sip,nf_conntrack_ipv4,nf_conntrack_tftp,ipt_MASQUERADE,nf_conntrack_pptp,nf_nat_ftp,nf_conntrack_proto_gre,xt_CT,nf_nat_masquerade_ipv4,nf_conntrack_h323,xt_conntrack,nf_nat_ipv4,nf_nat_tftp,nf_nat
That’s easy enough to disable
ubnt@ubnt:~$ configure
[edit]
ubnt@ubnt# set system conntrack modules sip disable
[edit]
ubnt@ubnt# commit
[edit]
ubnt@ubnt# save
Saving configuration to '/config/config.boot'...
Done
[edit]
ubnt@ubnt# exit
exit
ubnt@ubnt:~$ lsmod | grep sip
ubnt@ubnt:~$
Hey!! 🙂 The phone’s ringing.
If you want some quiet-time again 😉 just re-enable:
ubnt@ubnt:~$ configure
[edit]
ubnt@ubnt# set system conntrack modules sip enable-indirect-media
[edit]
ubnt@ubnt# set system conntrack modules sip enable-indirect-signalling
[edit]
ubnt@ubnt# commit
[edit]
ubnt@ubnt# save
Saving configuration to '/config/config.boot'...
Done
[edit]
ubnt@ubnt# exit
I recently had to add a pure HTML login form to an existing Spring Boot application, integrating with Spring Security. All the top posts in Google mentioned how to do it with Thymeleaf, but nothing with just pure HTML. It took me about three hours of reading documentation and trail and error to get it.
I followed the official Spring IO Guide to get started. I created a simple Spring Boot app at Spring Initializr configured with Spring Web, Thymeleaf and Spring Security. My pom.xml ended up looking like this:
The overridden configure method just sets up a username and password to login with. Obviously in a real system, you would do something actually useful.
There’s a lot going on in there, so let’s break it down.
.csrf().disable()
From what I can tell, Spring’s Cross-Site Request Forgery solution is to add a nonce to the login page. Smart. But I can’t use it with a pure, static HTML solution, so I’ve disabled it.
.loginPage("/html_login.html")
When Spring decides it’s time to ask for a user’s name and password, it forwards to this URL. This is where our pure, static HTML file with the simple login form will go. Default is “/login”.
.loginProcessingUrl("/doLogin")
This is the URL Spring will expect the username + password parameters to be found (either GET or POST). Default is “/login”, but I found debugging easier using a unique URL.
.failureUrl("/login-error.html")
By default, Spring will redirect to the loginPage and append "?error" onto the URL. This means you’ll need to add Javascript to detect this and display an error message to the user. Instead, I opted to just go to a different page with a hard-coded error message. No Javascript required.
.defaultSuccessUrl("/welcome", true)
This is obviously where Spring forwards to after a successful login. What’s isn’t obvious is the boolean parameter. true tells Spring to forget where the user was before and force a forward to the specified page.
That’s it! Finally we can add our two HTML pages into the “src/main/resources/static” directory. And test our work.
Running the Spring Boot application and pointing my web browser to http://localhost:8080/ :
Great success! But I don’t really like the hardcoded error message, nor disabling CSRF. I decided to try the Thymeleaf version and let management see the benefit for themselves.
Configure app for a dynamic HTML (Thymeleaf template) login form
First, we’ll need to tweak our custom LoginConfiguration class:
The loginPage is set to the default “/login”. But we’re going to intercept this call using a Spring Web @Controller to inject error messages and whatnot. We’ll need to add another class:
package ca.hendriks.logindemo;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class LoginController {
@RequestMapping("/welcome")
@ResponseBody
public String index() {
String time = LocalTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME);
return "Hello, world! Time is " + time;
}
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String thymeLeafLogin(Model model, String error, String logout) {
if (error != null)
model.addAttribute("errorMsg", "Your username and password are invalid.");
if (logout != null)
model.addAttribute("msg", "You have been logged out successfully.");
return "thyme_login.html";
}
}
The “/login” @RequestMapping we just wrote will inspect the Model for error messages and pass them on to the Thymeleaf template for display. The method ends by returning our destination – the Thymeleaf template file, which must be created in the templates directory of the class path.
Running the Spring Boot application and pointing my web browser to http://localhost:8080/ :
As the Spring article mentions, CSRF is also needs to logout, so you will have to use another Thymleaf template with a form to submit the logout request.
I feel that Thymeleaf is definitely the better way to go, but make sure you create the template file in the {CLASSPATH}/templates directory.
SSH is an application that allows you to remotely login and access another computer’s terminal. Like telnet, but with encryption. Usually the SSH application on the remote side will demand a login and password before letting you in.
There’s a quicker, more secure way of logging in if you take the time to set it up. By creating SSH keys (shared by both sides of the connection), SSH will login without requiring your password. Here’s how to set that up (in macOS Big Sur).
1. Generate the SSH keys
The SSH keys are a public/private pair (aka asymmetric cryptography). The private key should never be shared, and this one will live on the computer you are connecting from. The public key should be moved or copied to the computer you are connecting to.
In Terminal, enter the following command:
ssh-keygen -t rsa
When prompted, accept the default for file location and type in a password (optional – I never do – but recommended). ssh-keygen will generate two files for you:
Your private key will be at .ssh/id_ra
Your public key will be at .ssh/id_rsa.pub
2. Copy your public key to the Edgerouter.
I’m going to temporarily dump mine in the /var/tmp directory. Substitute your own user and host for “ubnt” and “ubnt.local” below:
scp ~/.ssh/id_rsa.pub ubnt@ubnt.local:/var/tmp/
Close the Terminal – none of the remaining commands below are entered into the Mac!
3. Import the file into the Edgerouter configuration
SSH into the Edgerouter, and substitute your own user and filename for “ubnt” and “id_rsa.pub” below:
configure
load key ubnt /var/tmp/id_rsa.pub
set service ssh disable-password-authentication
commit
save
4. You’re Finished!
The public key will be used to encrypt messages that can only be decrypted with the private key. When you SSH to the target machine, it will create a secret message and when the requesting machine proves that it can read the message – voila! – you are allowed to login sans password.
5. Technology – No Place for the Weak
Now of course trouble started when I tried to upload an updated public key. Each time I ran the “load key” command, the Ubiquiti responded with
Cannot open configuration file /config/key: No such file or directory
I tried deleting the key – didn’t work:
delete system login user ubnt autentication public-keys
commit
save
I tried re-enabling password authentication – didn’t help:
delete service ssh disable-password-authentication
commit
save
Even trying to load the key via the GUI didn’t work. Whatever this /config/key file is, it is important! Where the hell did it go??
I finally found more robust commands that allowed me to import a new key:
set system login user ubnt authentication public-keys ubnt key AAAAB3NzaC1yc2EAAAADAQABAAABgQDIjd4bDv35Yk0M8zePhKkuDIfcpBjD0PGb93L+gV2SIpWNHmCRffRQjZ5NJYLs95eMuCLY/761shjf+mmxnO7PZvFSWnsxgqO4sTZgbM/NUEquQy88peHreg3xli3IdBhImNCdFpNmgACqEiRPVuJ0Q6zeiym0zqKw8I7QGD0w2qjstzn6YCyDuzw04rHLW8eHffZAXmOp5AzSwc1VmegxWkh8Yp/Nptt6hZ9of68kCfJCX3Eiad/GFKVrvXULypkllSlNbBAfLJ+k/3NuJNxDruODyBSDMSlbGKB0H5uEhrAxLkzDSRvrNSgOboyi+D86708kvMlKKnRwQj8xg4aHsYz4a5TSWSne4dK3ttkbBFXv4PNdGCgVutm0PjACiq2Ck51c24o7rdzGbgXiDn/mF8k292KJD4ia4cNK3zZOxSy+o0iBvsmncBLHsHqQopDTMgxJYkOAckYe2kHKy3YLGONF82utTp5f70AHNSMq2ttw6hGzDrr1tsUaM/VOPZ8=
set system login user ubnt authentication public-keys ubnt type ssh-rsa