Showing posts with label Spring Security. Show all posts
Showing posts with label Spring Security. Show all posts

Saturday, July 8, 2023

Springboot 3.1 - Spring security - Create Basic Oauth2 client - Continue

 Let's continue add security features for the basic application we have built. As you can see, we have used the basic inbuild features of the spring security where default username=user and with generated password.

So let's start configuring.

1. we will create class "SecurityConfig" and let's put that inside "config" package

2. Annotate with @Configuration and @EnableWebSecurity

import org.springframework.context.annotation.Configuration;

import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

3. create new bean with "SecurityFilterChain" with "HttpSecurity"

below is the sample

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.build();
}

So up to now, the full continue code of the "SecurityConfig" class would looks like below.

Note: we need to add Exception

package com.example.danwega.outh2.config;

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

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.build();
}
}

4. Now let's start adding security features or configure Spring security environment. We will start modifying bean we created "SecurityFilterChain"

let's authorize http requests.

  • we use authorizeHttpRequests
  • with this we use matchers requestMatchers
  •     For home anybody can go -> auth.requestMatchers("/").permitAll()
  •     For any other request use authentication --> auth.anyRequest().authenticated()

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/").permitAll();
auth.anyRequest().authenticated();
})
.build();
}

5. Now how are you going to login. So for now, let's give a formLogin with defaults

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/").permitAll();
auth.anyRequest().authenticated();
})
.formLogin(Customizer.withDefaults())
.build();
}

let's change the code to static import for "withDefaults"

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/").permitAll();
auth.anyRequest().authenticated();
})
.formLogin(withDefaults())
.build();
}

Now user have to log with user name and password to login

6. without just providing a formLogin, we need to provide a oauth2 login as we are using oauth2.

we add auth2Login with defaults for now.

.oauth2Login(withDefaults())
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/").permitAll();
auth.anyRequest().authenticated();
})
.oauth2Login(withDefaults())
.formLogin(withDefaults())
.build();
}

That's it for now

7. configure properties to say what oauth2 client, providers we provide.

let's use log level just to what's happening inside when we running the code

logging:
level:
org:
springframework:
security: TRACE

here we will try to configure for GitHub and Google. 

let's first try with GitHub

Github

click your top right corner icon showing you --> go to "settings" --> left side go to "developer settings" in left bottom corner --> select "OAuth2 app" in left panel

8. Let's Register new application. you can give your details as needed, below is sample set

Application name: Spring Security OAuth2 client

Homepage url : localhost:8210

Application description : optional for now

Authorization callback url : localhost:8210/login/oauth2/code/github

call back url is set according to documentation. when it is google  final part will be "google", instead of "github"

 localhost:8210/login/oauth2/code/google

Click Register Application to finish the process. This will go to page and show you "client secret" and some other information

9. 





Friday, July 7, 2023

Springboot 3.1 - Spring security - Create Basic Oauth2 client

 Let's try to see what is new and what is the default behavior for spring security with oauth2 client.

Let's create sample springboot web application using https://start.spring.io/

 you can give chose basic configuration as your wish. 

Here I use, Maven - Java 17,pAckaging : jar

dependencies: Web, Oauth2 client

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


Sample POM file would like this


<?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.example.danwega</groupId>
<artifactId>outh2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>outh2</name>
<description>Spring security- Oauth2</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

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

</project>

After that you can download the project and I use Idea to open the project.

let's create sample controller name "HelloController" to see the basic features

Below is the sample code

package com.example.danwega.outh2.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {

@GetMapping("/")
public String home(){
return "Hello - Home";
}

@GetMapping("secured")
public String secured(){
return "Hello - Secured";
}
}

Now you can run the application

we have set the port and name for the application , not needed . if not go with default.

spring:
application:
name: oauth2-client
server:
port: 8210

When you run, application will up in port 8210 as suggested and will have default generated password from spring security, as we have not configured.

Default user name : user

Default password( what ever generated password) : b584e3b2-5840-48a8-a412-f1fbe3dd4b04 

you can test the application with url

http://localhost:8210/secured

http://localhost:8210/

this will pop up for login page with user name and password.

So that's done, basic simple spring security application with oauth2-client


Below is the sample output when you run the main spring boot application class, output may differ

output

