Spring Boot 3.2 Security Cheetsheet
Spring Boot 3.2 Security Cheetsheet
------------------
spring security introduciton
basics auth
basic auth with database
JWT
OAuth2
microservice security with keyclock
method level security: few annotation, sec line of defence ot the url based
sec
@PreAuthorized
@PostAuthorized
datasource:
url: jdbc:mysql://localhost:3306/demoms?useSSL=false
password: root
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
logging:
level:
org:
springframework:
web: DEBUG
with h2 database:
---------------
spring.application.name=secdemo1
spring.main.allow-circular-references=true
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
# Custom H2 Console URL
spring.h2.console.path=/h2
spring.jpa.hibernate.ddl-auto=update
logging.level.org.springframework.web: DEBUG
logging.level.org.hibernate: ERROR
@GetMapping(path = "mgr")
public String mgr(){
return "mgr";
}
@GetMapping(path = "clerk")
public String clerk(){
return "clerk";
}
}
@Component
@EnableWebSecurity
@EnableMethodSecurity
public class SecConfig {
@Bean
public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder){
UserDetails raj= User.withUsername("raj")
.password(passwordEncoder.encode("raj123"))
.roles("ADMIN")
.build();
UserDetails ekta= User.withUsername("ekta")
.password(passwordEncoder.encode("ekta123"))
.roles("MGR")
.build();
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(registry ->
registry.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy
.STATELESS));
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
Authorization:
----------------
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(registry ->
registry.requestMatchers("/admin/**").hasAnyRole("ADMIN")
.requestMatchers("/mgr/**").hasAnyRole("ADMIN","MGR")
.requestMatchers("/clerk/**").hasAnyRole("ADMIN","MGR","CLERK")
.requestMatchers("/home/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.
sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
step 3 a:
define user entity
---------------------
@Data
@NoArgsConstructor
@ToString
@Entity
@Table(name = "user_table_2")
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name="user_roles_2")
private List<String> roles= new ArrayList<>();
@Repository
public interface UserRepo extends JpaRepository<UserEntity, Integer> {
public UserEntity findByUsername(String userName);
}
@Service
@Transactional
public class UserServiceImpl implements UserService{
private UserRepo userRepo;
@Autowired
public UserServiceImpl(UserRepo userRepo) {
this.userRepo = userRepo;
}
@Override
public UserEntity findByUsername(String username) {
return userRepo.findByUsername(username);
}
@Override
public void addUserEntity(UserEntity userEntity) {
userRepo.save(userEntity);
}
}
@Autowired
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
UserEntity userEntity=userService.findByUsername(username);
if(userEntity==null)
throw new UsernameNotFoundException("Username/password is invalid");
//now problem: userEntity--->UserDetails(which spring sec understand)
return new SecUser(userEntity);
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.createAuthorityList(userEntity.getRoles());
}
@Override
public String getPassword() {
return userEntity.getPassword();
}
@Override
public String getUsername() {
return userEntity.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
userService.addUserEntity(new UserEntity("raj",
passwordEncoder.encode("raj123"), List.of("ROLE_ADMIN","ROLE_MGR","ROLE_CLERK")));
userService.addUserEntity(new UserEntity("ekta",passwordEncoder.encode("ekta123"),
List.of("ROLE_MGR","ROLE_CLERK")));
userService.addUserEntity(new UserEntity("gun",passwordEncoder.encode("gun123"),
List.of("ROLE_MGR","ROLE_CLERK")));
@Component
@EnableWebSecurity
@EnableMethodSecurity
public class SecConfig {
@Autowired
private DetailService userDetailsService;
@Bean
public AuthenticationProvider getAuthentication(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
//......
}
@Component
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecConfig {
//....
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(registry ->
registry.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy
.STATELESS));
return http.build();
}
//.....
Use annotation :
----------------
@RestController
public class HelloController {
@GetMapping(path = "home")
public String home(){
return "home";
}
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@GetMapping(path = "admin")
public String admin(){
return "admin";
}
@PreAuthorize("hasAuthority('ROLE_MGR') or hasAuthority('ROLE_CLERK')")
@GetMapping(path = "mgr")
public String mgr(){
return "mgr";
}
@PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_MGR') or
hasAuthority('ROLE_CLERK')")
@GetMapping(path = "clerk")
public String clerk(){
return "clerk";
}
}
* Exception handler
------------------------
AuthenticationEntryPoint: 401
AccessDeniedHandler: 403
@Service
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse
response,
AuthenticationException authException) throws IOException,
ServletException {
response.setHeader("busycoder-error", "Authentication Failure");
response.sendError(HttpStatus.UNAUTHORIZED.value(),
HttpStatus.UNAUTHORIZED.getReasonPhrase());
}
@Service
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException,
ServletException {
LocalDateTime currentTimeStamp = LocalDateTime.now();
String message = (accessDeniedException != null &&
accessDeniedException.getMessage() != null) ?
accessDeniedException.getMessage() : "Authorization failed";
String path = request.getRequestURI();
response.setHeader("busycoder-denied-reason", "Authorization
failed");
response.setStatus(HttpStatus.FORBIDDEN.value());
response.setContentType("application/json;charset=UTF-8");
// Construct the JSON response
String jsonResponse =
String.format("{\"timestamp\": \"%s\", \"status\":
%d, \"error\": \"%s\", \"message\": \"%s\", \"path\": \"%s\"}",
currentTimeStamp, HttpStatus.FORBIDDEN.value(),
HttpStatus.FORBIDDEN.getReasonPhrase(),
message, path);
response.getWriter().write(jsonResponse);
}
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecConfig {
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
//------------------------
//Authrization
@Bean
SecurityFilterChain chain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(registry->
registry.anyRequest().authenticated())
// .httpBasic(Customizer.withDefaults())
.httpBasic(hbc ->hbc.authenticationEntryPoint(authenticationEntryPoint)
)
.exceptionHandling(hbc-> hbc.accessDeniedHandler(accessDeniedHandler))
.sessionManagement(configure->
configure.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.build();
}
}
step
1. allow user to enter and /authenticate
and it will username and password and
if username and pw is correct it will produe jwt token
2. once user get jwt token for each request while accessing the
res u need allowed to pass jwt token then validate it
if it is correct you will provide with proper response
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoder;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtService {
public static final String
SECRET =
"5367566B59703373367639792F423F4528482B4D6251655468576D5A71347437";
Step 2: create an endpoint "authenticate" that allow user to get jwt token
-----------------------------------------------------------------------------
@Data
public class AuthRequest {
private String username;
private String password;
}
.authorizeHttpRequests(auth->
auth.requestMatchers("authenticate").permitAll()
.anyRequest().authenticated())
@RestController
public class HelloController {
@Autowired
private JwtService jwtService;
@Autowired
private AuthenticationManager authenticationManager;
@GetMapping(path = "home")
public String home() {
return "home ";
}
//3. craete a endpoint so that user can send his u/p and get token
@PostMapping(path = "authenticate")
public String authenticateAndGetToken(@RequestBody AuthRequest authRequest) {
Authentication authentication
=authenticationManager.
authenticate(new UsernamePasswordAuthenticationToken(
authRequest.getUsername(),
authRequest.getPassword()
));
if(authentication.isAuthenticated()){
return jwtService.generateToken(authRequest.getUsername());
}else {
throw new UsernameNotFoundException("user is invalid");
}
@PreAuthorize("hasAuthority('ROLE_MGR')")
@GetMapping(path = "mgr")
public String mgr(){
return "mgr ";
}
@PreAuthorize("hasAuthority('ROLE_MGR') or hasAuthority('ROLE_CLERK')")
@GetMapping(path = "clerk")
public String clerk(){
return "clerk ";
}
}
Step 3: write JwtAuthFilter that validate jwt token and put
UsernamePasswordAuthenticationToken in security context
-----------------------------------------------------------------------------
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import
org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
import org.springframework.web.filter.OncePerRequestFilter;
@Service
public class JwtAuthFilter extends OncePerRequestFilter {
@Autowired
private JwtService jwtService;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
response, FilterChain filterChain)
throws ServletException, IOException {
if(username!=null &&
SecurityContextHolder.getContext().getAuthentication()==null){
UserDetails
userDetails=userDetailsService.loadUserByUsername(username);
//username is correct , and we are going to get UNAuthToeken and put
that in SecurityContextHolder ....
if(jwtService.validateToken(token, userDetails)){
UsernamePasswordAuthenticationToken authToken=
new UsernamePasswordAuthenticationToken(userDetails, null,
userDetails.getAuthorities());
// authToken.setDetails(new
WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import
org.springframework.security.config.annotation.authentication.configuration.Authent
icationConfiguration;
import
org.springframework.security.config.annotation.method.configuration.EnableMethodSec
urity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import
org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import
org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigur
er;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilte
r;
import org.springframework.stereotype.Component;
@Component
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecConfig {
@Autowired
private DetailService userDetailsService;
@Autowired
private JwtAuthFilter jwtAuthFilter;
@Bean
public AuthenticationProvider getAuthentication(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws
Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth->
auth.requestMatchers("authenticate").permitAll()
.anyRequest().authenticated())
.httpBasic(Customizer.withDefaults())
.addFilterBefore(jwtAuthFilter,
UsernamePasswordAuthenticationFilter.class)
.sessionManagement(httpSecuritySessionManagementConfigurer ->
httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy
.STATELESS));
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration
config)throws Exception {
return config.getAuthenticationManager();
}
}
Ref:
-----
for csrf
https://www.javainuse.com/spring/boot_security_csrf