Securing a Java web application typically means adding:
- Authentication — verifying who the user is.
- Authorization — deciding what the authenticated user can access.
- Session/token protection — keeping the login state secure.
- Transport and application hardening — HTTPS, CSRF protection, password hashing, etc.
Since your stack includes Spring MVC / Spring Data JPA / Jakarta EE, the most common approach is Spring Security.
1. Add Spring Security
If you use Maven:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
If this is a Spring Boot app, use:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. Create a Security Configuration
For modern Spring Security, define a SecurityFilterChain.
package com.example.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/css/**", "/js/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
)
.build();
}
}
This configuration means:
| URL | Access |
|---|---|
/, /login, static files |
Public |
/user/** |
USER or ADMIN |
/admin/** |
ADMIN only |
| Everything else | Must be logged in |
3. Store Users in the Database
A simple JPA entity could look like this:
package com.example.user;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import java.util.Set;
@Entity
@Table(name = "app_users")
@Getter
@Setter
public class User {
@Id
private Long id;
private String username;
private String password;
private boolean enabled = true;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(
name = "app_user_roles",
joinColumns = @JoinColumn(name = "user_id")
)
private Set<String> roles;
}
Example roles:
ROLE_USER
ROLE_ADMIN
Spring Security’s hasRole("ADMIN") checks for ROLE_ADMIN internally.
4. Create a Repository
package com.example.user;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
5. Implement UserDetailsService
Spring Security uses UserDetailsService to load users during login.
package com.example.security;
import com.example.user.User;
import com.example.user.UserRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class DatabaseUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public DatabaseUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(username));
return org.springframework.security.core.userdetails.User
.withUsername(user.getUsername())
.password(user.getPassword())
.authorities(user.getRoles().toArray(String[]::new))
.disabled(!user.isEnabled())
.build();
}
}
Register it as a bean:
@Bean
public UserDetailsService userDetailsService(UserRepository userRepository) {
return new DatabaseUserDetailsService(userRepository);
}
6. Hash Passwords with BCrypt
Never store plain-text passwords.
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
When registering a user:
user.setPassword(passwordEncoder.encode(rawPassword));
A stored password should look similar to:
$2a$10$...
7. Add Method-Level Authorization
You can also secure service methods.
Enable method security:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@Configuration
@EnableMethodSecurity
public class MethodSecurityConfig {
}
Then protect methods:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
@Service
public class ReportService {
@PreAuthorize("hasRole('ADMIN')")
public void deleteReport(Long reportId) {
// admin-only logic
}
@PreAuthorize("hasAnyRole('USER', 'ADMIN')")
public Object viewReport(Long reportId) {
// user/admin logic
return new Object();
}
}
8. Protect Against CSRF
For browser-based apps using sessions and forms, keep CSRF enabled.
Spring Security enables CSRF by default.
In a Thymeleaf form, include the CSRF token:
<form method="post" action="/profile">
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}" />
<button type="submit">Save</button>
</form>
For JSP:
<input type="hidden"
name="${_csrf.parameterName}"
value="${_csrf.token}" />
For stateless REST APIs using JWT, CSRF is often disabled because the API does not rely on cookies for authentication.
9. Use HTTPS
Always run authentication over HTTPS.
For production:
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=changeit
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=myapp
If your app is behind a reverse proxy, configure forwarded headers correctly so Spring knows the original request was HTTPS.
server.forward-headers-strategy=framework
10. Use Secure Session Cookies
Configure cookies as secure and HTTP-only.
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=lax
Use same-site=strict if your app does not require cross-site navigation flows.
11. Example REST API Authorization
For controller endpoints:
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AdminController {
@GetMapping("/admin/stats")
@PreAuthorize("hasRole('ADMIN')")
public String stats() {
return "admin stats";
}
}
Or configure URL-based access:
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasAnyRole("USER", "ADMIN")
12. JWT Option for Stateless APIs
If your frontend is separate, such as React, Angular, mobile, or another client, you may use JWT or OAuth2.
Typical setup:
.oauth2ResourceServer(oauth2 -> oauth2.jwt())
Then secure routes:
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
For production, prefer a standards-based identity provider such as:
- Keycloak
- Auth0
- Okta
- Microsoft Entra ID
- AWS Cognito
13. Important Security Checklist
Use this checklist before production:
- Hash passwords with BCrypt, Argon2, or PBKDF2
- Never store plain-text passwords
- Use HTTPS only
- Keep CSRF enabled for session-based browser apps
- Use secure HTTP-only cookies
- Validate all user inputs
- Apply authorization in the service layer for sensitive business operations
- Avoid exposing stack traces or internal errors
- Lock down admin endpoints
- Use least-privilege roles
- Add audit logging for sensitive actions
- Rate-limit login attempts
- Use MFA for admin users
- Keep dependencies updated
- Add security headers
- Do not put secrets in source code
Minimal Spring Security Setup
A compact working configuration could look like this:
package com.example.security;
import com.example.user.UserRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login", "/css/**", "/js/**").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.defaultSuccessUrl("/dashboard", true)
.permitAll()
)
.logout(logout -> logout
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
)
.build();
}
@Bean
public UserDetailsService userDetailsService(UserRepository userRepository) {
return new DatabaseUserDetailsService(userRepository);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
For most Java web applications, the recommended path is:
Spring Security + database-backed users + BCrypt password hashing + role-based authorization + HTTPS + CSRF protection.
