Additional Security with Spring Security
Source: Spring Boot in Practice — Chapter 6 by Somnath Musib (Manning). Concepts rewritten for this course; companion samples: spring-boot-in-practice/repo ch06. Assumes Spring Security basics from Chapter 5.
HTTPS, secrets, registration flows, CAPTCHA, 2FA, OAuth2, and locking down Actuator — the extras real apps add after “form login works.”
Editors do not compile or run Spring Boot. Check uses sourceChecks only. Snippets above exercises show the usual shape with inline comments; type the code yourself. Paste is disabled on Spring Boot lessons.
What this lesson covers
- HTTPS (TLS) and redirecting HTTP → HTTPS
- Secrets with Spring Cloud Vault (vs plain
application.properties) - User registration, email verification, account lockout
- Remember-me, Google reCAPTCHA, TOTP 2FA
- OAuth2 login (e.g. Google)
- Securing Actuator endpoints by role
Book samples often use WebSecurityConfigurerAdapter. On Spring Security 6 / Boot 3, prefer a @Bean SecurityFilterChain. Concepts map 1:1; snippets below show the modern style where it matters.
1. Enable HTTPS
HTTP sends cleartext. TLS encrypts traffic. Steps:
- Obtain a certificate (trusted CA for prod;
keytool/opensslself-signed for demos) - Put the keystore on the classpath and set
server.ssl.* - Optionally force HTTPS in the security filter chain
- Optionally redirect port 8080 → 8443 with an extra Tomcat connector
server.ssl.key-store-type=PKCS12
server.ssl.key-store=classpath:keystore/sbip.p12 # keystore on classpath
server.ssl.key-store-password=${keystore} # prefer Vault/env — not plain text in git
server.ssl.key-alias=sbip
server.port=8443 # HTTPS listen port
http.requiresChannel(channel -> channel.anyRequest().requiresSecure()); // force HTTPS
Self-signed certs show browser warnings — expected in demos; use a CA in production.
server.ssl.key-store-type=PKCS12
server.ssl.key-store=classpath:keystore/sbip.p12
server.ssl.key-store-password=p@ssw0rd
server.ssl.key-alias=sbip
server.port=8443
2. Secrets with HashiCorp Vault
Putting passwords and API keys in application.properties in git is a common leak. Vault stores secrets; Spring Cloud Vault loads them at startup.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
Import the Cloud BOM, then:
spring.cloud.vault.host=localhost
spring.cloud.vault.port=8200
spring.cloud.vault.scheme=https # use TLS in production
spring.cloud.vault.authentication=token
spring.cloud.vault.token=${VAULT_TOKEN} # from env — not committed
spring.config.import=vault://secret/coursetracker
server.ssl.key-store-password=${keystore} # key stored in Vault
Demo setups sometimes use HTTP + root token in properties — never do that in production.
spring.config.import=vault://secret/coursetracker # load secret/coursetracker path
server.ssl.key-store-password=${keystore} # resolve keystore from Vault
3. User registration
Typical pieces:
| Piece | Role |
|---|---|
Form + UserDto | Capture + Bean Validation |
ApplicationUser @Entity | Persist to CT_USERS |
UserService | Map DTO → entity; encode password |
UserDetailsService | Bridge entity → Spring Security UserDetails |
RegistrationController | GET form / POST create |
SecurityFilterChain | Permit /adduser, /login; encode with BCryptPasswordEncoder |
applicationUser.setPassword(passwordEncoder.encode(userDto.getPassword())); // never store raw
User.withUsername(username)
.password(applicationUser.getPassword())
.roles("USER")
.disabled(!applicationUser.isVerified()) // gate on email verification later
.build();
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(); // hash passwords at rest
}
4. Email verification
Flow:
- On register, save user with
verified=false - Publish
UserRegistrationEvent(optionally async) - Listener sends mail via
JavaMailSender+spring-boot-starter-mail - Store a UUID in
CT_EMAIL_VERIFICATIONS; put Base64 id in activation link /verify/email?id=…marks user verified- Unverified login →
DisabledException→ custom failure handler → “activate account” page
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
spring.mail.host=smtp.example.com
spring.mail.port=587
spring.mail.username=${MAIL_USER}
spring.mail.password=${MAIL_PASSWORD} # never commit real credentials
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
if (exception instanceof DisabledException) {
redirectStrategy.sendRedirect(request, response, "/login-disabled");
}
5. Lock after failed logins
Listen to Security events:
AuthenticationFailureBadCredentialsEvent→ increment Guava (or similar) cacheAuthenticationSuccessEvent→ invalidate counter- In
UserDetailsService, if attempts ≥ N → throwLockedException
if (loginAttemptService.isBlocked(username)) {
throw new LockedException("User Account is Locked"); // temporary lock
}
Cache expireAfterWrite(1, TimeUnit.DAYS) auto-clears locks after 24h in the book’s design.
public UserDetails loadUserByUsername(String username) {
if (loginAttemptService.isBlocked(username)) {
throw new LockedException("User Account is Locked");
}
// load user …
}
6. Remember-me
- Login form checkbox named
remember-me(exact name) - Security DSL:
.rememberMe().key("…").rememberMeCookieName("…") - Provide a
UserDetailsServiceso tokens can be rebuilt
Hash-based token (cookie): convenient but stealable; key should be stable across restarts.
Persistent token: store series/token in DB — safer.
<input type="checkbox" name="remember-me" /> <!-- Spring Security looks for this name -->
http.rememberMe(remember -> remember
.key("remember-me-key") // HMAC secret; set explicitly
.rememberMeCookieName("course-tracker-remember-me"));
http.rememberMe(r -> r
.key("remember-me-key")
.rememberMeCookieName("course-tracker-remember-me"));
7. Google reCAPTCHA
Not a Spring feature — but common on registration:
- Site key in HTML (
g-recaptcha+api.js) - Secret in Vault/env (
captcha.secret.key) - On POST, read
g-recaptcha-responseand verify with Google’s siteverify API viaRestTemplate/WebClient - Reject registration if verification fails
Balance UX: some apps show CAPTCHA only after repeated failures.
8. Two-factor authentication (TOTP)
With Google Authenticator (or compatible apps):
- After password login, offer “Enable 2FA”
- Generate secret + QR (
googleauthlibrary); store inCT_TOTP_DETAILS - Confirm with a code → set
totpEnabledon user - Later logins: password → OTP page
- Custom filter / success handler checks authority
TOTP_AUTH_AUTHORITY, verifies code, then grantsROLE_USER
TOTP = shared secret + current time window. Treat QR/secret like a password.
if (user.isTotpEnabled()) {
authority = new SimpleGrantedAuthority("TOTP_AUTH_AUTHORITY"); // needs OTP step
} else {
authority = new SimpleGrantedAuthority("ROLE_USER");
}
9. OAuth2 login (e.g. Google)
- Register the app with the IdP → client id + secret
- Add
spring-boot-starter-oauth2-client - Properties under
spring.security.oauth2.client.registration.google.* .oauth2Login()alongside form login- Success handler → app home
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=email,profile
http.oauth2Login(oauth -> oauth
.loginPage("/login")
.successHandler(oauth2SuccessHandler)); // redirect after Google consent
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.scope=email,profile
10. Secure Actuator endpoints
Actuator data is sensitive. Pattern:
health→USERorENDPOINT_ADMIN- All other endpoints →
ENDPOINT_ADMINonly
UseEndpointRequest.to("health")/toAnyEndpoint().
http.authorizeHttpRequests(auth -> auth
.requestMatchers(EndpointRequest.to("health")).hasAnyRole("USER", "ENDPOINT_ADMIN")
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ENDPOINT_ADMIN")
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults());
.requestMatchers(EndpointRequest.to("health")).hasAnyRole("USER", "ENDPOINT_ADMIN")
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ENDPOINT_ADMIN")
Additional security quiz
1 / 4Best place for a production TLS keystore password?
Pick an answer to see if you are right.
Chapter checklist
| Feature | Idea |
|---|---|
| HTTPS | server.ssl.* + require secure channel + optional HTTP redirect |
| Vault | Externalize secrets; spring.config.import=vault://… |
| Registration | DTO → encoded password → UserDetailsService |
| Email verify | Disabled until link; mail starter + events |
| Lockout | Security events + cache + LockedException |
| Remember-me | Checkbox + DSL; prefer persistent tokens for higher risk |
| reCAPTCHA | Verify server-side with secret |
| 2FA | TOTP secret + QR + second factor filter |
| OAuth2 | Client registration + oauth2Login() |
| Actuator | Role-gate with EndpointRequest |
Full walkthroughs (QR pages, filters, email listeners) live in the book’s ch06 samples.
Next in the book: RESTful APIs with Spring Boot → Lesson 5.