Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
@Configuration: The class is marked as a configuration class, and it defines Spring Beans.SecurityFilterChain: This crucial Bean within the Spring Security filter chain, allows us to configure the rules and conditions for security along different paths in our application. We disable CSRF protection as it is not needed for REST APIs and define stateless session management. Furthermore, we specify URL patterns and the associated security settings, permitting everyone to access the signup and signin endpoints, whereas protecting all other endpoints. Finally, we ensure that the JWT filter is applied before the UsernamePasswordAuthenticationFilter to extract and verify the token.UserDetailsADMIN<ProjectName>
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ ├── configs
| | | ├── controllers
| | | ├── dto
| | | ├── filters
| | | ├── models
| | | ├── repositories
| | | ├── services
│ │ └── resources
│ └── test
│ └── java
├── .gitignore
├── mvnw
├── mvnw.cmd
├── pom.xml
└── README.md<dependencies>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.220</version>
</dependency>
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<version>3.1.2</version>
</dependency>
</dependencies># H2 Database Configuration
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.h2.console.enabled=true
spring.datasource.initialize=true
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# Logging
logging.level.root=WARN
logging.level.web=INFO
logging.level.com.example=DEBUG
logging.level.org.springframework.security=DEBUG
spring.jpa.generate-ddl=true
# Generate a secret token.secret.key for our application
# node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
token.secret.key=<JWT Secret>
# JWT expiration is 1 hour
token.expirationms=3600000package dev.katyella.oauth.configs;
import dev.katyella.oauth.filters.JwtAuthenticationFilter;
import dev.katyella.oauth.services.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserService userService;
private final PasswordEncoder passwordEncoder;
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userService.userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder);
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(HttpMethod.POST, "/api/v1/signup", "/api/v1/signin").permitAll()
.requestMatchers(HttpMethod.GET, "/api/v1/test/**").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider()).addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}package dev.katyella.oauth.services;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
@Service
public class JwtService {
@Value("${token.secret.key}")
String jwtSecretKey;
@Value("${token.expirationms}")
Long jwtExpirationMs;
public String extractUserName(String token) {
return extractClaim(token, Claims::getSubject);
}
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String userName = extractUserName(token);
return (userName.equals(userDetails.getUsername())) && !isTokenExpired(token);
}
private <T> T extractClaim(String token, Function<Claims, T> claimsResolvers) {
final Claims claims = extractAllClaims(token);
return claimsResolvers.apply(claims);
}
private String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return Jwts
.builder()
.setClaims(extraClaims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + jwtExpirationMs))
.signWith(getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
private boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Key getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(jwtSecretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
}@RestController
@RequestMapping("/api/v1/test")
public class TestController {
@GetMapping("/anon")
public String anonEndPoint() {
return "everyone can see this";
}
@GetMapping("/users")
@PreAuthorize("hasRole('USER')")
public String usersEndPoint() {
return "ONLY users can see this";
}
@GetMapping("/admins")
@PreAuthorize("hasRole('ADMIN')")
public String adminsEndPoint() {
return "ONLY admins can see this";
}
@GetMapping("/role_dependent")
public String roleDependentEndPoint() {
// Get authenticated user
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// Check if admin role
if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
return "Data for admins";
} else {
String username = authentication.getName();
return "Data for user: " + username;
}
}
}@RestController
@RequestMapping("/api/v1")
@RequiredArgsConstructor
public class BookController {
@Autowired
private BookRepository bookRepository;
@GetMapping("/books")
public List<Book> getAllBooks() {
// Get the authenticated user
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
// If user is admin, return all books
if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
return bookRepository.findAll();
} else {
// Otherwise, return books created by the user
return bookRepository.findByCreatedBy(username);
}
}
@GetMapping("/books/{id}")
public ResponseEntity<Book> getBookById(@PathVariable(value = "id") Long bookId) throws ConfigDataResourceNotFoundException {
// Get the authenticated user
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName();
Book book = bookRepository.findById(bookId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Book not found for this id :: " + bookId));
// Check if the book belongs to the authenticated user or if the user is admin
if (book.getCreatedBy().equals(username) || authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
return ResponseEntity.ok().body(book);
} else {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "You are not authorized to access this resource");
}
}
// More endpoints for handling creation, updating, deleting...POST http://localhost:8080/api/v1/signup
Content-Type: application/json
{
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]",
"password": "password"
}
POST http://localhost:8080/api/v1/signin
Content-Type: application/json
{
"email": "[email protected]",
"password": "password"
}POST http://localhost:8080/api/v1/signin
Content-Type: application/json
{
"email": "[email protected]",
"password": "password"
}GET http://localhost:8080/api/v1/test/users
Authorization: Bearer {{USER_AUTH_TOKEN}}@RestController
@RequestMapping("/api/books")
public class BookController {
@Autowired
private BookService bookService;
// Method to retrieve a book by its ID
@GetMapping("/{id}")
public ResponseEntity<Book> getBookById(@PathVariable Long id) {
Book book = bookService.findBookById(id)
.orElseThrow(() -> new BookNotFoundException("Book with ID: " + id + " not found."));
return new ResponseEntity<>(book, HttpStatus.OK);
}
// Exception handler for BookNotFoundException
@ExceptionHandler(BookNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleBookNotFoundException(BookNotFoundException ex) {
// Log the exception details for debugging
Logger.log(ex.getMessage());
// Return a user-friendly message and the appropriate HTTP status
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body("Book not found. Please check the provided book ID.");
}
// ... other methods or exception handlers ...
}Spring Boot application for web scraping with JSoup
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
@SpringBootApplication
@EnableScheduling
public class WebScrapingApplication {
public static void main(String[] args) {
SpringApplication.run(WebScrapingApplication.class, args);
}
@Service
public static class WebScrapingService {
@Scheduled(fixedRate = 10000) // Runs every 10 seconds
public void scrapeWebsite() {
try {
Document doc = Jsoup.connect("https://example.com").get();
String title = doc.title();
System.out.println("Website Title: " + title);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}# Define properties
bookstore.name=My Awesome Bookstore
bookstore.location=Main Street, SpringfieldEffectively use Lombok for logging while cautiously managing entities with lazy-loaded relationships to circumvent potential performance issues.
@Component
public class BookstoreProperties {
@Value("${bookstore.name}")
private String name;
@Value("${bookstore.location}")
private String location;
// getters and setters
public void printBookstoreDetails() {
System.out.println("Bookstore Name: " + name);
System.out.println("Bookstore Location: " + location);
}
}bookstore.location@Slf4j@DatatoString()import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.*;
import java.util.Set;
@Entity
@Getter
@Setter
@ToString(exclude = "orders") // Exclude lazy-loaded fields from toString
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private Set<Order> orders; // Lazy-loaded relationship
}import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class UserService {
public void updateUser(User user) {
// Safe logging without initializing lazy-loaded 'orders'
log.info("Updating user: {}", user);
}
}Updating user: User(id=1, name=John Doe, [email protected])@ControllerAdvice
public class GlobalExceptionHandler {
// Global exception handler for BookNotFoundException
@ExceptionHandler(BookNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<String> handleBookNotFoundException(BookNotFoundException ex) {
// Log the exception details for debugging
Logger.log(ex.getMessage());
// Return a user-friendly message and the appropriate HTTP status
return ResponseEntity
.status(HttpStatus.NOT_FOUND)
.body("Book not found. Please check the provided book ID.");
}
// ... other global exception handlers ...
}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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;
@TestConfiguration
public class IntegrationTestConfig {
/**
* Configures a security filter chain to disable OAuth2 security for integration tests.
*
* @param http the {@link HttpSecurity} to configure
* @return the {@link SecurityFilterChain} configured to disable OAuth2 security
* @throws Exception if an error occurs while configuring security
*/
@Bean
public SecurityFilterChain disableOAuth2Security(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.anyRequest().permitAll())
.build();
}
}import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
@Slf4j
public class UserService {
private final UserRepository userRepository;
public User findUserById(Long id) {
log.info("Finding user by ID: {}", id);
return userRepository.findById(id).orElse(null);
}
}
import lombok.Data;
@Data
public class User {
private Long id;
private String name;
private String email;
}import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Service
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
private UserRepository userRepository;
@Autowired
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User findUserById(Long id) {
log.info("Finding user by ID: {}", id);
return userRepository.findById(id).orElse(null);
}
}
public class User {
private Long id;
private String name;
private String email;
public User() {}
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}
}In this quick recipe, we'll explore the proper and improper ways to implement CRUD (Create, Read, Update, Delete) operations in RESTful services
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.Map;
@Component
@Endpoint(id = "activeSessions")
public class ActiveSessionsEndpoint {
private final SessionRegistry sessionRegistry;
public ActiveSessionsEndpoint(SessionRegistry sessionRegistry) {
this.sessionRegistry = sessionRegistry;
}
@ReadOperation
public Map<String, Integer> activeSessionsCount() {
return Collections.singletonMap("activeSessions", sessionRegistry.getAllPrincipals().size());
}
}{
"activeSessions": 42
}@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// Create
@PostMapping
public ResponseEntity<Object> createUser(@RequestBody User user, UriComponentsBuilder uriBuilder) {
User createdUser = userService.saveUser(user);
// Create URI of the created user using UriComponentsBuilder parameter
URI location = uriBuilder.path("/api/users/{id}")
.buildAndExpand(createdUser.getId())
.toUri();
return ResponseEntity.created(location).build();
}
// Read
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
// Update
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
User updatedUser = userService.updateUser(id, userDetails);
return ResponseEntity.ok(updatedUser);
}
// Delete
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return ResponseEntity.noContent().build();
}
}@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserService userService;
// Create
@GetMapping("/createUser") // Incorrect HTTP method and URI
public User createUser(@RequestBody User user) {
return userService.saveUser(user);
}
// Read
@PostMapping("/getUser") // Incorrect HTTP method and URI
public User getUserById(@RequestParam Long id) {
return userService.findById(id);
}
// Update
@GetMapping("/updateUser/{id}") // Incorrect HTTP method and URI
public User updateUser(@PathVariable Long id, @RequestBody User userDetails) {
return userService.updateUser(id, userDetails);
}
// Delete
@GetMapping("/deleteUser") // Incorrect HTTP method and URI
public void deleteUser(@RequestParam Long id) {
userService.deleteUser(id);
}
}