Thursday, June 29, 2023

Springboot - Maven - Issues with downloading dependencies

 Due to various reason, your dependencies might not downloaded. There is no exact answer as it depends on the issue comes with that situation.

Below is one solution you can try.


<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>


Tuesday, June 27, 2023

MS - chapter 4 - Create Config server - Problem with *

 Error


org.springframework.cloud.config.server.environment.FailedToConstructEnvironmentException: Could not construct context for config=student profile=default label=null includeOrigin=true; nested exception is while scanning an alias

 in 'reader', line 33, column 18:

            include: *

                     ^

unexpected character found  (0)

 in 'reader', line 33, column 19:

            include: *

                      ^


at org.springframework.cloud.config.server.environment.NativeEnvironmentRepository.findOne(NativeEnvironmentRepository.java:165) ~[spring-cloud-config-server-4.0.3.jar:4.0.3]

at org.springframework.cloud.config.server.environment.CompositeEnvironmentRepository.findOne(CompositeEnvironmentRepository.java:82) ~[spring-cloud-config-server-4.0.3.jar:4.0.3]

at org.springframework.cloud.config.server.environment.ObservationEnvironmentRepositoryWrapper.lambda$findOne$3(ObservationEnvironmentRepositoryWrapper.java:75) ~[spring-cloud-config-server-4.0.3.jar:4.0.3]

at io.micrometer.observation.Observation.lambda$observe$4(Observation.java:544) ~[micrometer-observation-1.11.1.jar:1.11.1]

at io.micrometer.observation.Observation.observeWithContext(Observation.java:603) ~[micrometer-observation-1.11.1.jar:1.11.1]

at io.micrometer.observation.Observation.observe(Observation.java:544) ~[micrometer-observation-1.11.1.jar:1.11.1]

at org.springframework.cloud.config.server.environment.ObservationEnvironmentRepositoryWrapper.findOne(ObservationEnvironmentRepositoryWrapper.java:75) ~[spring-cloud-config-server-4.0.3.jar:4.0.3]

at org.springframework.cloud.config.server.environment.EnvironmentEncryptorEnvironmentRepository.findOne(EnvironmentEncryptorEnvironmentRepository.java:64) ~[spring-cloud-config-server-4.0.3.jar:4.0.3]

at org.springframework.cloud.config.server.environment.EnvironmentController.getEnvironment(EnvironmentController.java:134) ~[spring-cloud-config-server-4.0.3.jar:4.0.3]

at org.springframework.cloud.config.server.environment.EnvironmentController.defaultLabelIncludeOrigin(EnvironmentController.java:116) ~[spring-cloud-config-server-4.0.3.jar:4.0.3]

at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]

at java.base/java.lang.reflect.Method.invoke(Method.java:577) ~[na:na]

at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:281) ~[spring-core-6.0.10.jar:6.0.10]

at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:482) ~[spring-cloud-context-4.0.3.jar:4.0.3]

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.10.jar:6.0.10]

at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:756) ~[spring-aop-6.0.10.jar:6.0.10]

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-6.0.10.jar:6.0.10]

at org.springframework.cloud.config.server.environment.EnvironmentController$$SpringCGLIB$$0.defaultLabelIncludeOrigin(<generated>) ~[spring-cloud-config-server-4.0.3.jar:4.0.3]

at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ~[na:na]

at java.base/java.lang.reflect.Method.invoke(Method.java:577) ~[na:na]

at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:207) ~[spring-web-6.0.10.jar:6.0.10]

at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:152) ~[spring-web-6.0.10.jar:6.0.10]

at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.0.10.jar:6.0.10]

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:884) ~[spring-webmvc-6.0.10.jar:6.0.10]

at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:797) ~[spring-webmvc-6.0.10.jar:6.0.10]

at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.0.10.jar:6.0.10]

at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1081) ~[spring-webmvc-6.0.10.jar:6.0.10]

at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:974) ~[spring-webmvc-6.0.10.jar:6.0.10]

at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1011) ~[spring-webmvc-6.0.10.jar:6.0.10]

at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903) ~[spring-webmvc-6.0.10.jar:6.0.10]

at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564) ~[tomcat-embed-core-10.1.10.jar:6.0]

at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.0.10.jar:6.0.10]

at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.10.jar:6.0]

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:205) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.10.jar:10.1.10]

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.0.10.jar:6.0.10]

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.10.jar:6.0.10]

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.0.10.jar:6.0.10]

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.10.jar:6.0.10]

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.0.10.jar:6.0.10]

at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.0.10.jar:6.0.10]

at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:174) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:149) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:166) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:482) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:341) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:391) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:894) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-10.1.10.jar:10.1.10]

at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]

Caused by: org.yaml.snakeyaml.scanner.ScannerException: while scanning an alias

 in 'reader', line 33, column 18:

            include: *

                     ^

unexpected character found  (0)

 in 'reader', line 33, column 19:

            include: *

                      ^

Reason

Problem with * character in the yaml file. 

#Enable actuator and all
management:
endpoints:
web:
exposure:
include: *

Here we give * 

solution

give the star character inside the quotes

#Enable actuator and all
management:
endpoints:
web:
exposure:
include: "*"




MS - chapter 4 - Create Config server

 Let's create config server to keep all the configurations and get the configurations from it.

Up to now, we have kept all the configurations in the same service. Here we are going to create a separate service to keep only the configurations.

Below are the steps

1. create spring boot application adding "config server" dependency

        <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency

2. add @EnableConfigServer annotation in the main class

3. create a "config" directory inside resources folder. After that create files with each service name

eg: student.yaml

copy all the configurations from service, here student service

4. Go to respective service (student service) and add the link to configuration server.

