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



Tuesday, July 4, 2023

Java Basics - Record

 Record is one of the cool features Java has and below are the difference with normal class Vs Record class and how it will help us to get rid of boilerplate code.

For understanding purpose, we create a simple class with two variables, name and email.

you can see how much code we need to write in legacy class to deal with just to deal with a record/object with two variables

  • two variables
  • Getters and setter
  • Constructor
  • equals and hashcode
  • toString() Method

In Record

    just the class name with variables inside parenthesis


Record

variables are final - so no setters

Record will create a canonocal constructor - with parametrs you have given, where in class it will create only default constructor without paramters

Can have instance methods.

Can have static methods

Static variables, cannot create instance variables - why , by default, Record is a final

Get the value of variables using the name and paranthesis eg:varibaleName()

Record has by default toString method

Cannot extend any class cause By default it extends Record class, But you can implement any interface

Has a compact constructor

Can have validations here

name of the record is enough to declare constructor with "public" access

eg: 

public record SampleRecord(String name, String email) {
 public SampleRecord {
if (name.isBlank()) {
throw new IllegalArgumentException("Name cannot be blank.");
}
}

}


Below are sample code blocks

package com.example.demo.records;

import java.util.Objects;

public class SampleClass {
private String name;
private String email;

public SampleClass(String name, String email) {
this.name = name;
this.email = email;
}

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 "SampleClass{" +
"name='" + name + '\'' +
", email='" + email + '\'' +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SampleClass that = (SampleClass) o;
return name.equals(that.name) && email.equals(that.email);
}

@Override
public int hashCode() {
return Objects.hash(name, email);
}
}
package com.example.demo.records;

public record SampleRecord(String name, String email) {

public static String STATIC_VARIABLE = "static_variable";
// Instance variables are not allowed as by default variables are final cause of Record
//public String instanceVarible ="instanceVairableNot Allowed";
public String instanceMethod() {
return name();
}

public static void staticMethod() {
System.out.println("From static method");
}


public SampleRecord {
if (name.isBlank()) {
throw new IllegalArgumentException("Name cannot be blank.");
}
}

}
package com.example.demo.records;

public class TestSampleRecord {
public static void main(String[] args) {
SampleClass sampleClass = new SampleClass("Roshan", "roshan@email.com");
System.out.println(sampleClass);

SampleRecord sampleRecord = new SampleRecord("Mahanama", "mahanama@email.com");
System.out.println("default toString method in Record");
System.out.println(sampleRecord);
System.out.println("From instance method :" + sampleRecord.instanceMethod());
SampleRecord.staticMethod();
System.out.println(new SampleRecord("", ""));
}
}

 Output

SampleClass{name='Roshan', email='roshan@email.com'}

default toString method in Record

SampleRecord[name=Mahanama, email=mahanama@email.com]

From instance method :Mahanama

From static method

Exception in thread "main" java.lang.IllegalArgumentException: Name cannot be blank.

at com.example.demo.records.SampleRecord.<init>(SampleRecord.java:16)

at com.example.demo.records.TestSampleRecord.main(TestSampleRecord.java:13)