= jwt (JSON Web Token) =
JSON Web Token
 * https://tools.ietf.org/html/rfc7519
 * https://en.wikipedia.org/wiki/JSON_Web_Token
 * https://jwt.io/
 * https://www.devglan.com/spring-security/spring-boot-jwt-auth
 * https://www.baeldung.com/spring-security-oauth-jwt
 * https://www.tutorialspoint.com/spring_boot/spring_boot_oauth2_with_jwt.htm

== Overview ==
In authentication, when the user successfully logs in using their credentials, a JSON Web Token will be returned and must be saved locally (typically in local or session storage, but cookies can also be used), instead of the traditional approach of creating a session in the server and returning a cookie. 

The tokens are designed to be compact,[2] URL-safe,[3] and usable especially in a '''web-browser single-sign-on (SSO) context'''. JWT claims can be typically used to pass identity of authenticated users between an identity provider and a service provider, or any other type of claims as required by business processes.

This is a stateless authentication mechanism as the user state is never saved in server memory. The server's protected routes will check for a valid JWT in the '''Authorization header''', and if it is present, the user will be allowed to access protected resources. As JWTs are self-contained, all the necessary information is there, reducing the need to query the database multiple times. 

== Adapted tutorials point Spring boot + JWT example ==
 *  https://www.tutorialspoint.com/spring_boot/spring_boot_oauth2_with_jwt.htm

The example in the tutorial doesn't run out of the box. It doesn't explain how to load the private key (PEM). Also it relies on Java 8. If we try to use the example with Java 11 it doesn't work due to issues with xml bind.

=== Structure ===
{{{
.
├── build_image.sh
├── connect_container.sh
├── Dockerfile
├── get_token.sh
├── jwt.pem
├── pom.xml
├── run_container.sh
├── src
│   └── main
│       ├── java
│       │   └── com
│       │       └── tutorialspoint
│       │           └── websecurityapp
│       │               ├── AnonymousController.java
│       │               ├── CustomDetailsService.java
│       │               ├── CustomUser.java
│       │               ├── OAuth2Config.java
│       │               ├── OAuthDao.java
│       │               ├── SecurityConfiguration.java
│       │               ├── UserEntity.java
│       │               └── WebsecurityappApplication.java
│       └── resources
│           ├── application.properties
│           ├── data.sql
│           └── schema.sql
└── stop_container.sh
}}}

 * mkdir -p src/main/java/com/tutorialspoint/websecurityapp/
 * mkdir src/main/resources/
 * openssl genrsa -out jwt.pem 2048 # generate private key

=== run_container.sh ===
{{{#!highlight bash
docker run -d -p 8080:8080 --name test test_image
}}}

=== Dockerfile ===
{{{#!highlight bash
FROM openjdk:8-alpine
RUN apk add maven
RUN mkdir -p /usr/src/tutpoint-jwt
COPY . /usr/src/tutpoint-jwt
WORKDIR /usr/src/tutpoint-jwt
RUN mvn clean install
CMD ["java","-jar","target/websecurityapp-0.0.1-SNAPSHOT.jar"]
}}}

=== src/main/java/com/tutorialspoint/websecurityapp/SecurityConfiguration.java ===
{{{#!highlight java
package com.tutorialspoint.websecurityapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
	@Autowired
	private CustomDetailsService customDetailsService;

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

	@Override
	@Autowired
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(customDetailsService).passwordEncoder(encoder());
	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().anyRequest().authenticated().and().sessionManagement()
				.sessionCreationPolicy(SessionCreationPolicy.NEVER);

	}

	@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/anonymous/**", "/resources/**", "/static/**");
	}

	@Override
	@Bean
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}
}

}}}

=== src/main/java/com/tutorialspoint/websecurityapp/OAuth2Config.java ===
{{{#!highlight java
package com.tutorialspoint.websecurityapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.io.FileReader;
import java.io.BufferedReader;

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
	private String clientid = "tutorialspoint";
	private String clientSecret = "my-secret-key";

	@Autowired
	@Qualifier("authenticationManagerBean")
	private AuthenticationManager authenticationManager;

	private String getFileContent(String filename) {
		String ret = "";
		BufferedReader bufReader = null;
		try {
			bufReader = new BufferedReader(new FileReader(filename));
			String line = "";
			while (line != null) {
				line = bufReader.readLine();
				if (line != null) {
					ret += line;
				}
			}
			bufReader.close();
		} catch (Exception ex) {
		} finally {
		}

		return ret;
	}

	@Bean
	public JwtAccessTokenConverter tokenEnhancer() {

		JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
		String pemData = getFileContent("jwt.pem");
		converter.setSigningKey(pemData);
		converter.setVerifierKey(pemData);
		return converter;
	}

	@Bean
	public JwtTokenStore tokenStore() {
		return new JwtTokenStore(tokenEnhancer());
	}

	@Override
	public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
		endpoints.authenticationManager(authenticationManager).tokenStore(tokenStore())
				.accessTokenConverter(tokenEnhancer());
	}

	@Override
	public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
		security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
	}

	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.inMemory().withClient(clientid).secret(clientSecret).scopes("read", "write")
				.authorizedGrantTypes("password", "refresh_token").accessTokenValiditySeconds(20000)
				.refreshTokenValiditySeconds(20000);

	}
}

}}}

=== src/main/java/com/tutorialspoint/websecurityapp/CustomUser.java ===
{{{#!highlight java
package com.tutorialspoint.websecurityapp;

import org.springframework.security.core.userdetails.User;

public class CustomUser extends User {
   private static final long serialVersionUID = 1L;
   public CustomUser(UserEntity user) {
      super(user.getUsername(), user.getPassword(), user.getGrantedAuthoritiesList());
   }
} 
}}}

=== src/main/java/com/tutorialspoint/websecurityapp/UserEntity.java ===
{{{#!highlight java
package com.tutorialspoint.websecurityapp;

import java.util.ArrayList;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;

public class UserEntity {
	private String username;
	private String password;
	private String role;
	private Collection<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public Collection<GrantedAuthority> getGrantedAuthoritiesList() {
		return grantedAuthoritiesList;
	}

	public void setGrantedAuthoritiesList(Collection<GrantedAuthority> grantedAuthoritiesList) {
		this.grantedAuthoritiesList = grantedAuthoritiesList;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getRole() {
		return role;
	}

	public void setRole(String role) {
		this.role = role;
	}

}
}}}

=== src/main/java/com/tutorialspoint/websecurityapp/AnonymousController.java ===
{{{
package com.tutorialspoint.websecurityapp;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/anonymous")
public class AnonymousController {

	@ResponseBody
	@RequestMapping(value = "/dummy", produces = "application/json")
	// http://localhost:8080/anonymous/dummy
	public String getDummy() {
		return "dummy";
	}

}
}}}

=== src/main/java/com/tutorialspoint/websecurityapp/WebsecurityappApplication.java ===
{{{#!highlight java
package com.tutorialspoint.websecurityapp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@EnableAuthorizationServer
@EnableResourceServer
@RestController
public class WebsecurityappApplication {
	public static void main(String[] args) {
		SpringApplication.run(WebsecurityappApplication.class, args);
	}

	@PreAuthorize("hasRole('ROLE_ADMIN')")
	@RequestMapping(value = "/products")
	public String getProductName() {
		return "Honey";
	}

	@PreAuthorize("hasRole('ROLE_USER')")
	@RequestMapping(value = "/productsuser")
	public String getProductNameUsers() {
		return "HoneyUser";
	}

}
}}}

=== src/main/java/com/tutorialspoint/websecurityapp/CustomDetailsService.java ===
{{{#!highlight java
package com.tutorialspoint.websecurityapp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomDetailsService implements UserDetailsService {
   @Autowired
   OAuthDao oauthDao;

   @Override
   public CustomUser loadUserByUsername(final String username) throws UsernameNotFoundException {
      UserEntity userEntity = null;
      try {
         userEntity = oauthDao.getUserDetails(username);
         CustomUser customUser = new CustomUser(userEntity);
         return customUser;
      } catch (Exception e) {
         e.printStackTrace();
         throw new UsernameNotFoundException("User " + username + " was not found in the database");
      }
   }
} 
}}}

=== src/main/java/com/tutorialspoint/websecurityapp/OAuthDao.java ===
{{{#!highlight java
package com.tutorialspoint.websecurityapp;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Repository;

@Repository
public class OAuthDao {
	@Autowired
	private JdbcTemplate jdbcTemplate;

	public UserEntity getUserDetails(String username) {

		String userSQLQuery = "SELECT * FROM USERS WHERE USERNAME=?";
		List<UserEntity> list = jdbcTemplate.query(userSQLQuery, new String[] { username },
				(ResultSet resultSet, int rowNum) -> {

					UserEntity user = new UserEntity();
					user.setUsername(username);
					user.setPassword(resultSet.getString("PASSWORD"));
					user.setRole(resultSet.getString("ROLE"));
					return user;
				});
		
		if (list.size() > 0) {
			Collection<GrantedAuthority> grantedAuthoritiesList = new ArrayList<>();
			UserEntity user = list.get(0);
			GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(user.getRole());
			grantedAuthoritiesList.add(grantedAuthority);
			user.setGrantedAuthoritiesList(grantedAuthoritiesList);
			return user;
		}
		
		return null;
	}
}
}}}

=== src/main/resources/schema.sql ===
{{{#!highlight sql
CREATE TABLE USERS (ID INT PRIMARY KEY, USERNAME VARCHAR(45), PASSWORD VARCHAR(60), ROLE VARCHAR(60) );
}}}

=== src/main/resources/application.properties ===
{{{
security.oauth2.resource.filter-order=3 
logging.file=/tmp/testout.log
}}}

=== src/main/resources/data.sql ===
{{{#!highlight sql
INSERT INTO USERS (ID, USERNAME,PASSWORD, ROLE) VALUES (
   1, 'tutorialspoint@gmail.com','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG','ROLE_ADMIN');

INSERT INTO USERS (ID, USERNAME,PASSWORD, ROLE) VALUES (
   2, 'myemail@gmail.com','$2a$08$fL7u5xcvsZl78su29x1ti.dxI.9rYO8t0q5wk2ROJ.1cdR53bmaVG','ROLE_USER'); 
}}}

=== build_image.sh ===
{{{#!highlight bash
docker build -t test_image .
}}}

=== pom.xml ===
{{{#!highlight xml
<?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 
   http://maven.apache.org/xsd/maven-4.0.0.xsd">

	<modelVersion>4.0.0</modelVersion>
	<groupId>com.tutorialspoint</groupId>
	<artifactId>websecurityapp</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>
	<name>websecurityapp</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.9.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<!--  Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException  -->
		<!-- API, java.xml.bind module -->
		<dependency>
			<groupId>jakarta.xml.bind</groupId>
			<artifactId>jakarta.xml.bind-api</artifactId>
			<version>2.3.2</version>
		</dependency>

		<!-- Runtime, com.sun.xml.bind module -->
		<dependency>
			<groupId>org.glassfish.jaxb</groupId>
			<artifactId>jaxb-runtime</artifactId>
			<version>2.3.2</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>

		<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.security.oauth</groupId>
			<artifactId>spring-security-oauth2</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.security</groupId>
			<artifactId>spring-security-jwt</artifactId>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</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>
			</plugin>
		</plugins>
	</build>

</project>
}}}

=== get_token.sh ===
{{{#!highlight bash
ACCESS_TOKEN=$(curl -X POST --url http://localhost:8080/oauth/token --user tutorialspoint:my-secret-key --data 'grant_type=password&username=myemail@gmail.com&password=password' | jq -r '.access_token')
ACCESS_TOKEN_ADMIN=$(curl -X POST --url http://localhost:8080/oauth/token --user tutorialspoint:my-secret-key --data 'grant_type=password&username=tutorialspoint@gmail.com&password=password' | jq -r '.access_token')
#echo $ACCESS_TOKEN
curl -X GET http://localhost:8080/productsuser  --header "Authorization: Bearer $ACCESS_TOKEN"
curl -X GET http://localhost:8080/products  --header "Authorization: Bearer $ACCESS_TOKEN_ADMIN"
curl -X GET http://localhost:8080/anonymous/dummy

curl -X GET http://localhost:8080/products  --header "Authorization: Bearer $ACCESS_TOKEN"
curl -X GET http://localhost:8080/productsuser  --header "Authorization: Bearer $ACCESS_TOKEN_ADMIN"
}}}

=== connect_container.sh ===
{{{#!highlight bash
docker exec -it test /bin/sh
}}}

=== stop_container.sh ===
{{{#!highlight bash
docker stop test
docker rm test
}}}


== SpringBoot 2.2.8 (resource server) + keycloak 10.0.2 (authentication server) ==

{{{
wget https://downloads.jboss.org/keycloak/10.0.2/keycloak-10.0.2.zip
unzip -t keycloak-10.0.2.zip
cd ~/keycloak-10.0.2/bin
sh standalone.sh 
http://localhost:8080/auth
admin admin admin create 
http://localhost:8080/auth/admin/master/console/#/realms/master
Master, add realm, MyRealm , create 
Users, add user, myuser
select user, credentials, mypwd mypwd, temporary off 
signout
http://localhost:8080/auth/realms/MyRealm/account/

realm: MyRealm
user pwd: myuser mypwd
client id: curl_confidential
protocol: openid-connect
Curl_confidential  settings: access-type confidential
valid redirect url http://localhost:8080
tab credentials: regenerate secret 6dfe5f84-d115-4d3e-8a56-a0fcf5b2f13e

curl -d 'client_id=curl_confidential' -d 'client_secret=6dfe5f84-d115-4d3e-8a56-a0fcf5b2f13e' -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' 'http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/token'

}}}

=== pom.xml ===
{{{
<?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>2.2.8.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>demo</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>11</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
		</dependency>
		<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>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
}}}


=== Invoke endpoint on resource server with access token from keycloak ===
{{{
curl http://localhost:8181/hello -vvv
TOKEN=$(curl -d 'client_id=curl_confidential' -d 'client_secret=6dfe5f84-d115-4d3e-8a56-a0fcf5b2f13e' -d 'username=myuser' -d 'password=mypwd' -d 'grant_type=password' 'http://localhost:8080/auth/realms/MyRealm/protocol/openid-connect/token' | jq -r '.access_token')
curl -X GET http://localhost:8181/hello  --header "Authorization: Bearer $TOKEN"
}}}