you can keep the service name in the respective property files inside each service.

As you shown client service to import details from config server , now student service can take properties from config server

spring:
application:
name: student
config:
import: optional:configserver:http://localhost:8088
cloud:
config:
server:
bootstrap: true

5. Not enough, you need to add the config server client dependency to the student service.

<!--config server client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

6. 


   

Below is the total code samples for Config server

main class

package com.demo.micro.configserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;

@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {

public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}

}


Student .yaml file inside "config" directory

server:
port: 8203
spring:
datasource:
url: jdbc:mysql://localhost:3306/student_mgt
username: root
password: root

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

eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#Enable actuator and all
management:
endpoints:
web:
exposure:
include: "*"


application.yaml

server:
port: 8088
spring:
profiles:
active: native

POM 

<?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.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.demo.micro</groupId>
<artifactId>config-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>config-server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2022.0.3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

Student Service - application.yaml

spring:
application:
name: student
config:
import: optional:configserver:http://localhost:8088
cloud:
config:
server:
bootstrap: true



Monday, June 26, 2023

Spring Security - Step4 - Sample Spring Security application - Take User Info from database

Before we have hard coded user details inside the "SecurityConfig" and loaded using "InMemoryUserDetailsManager"
Now let's take those details from database
To do that we need
1. create a entity/model class to map the database table --> UserInfo
2. Repository class --> UserInfoRepository
3. UserController class to save the details of new user
4. UserService class to connect Repository class
5.  UserInfoUserDetails config class which implements UserDetails class for configurations

Below is the code

SecurityConfig

package com.example.springsecurityjavaTechie.config;

import com.example.springsecurityjavaTechie.service.UserInfoUserDetailsService;
import lombok.RequiredArgsConstructor;
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.config.annotation.web.configuration.EnableWebSecurity;
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;


@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
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);
return new UserInfoUserDetailsService();
}

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

}

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

package com.example.springsecurityjavaTechie.config;

import com.example.springsecurityjavaTechie.model.UserInfo;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class UserInfoUserDetails implements UserDetails {

private String name;
private String password;
private List<GrantedAuthority> authorities;

public UserInfoUserDetails(UserInfo userInfo){
this.name = userInfo.getName();
this.password = userInfo.getPassword();
this.authorities = Arrays.stream(userInfo.getRoles().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public String getPassword() {
return password;
}

@Override
public String getUsername() {
return name;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}

Controllers

package com.example.springsecurityjavaTechie.controller;

import com.example.springsecurityjavaTechie.model.Product;
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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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";
}


@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);
}
}
package com.example.springsecurityjavaTechie.controller;

import com.example.springsecurityjavaTechie.model.UserInfo;
import com.example.springsecurityjavaTechie.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/users")
public class UserController {

@Autowired
UserService userService;

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

Model

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;
}

package com.example.springsecurityjavaTechie.model;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String email;
private String password;
private String roles;
}


Repository

package com.example.springsecurityjavaTechie.repository;


import com.example.springsecurityjavaTechie.model.UserInfo;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserInfoRepository extends JpaRepository<UserInfo, Integer> {
Optional<UserInfo> findByName(String username);

}
Service classes

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"));
}



}

package com.example.springsecurityjavaTechie.service;

import com.example.springsecurityjavaTechie.config.UserInfoUserDetails;
import com.example.springsecurityjavaTechie.model.UserInfo;
import com.example.springsecurityjavaTechie.repository.UserInfoRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.Optional;

public class UserInfoUserDetailsService implements UserDetailsService {

@Autowired
UserInfoRepository userInfoRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserInfo> userInfo = userInfoRepository.findByName(username);
return userInfo.map(UserInfoUserDetails::new)
.orElseThrow(()-> new UsernameNotFoundException("user not found "+username));
}
}

package com.example.springsecurityjavaTechie.service;

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

@Service
public class UserService {

@Autowired
PasswordEncoder passwordEncoder;

@Autowired
UserInfoRepository userInfoRepository;

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

Main class

package com.example.springsecurityjavaTechie;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringSecurityJavaTechieApplication {

public static void main(String[] args) {
SpringApplication.run(SpringSecurityJavaTechieApplication.class, args);
}

}

References

Spring Security - Step3 - Sample Spring Security application - add authorization

Previously once you authenticated with credentials, both "/all" and "{id}" urls were exposed to both users.

One is Admin and other oner is with USER privileges.

Now let's see handle authorization.

Case :

"products/all"  - Admin

"products/{id}" = USER


How to do that 

1. Add method level annotation for mention the authorization level 

@PreAuthorize("hasAuthority('ROLE_ADMIN')")

eg:

@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);
}

But this is not enough

2. We need to enable Method level security in our "SecurityConfig" class. For that we add annotation "@EnableMethodSecurity"

sample as shown below

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {


3. Now you can try to access after login with credentials

eg: logged with ADMIN {Alex,Pwd1}

Then you can access only /products/all

If you try to access /products/{id}

you will most probably get a message like

--------------------------

Access to localhost was denied

You don't have authorization to view this page.

HTTP ERROR 403

------------------------



Below are two full code samples


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


@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
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();
}
}


package com.example.springsecurityjavaTechie.controller;

import com.example.springsecurityjavaTechie.model.Product;
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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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);
}
}


Spring Security - Step2 - Sample Spring Security application with In InMemoryUserDetailsManager - getting HTTP ERROR 403

 I got below error when tried to access http://localhost:8500/products/all?continue


Access to localhost was denied

You don't have authorization to view this page.

HTTP ERROR 403

 Reason
I have missed "backslash" before "product"


@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();


    }


Solution

add a backslash

.authorizeHttpRequests().requestMatchers("/products/**")


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}