Skip to main content

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.”

Pattern-check challenges

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
API versions

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:

  1. Obtain a certificate (trusted CA for prod; keytool / openssl self-signed for demos)
  2. Put the keystore on the classpath and set server.ssl.*
  3. Optionally force HTTPS in the security filter chain
  4. 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:

PieceRole
Form + UserDtoCapture + Bean Validation
ApplicationUser @EntityPersist to CT_USERS
UserServiceMap DTO → entity; encode password
UserDetailsServiceBridge entity → Spring Security UserDetails
RegistrationControllerGET form / POST create
SecurityFilterChainPermit /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:

  1. On register, save user with verified=false
  2. Publish UserRegistrationEvent (optionally async)
  3. Listener sends mail via JavaMailSender + spring-boot-starter-mail
  4. Store a UUID in CT_EMAIL_VERIFICATIONS; put Base64 id in activation link
  5. /verify/email?id=… marks user verified
  6. 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) cache
  • AuthenticationSuccessEvent → invalidate counter
  • In UserDetailsService, if attempts ≥ N → throw LockedException
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​

  1. Login form checkbox named remember-me (exact name)
  2. Security DSL: .rememberMe().key("…").rememberMeCookieName("…")
  3. Provide a UserDetailsService so 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:

  1. Site key in HTML (g-recaptcha + api.js)
  2. Secret in Vault/env (captcha.secret.key)
  3. On POST, read g-recaptcha-response and verify with Google’s siteverify API via RestTemplate / WebClient
  4. 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):

  1. After password login, offer “Enable 2FA”
  2. Generate secret + QR (googleauth library); store in CT_TOTP_DETAILS
  3. Confirm with a code → set totpEnabled on user
  4. Later logins: password → OTP page
  5. Custom filter / success handler checks authority TOTP_AUTH_AUTHORITY, verifies code, then grants ROLE_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)​

  1. Register the app with the IdP → client id + secret
  2. Add spring-boot-starter-oauth2-client
  3. Properties under spring.security.oauth2.client.registration.google.*
  4. .oauth2Login() alongside form login
  5. 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 → USER or ENDPOINT_ADMIN
  • All other endpoints → ENDPOINT_ADMIN only
    Use EndpointRequest.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")
Quiz

Additional security quiz

1 / 4

Best place for a production TLS keystore password?

Pick an answer to see if you are right.


Chapter checklist​

FeatureIdea
HTTPSserver.ssl.* + require secure channel + optional HTTP redirect
VaultExternalize secrets; spring.config.import=vault://…
RegistrationDTO → encoded password → UserDetailsService
Email verifyDisabled until link; mail starter + events
LockoutSecurity events + cache + LockedException
Remember-meCheckbox + DSL; prefer persistent tokens for higher risk
reCAPTCHAVerify server-side with secret
2FATOTP secret + QR + second factor filter
OAuth2Client registration + oauth2Login()
ActuatorRole-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.