= 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 * https://www.rfc-editor.org/rfc/rfc7519.html == 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 + OAuth2 == * 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 }}} {{{#!highlight 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 sh docker run -d -p 8080:8080 --name test test_image }}} === Dockerfile === {{{#!highlight sh 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 grantedAuthoritiesList = new ArrayList<>(); public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Collection getGrantedAuthoritiesList() { return grantedAuthoritiesList; } public void setGrantedAuthoritiesList(Collection 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 === {{{#!highlight 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 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 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 4.0.0 com.tutorialspoint websecurityapp 0.0.1-SNAPSHOT jar websecurityapp Demo project for Spring Boot org.springframework.boot spring-boot-starter-parent 1.5.9.RELEASE UTF-8 UTF-8 1.8 jakarta.xml.bind jakarta.xml.bind-api 2.3.2 org.glassfish.jaxb jaxb-runtime 2.3.2 org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.springframework.security.oauth spring-security-oauth2 org.springframework.security spring-security-jwt com.h2database h2 org.springframework.boot spring-boot-starter-test test org.springframework.security spring-security-test test org.springframework.boot spring-boot-maven-plugin }}} === 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 - Spring Security 5) + keycloak 10.0.2 (authentication server) + Java 11 == {{{#!highlight bash mkdir -p ~/Documents/test-springboot-keycloak cd ~/Documents/test-springboot-keycloak wget https://downloads.jboss.org/keycloak/10.0.2/keycloak-10.0.2.zip unzip -t keycloak-10.0.2.zip unzip 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 Add role USER to MyRealm Make user myuser have role USER 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 save in MyRealm, Client, client: curl_confidential 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 └── src └── main ├── java │   └── com │   └── example │   └── demo │   └── DemoApplication.java └── resources └── application.properties }}} === pom.xml === {{{#!highlight xml 4.0.0 org.springframework.boot spring-boot-starter-parent 2.2.8.RELEASE com.example demo 0.0.1-SNAPSHOT demo Demo project for Spring Boot 11 org.springframework.boot spring-boot-starter-oauth2-resource-server org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-maven-plugin }}} === src/main/resources/application.properties === {{{ server.port=8181 spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/auth/realms/MyRealm #logging.level.root=DEBUG logging.level.root=INFO logging.file=/tmp/testout.log }}} === src/main/java/com/example/demo/DemoApplication.java === {{{#!highlight java package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.Jwt; @RestController @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } @GetMapping("/otherhello") public String otherHello(){ return "other hello"; } @GetMapping("/hello") public String hello(Authentication authentication) { String s=""; for(int i=0; i