Monday, June 26, 2023

Spring Security - Step2 - Sample Spring Security application with In InMemoryUserDetailsManager

 In earlier, we config the user name and password in the appplication.properties file.

there wew could only configure one user name and password basically and details are hard code in file.

Here we create 

1. Two users under ADMIN and USER category and use them using "InMemoryUserDetailsManager" as we are not using a database upto now.

2. Create filters to permit all for basic "welcome" and authentication for other paths


Sample code

1. application.prperties - commented hard coded user name and password

server.port=8500

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root

# for Spring Boot 2
# spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQL5InnoDBDialect
# for Spring Boot 3
spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect
spring.jpa.show-sql=false
spring.jpa.generate-ddl=true
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

spring.application.name=spring-security-javaTechie

#spring.security.user.name=Alex
#spring.security.user.password=Pass1

2. POM - no changes - downgraded spring boot version to 3.0.1

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring-security-javaTechie</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-security-javaTechie</name>
<description>spring-security-bezkoder</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

</project>

3. Controller - no changes 

commented lines used in next steps  "@PreAuthorize

package com.example.springsecurityjavaTechie.controller;

import com.example.springsecurityjavaTechie.model.Product;
import com.example.springsecurityjavaTechie.model.UserInfo;
import com.example.springsecurityjavaTechie.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/products")
public class ProductController {

@Autowired
private ProductService service;

@GetMapping("/welcome")
public String welcome() {
return "Welcome this endpoint is not secure";
}

// @PostMapping("/new")
// public String addNewUser(@RequestBody UserInfo userInfo){
// return service.addUser(userInfo);
// }

@GetMapping("/all")
// @PreAuthorize("hasAuthority('ROLE_ADMIN')")
public List<Product> getAllTheProducts() {
return service.getProducts();
}

@GetMapping("/{id}")
// @PreAuthorize("hasAuthority('ROLE_USER')")
public Product getProductById(@PathVariable int id) {
return service.getProduct(id);
}
}

4. Product

package com.example.springsecurityjavaTechie.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Product {

private int productId;
private String name;
private int qty;
private double price;
}

6.ProductService

package com.example.springsecurityjavaTechie.service;

import com.example.springsecurityjavaTechie.model.Product;
import com.example.springsecurityjavaTechie.model.UserInfo;
import com.example.springsecurityjavaTechie.repository.UserInfoRepository;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Service
public class ProductService {

List<Product> productList = null;

// @Autowired
// private UserInfoRepository repository;
//
// @Autowired
// private PasswordEncoder passwordEncoder;

@PostConstruct
public void loadProductsFromDB() {
productList = IntStream.rangeClosed(1, 100)
.mapToObj(i -> Product.builder()
.productId(i)
.name("product " + i)
.qty(new Random().nextInt(10))
.price(new Random().nextInt(5000)).build()
).collect(Collectors.toList());
}


public List<Product> getProducts() {
return productList;
}

public Product getProduct(int id) {
return productList.stream()
.filter(product -> product.getProductId() == id)
.findAny()
.orElseThrow(() -> new RuntimeException("product " + id + " not found"));
}


// public String addUser(UserInfo userInfo) {
// userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));
// repository.save(userInfo);
// return "user added to system ";
// }
}

8. SecurityConfig

all changes are coming here.

3 beans are created (UserDetailsService , SecurityFilterChain,PasswordEncoder

package com.example.springsecurityjavaTechie.config;

import lombok.RequiredArgsConstructor;
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.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.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationFilter;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;



@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
UserDetailsService userDetailsService(PasswordEncoder passwordEncoder){
// authentication
UserDetails admin = User.withUsername("Alex")
.password(passwordEncoder.encode("Pwd1"))
.roles("ADMIN")
.build();
UserDetails user = User.withUsername("John")
.password(passwordEncoder().encode("Pwd2"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(admin,user);
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/products/welcome").permitAll()//just let any one access
.and()
.authorizeHttpRequests().requestMatchers("/products/**")
.authenticated().and().formLogin().and().build();

}

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}


Testing code

http://localhost:8500/products/welcome 

    this is like public page, but when you access other two urls "/all" and /{id} --> you need to provide the user name and password configured or hardcided in SecurityConfig class inside "UserDetailService"

http://localhost:8500/products/all

http://localhost:8500/products/{id}

Admin User = {Alex,Pwd1}

User = {John,Pwd2}



    




No comments:

Post a Comment