"C:\Program Files\Zulu\zulu-18\bin\java.exe" "-javaagent:C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 2022.3.2\lib\idea_rt.jar=60521:C:\Program Files (x86)\JetBrains\IntelliJ IDEA Community Edition 2022.3.2\bin" -Dfile.encoding=UTF-8 -Dsun.stdout.encoding=UTF-8 -Dsun.stderr.encoding=UTF-8 -classpath C:\Project\Tutorial\MicroService\Code\outh2\target\classes;C:\Users\USERNAME\.m2\repository\org\springframework\boot\spring-boot-starter\3.1.1\spring-boot-starter-3.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\boot\spring-boot\3.1.1\spring-boot-3.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\spring-context\6.0.10\spring-context-6.0.10.jar;C:\Users\USERNAME\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\3.1.1\spring-boot-autoconfigure-3.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\boot\spring-boot-starter-logging\3.1.1\spring-boot-starter-logging-3.1.1.jar;C:\Users\USERNAME\.m2\repository\ch\qos\logback\logback-classic\1.4.8\logback-classic-1.4.8.jar;C:\Users\USERNAME\.m2\repository\ch\qos\logback\logback-core\1.4.8\logback-core-1.4.8.jar;C:\Users\USERNAME\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.20.0\log4j-to-slf4j-2.20.0.jar;C:\Users\USERNAME\.m2\repository\org\apache\logging\log4j\log4j-api\2.20.0\log4j-api-2.20.0.jar;C:\Users\USERNAME\.m2\repository\org\slf4j\jul-to-slf4j\2.0.7\jul-to-slf4j-2.0.7.jar;C:\Users\USERNAME\.m2\repository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\spring-core\6.0.10\spring-core-6.0.10.jar;C:\Users\USERNAME\.m2\repository\org\springframework\spring-jcl\6.0.10\spring-jcl-6.0.10.jar;C:\Users\USERNAME\.m2\repository\org\yaml\snakeyaml\1.33\snakeyaml-1.33.jar;C:\Users\USERNAME\.m2\repository\org\springframework\boot\spring-boot-starter-oauth2-client\3.1.1\spring-boot-starter-oauth2-client-3.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\security\spring-security-config\6.1.1\spring-security-config-6.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\spring-aop\6.0.10\spring-aop-6.0.10.jar;C:\Users\USERNAME\.m2\repository\org\springframework\spring-beans\6.0.10\spring-beans-6.0.10.jar;C:\Users\USERNAME\.m2\repository\org\springframework\security\spring-security-core\6.1.1\spring-security-core-6.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\security\spring-security-crypto\6.1.1\spring-security-crypto-6.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\spring-expression\6.0.10\spring-expression-6.0.10.jar;C:\Users\USERNAME\.m2\repository\io\micrometer\micrometer-observation\1.11.1\micrometer-observation-1.11.1.jar;C:\Users\USERNAME\.m2\repository\io\micrometer\micrometer-commons\1.11.1\micrometer-commons-1.11.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\security\spring-security-oauth2-client\6.1.1\spring-security-oauth2-client-6.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\security\spring-security-oauth2-core\6.1.1\spring-security-oauth2-core-6.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\security\spring-security-web\6.1.1\spring-security-web-6.1.1.jar;C:\Users\USERNAME\.m2\repository\com\nimbusds\oauth2-oidc-sdk\9.43.3\oauth2-oidc-sdk-9.43.3.jar;C:\Users\USERNAME\.m2\repository\com\github\stephenc\jcip\jcip-annotations\1.0-1\jcip-annotations-1.0-1.jar;C:\Users\USERNAME\.m2\repository\com\nimbusds\content-type\2.2\content-type-2.2.jar;C:\Users\USERNAME\.m2\repository\com\nimbusds\lang-tag\1.7\lang-tag-1.7.jar;C:\Users\USERNAME\.m2\repository\org\springframework\security\spring-security-oauth2-jose\6.1.1\spring-security-oauth2-jose-6.1.1.jar;C:\Users\USERNAME\.m2\repository\com\nimbusds\nimbus-jose-jwt\9.31\nimbus-jose-jwt-9.31.jar;C:\Users\USERNAME\.m2\repository\org\springframework\boot\spring-boot-starter-web\3.1.1\spring-boot-starter-web-3.1.1.jar;C:\Users\USERNAME\.m2\repository\org\springframework\boot\spring-boot-starter-json\3.1.1\spring-boot-starter-json-3.1.1.jar;C:\Users\USERNAME\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.15.2\jackson-databind-2.15.2.jar;C:\Users\USERNAME\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.15.2\jackson-annotations-2.15.2.jar;C:\Users\USERNAME\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.15.2\jackson-core-2.15.2.jar;C:\Users\USERNAME\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.15.2\jackson-datatype-jdk8-2.15.2.jar;C:\Users\USERNAME\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.15.2\jackson-datatype-jsr310-2.15.2.jar;C:\Users\USERNAME\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.15.2\jackson-module-parameter-names-2.15.2.jar;C:\Users\USERNAME\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\3.1.1\spring-boot-starter-tomcat-3.1.1.jar;C:\Users\USERNAME\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\10.1.10\tomcat-embed-core-10.1.10.jar;C:\Users\USERNAME\.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\10.1.10\tomcat-embed-el-10.1.10.jar;C:\Users\USERNAME\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\10.1.10\tomcat-embed-websocket-10.1.10.jar;C:\Users\USERNAME\.m2\repository\org\springframework\spring-web\6.0.10\spring-web-6.0.10.jar;C:\Users\USERNAME\.m2\repository\org\springframework\spring-webmvc\6.0.10\spring-webmvc-6.0.10.jar;C:\Users\USERNAME\.m2\repository\org\slf4j\slf4j-api\2.0.7\slf4j-api-2.0.7.jar;C:\Users\USERNAME\.m2\repository\net\minidev\json-smart\2.4.11\json-smart-2.4.11.jar;C:\Users\USERNAME\.m2\repository\net\minidev\accessors-smart\2.4.11\accessors-smart-2.4.11.jar;C:\Users\USERNAME\.m2\repository\org\ow2\asm\asm\9.3\asm-9.3.jar com.example.danwega.outh2.Outh2Application




  .   ____          _            __ _ _


 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \


( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \


 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )


  '  |____| .__|_| |_|_| |_\__, | / / / /


 =========|_|==============|___/=/_/_/_/


 :: Spring Boot ::                (v3.1.1)


2023-07-08T11:30:14.134+05:30  INFO 23896 --- [           main] c.e.danwega.outh2.Outh2Application       : Starting Outh2Application using Java 18.0.1 with PID 23896 (C:\Project\Tutorial\MicroService\Code\outh2\target\classes started by {yourmachineName}in C:\Project\Tutorial\MicroService\Code\outh2)


2023-07-08T11:30:14.137+05:30  INFO 23896 --- [           main] c.e.danwega.outh2.Outh2Application       : No active profile set, falling back to 1 default profile: "default"


2023-07-08T11:30:15.330+05:30  INFO 23896 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8210 (http)


2023-07-08T11:30:15.342+05:30  INFO 23896 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]


2023-07-08T11:30:15.342+05:30  INFO 23896 --- [           main] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.10]


2023-07-08T11:30:15.458+05:30  INFO 23896 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext

2023-07-08T11:30:15.460+05:30  INFO 23896 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1246 ms

2023-07-08T11:30:15.916+05:30  WARN 23896 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: b584e3b2-5840-48a8-a412-f1fbe3dd4b04

This generated password is for development use only. Your security configuration must be updated before running your application in production.

2023-07-08T11:30:16.045+05:30  INFO 23896 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [org.springframework.security.web.session.DisableEncodeUrlFilter@163042ea, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@11d045b4, org.springframework.security.web.context.SecurityContextHolderFilter@3252747e, org.springframework.security.web.header.HeaderWriterFilter@3234474, org.springframework.security.web.csrf.CsrfFilter@1e692555, org.springframework.security.web.authentication.logout.LogoutFilter@d3f4505, org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@73aeef7d, org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@2c7db926, org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@1fbf088b, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@67022ea, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@2dd8ff1d, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@2bfaba70, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@1943c1f2, org.springframework.security.web.access.ExceptionTranslationFilter@6ef60295, org.springframework.security.web.access.intercept.AuthorizationFilter@7ddd84b5]


2023-07-08T11:30:16.100+05:30  INFO 23896 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8210 (http) with context path ''


2023-07-08T11:30:16.107+05:30  INFO 23896 --- [           main] c.e.danwega.outh2.Outh2Application       : Started Outh2Application in 2.479 seconds (process running for 2.901)


2023-07-08T11:31:08.174+05:30  INFO 23896 --- [nio-8210-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'


2023-07-08T11:31:08.174+05:30  INFO 23896 --- [nio-8210-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'


2023-07-08T11:31:08.175+05:30  INFO 23896 --- [nio-8210-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms



Monday, June 26, 2023

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}



    




Thursday, June 22, 2023

Spring Security - Sample Spring Security application with default settings

 Let's create a Spring boot app adding dependencies of Web, Spring security, Lombok.

As you have added Spring security, it will be enabled with Spring security by default.

To enable User name and password, i have added user name password to application.properties

This is not recommended way, just to test and demo purposes. 

By adding username and password, you can add only one credential.


Below is the code

<?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.0</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>

Application.properties

Note:

when you add spring secirity dependecy, by default Spring secirity will be enabled.

If you want to add user name and password, you add that using appplication.properties file

But this is not recommended, but you can try


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


Controller

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



@GetMapping("/all")

public List<Product> getAllTheProducts() {
return service.getProducts();
}

@GetMapping("/{id}")

public Product getProductById(@PathVariable int id) {
return service.getProduct(id);
}
}

 

Service

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;



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



}


Repo

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

}


Model classes

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


To test the application just open a browser and check your endpoints with your used ports

http://localhost:8500/products/welcome

Will ask user name and password.

Give the user name and password you have configured.

If you give a wrong details, it will say "Bad credentials"


http://localhost:8500/products/all


Friday, June 16, 2023

MS - Security - OAuth 2

 Few key points to remember

  • Protocol for token based authorization
  • Delegate access without sharing credentials. Grant client to perform certain actions on behalf of user
  • Technically, oAuth is not authenticating user. User needs to pre authenticated to receive an access token. ( note: most implement both together and most confused over the concept of authentication + authorization )
  • Can have 3rd party OAuth providers. eg: Google,Facebook, Github, your OAuth server


Key Items to Remember

Client

Authorization Server

Resource Owner

Resource Server


Key Words / Terms

  • Access Token - random string , human unreadable
  • Refresh Token - Same as Access Token which used to renew access when expired
  • Client-Id / Secret - Identify the app/client
  • Scope - Allowed permission
  • JWT - most used mechanism to pass information  between services. Support Encryption.


Grant Types

Authorization Code

Client Credentials

Implicit

Password

Device Code