Skip to content

Commit

Permalink
Smart authentication implementation being tested
Browse files Browse the repository at this point in the history
  • Loading branch information
cerbeor committed Jan 3, 2024
1 parent 6531fdf commit d19cf0b
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 7 deletions.
34 changes: 29 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring_boot_version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.security.oauth.boot</groupId>-->
<!-- <artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
<!-- <version>2.6.8</version>-->
<!-- </dependency>-->

<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down Expand Up @@ -309,10 +314,10 @@

<!-- HAPI-FHIR uses Logback for logging support. The logback library is included automatically by Maven as a part of the hapi-fhir-base dependency, but you also need to include a logging library. Logback
is used here, but log4j would also be fine. -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>ch.qos.logback</groupId>-->
<!-- <artifactId>logback-classic</artifactId>-->
<!-- </dependency>-->

<!-- Needed for JEE/Servlet support -->
<dependency>
Expand Down Expand Up @@ -543,6 +548,25 @@
<version>${logback-classic.version}</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/com.nimbusds/nimbus-jose-jwt -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.37.3</version>
</dependency>


</dependencies>

<build>
Expand Down
3 changes: 2 additions & 1 deletion src/main/database/create-mysql-database.sql
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,5 @@ CREATE TABLE `tenant` (
--insert into user_access (org_id, access_name, access_key) values (1, 'Mercy', 'password1234');
--insert into user_access (org_id, access_name, access_key) values (2, 'Bob', '1234password1234');
--insert into user_access (org_id, access_name, access_key) values (3, 'DEFAULT', 'BabySharkJaws');
--insert into user_access (org_id, access_name, access_key) values (4, 'Connectathon', 'SundaysR0ck!');
insert into user_access (org_id, access_name, access_key) values (4, 'Connectathon', 'SundaysR0ck!');
insert into user_access (org_id, access_name, access_key) values (4, 'admin', '?whooosM0reSorino?);
120 changes: 120 additions & 0 deletions src/main/java/org/immregistries/iis/kernal/JwtAuthProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package org.immregistries.iis.kernal;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.SignedJWT;
import org.hibernate.Session;
import org.immregistries.iis.kernal.fhir.security.ServletHelper;
import org.immregistries.iis.kernal.model.UserAccess;
import org.immregistries.iis.kernal.servlet.PopServlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

@RestController()
public class JwtAuthProvider {
@Autowired
JwtUtils jwtUtils;
Map<String, PublicKey> keystore;
Map<String,SignedJWT> jwtStore;
Logger logger = LoggerFactory.getLogger(JwtAuthProvider.class);

public JwtAuthProvider() {
this.keystore = new HashMap<>(10);
this.jwtStore = new HashMap<>(10);
}

@GetMapping("/.well-known/smart-configuration")
public String wellKnownConfiguration() {
return "{\n" +
" \"token_endpoint\": \"/token\",\n" +
" \"token_endpoint_auth_methods_supported\": [\"private_key_jwt\"],\n" +
" \"token_endpoint_auth_signing_alg_values_supported\": [\"RS384\", \"ES384\"],\n" +
" \"scopes_supported\": [\"system/*.rs\"]\n" +
"}";
}

@PostMapping("/registerClient")
public String register(@RequestBody String jwkString) { //TODO TLS config
// assert(ServletHelper.getUserAccess().getAccessName().equals("admin")); TODO safely define admin user
logger.info("Registering client JWK: {}", jwkString); //TODO not log key
Map<String,Object> parsedJwk = new Gson().fromJson(jwkString, new TypeToken<HashMap<String, Object>>() {}.getType());
String alg = (String) parsedJwk.get("alg");
String kid = (String) parsedJwk.get("kid");
switch (alg) {
case "RS384":
RSAPublicKey rsaJwk = new Gson().fromJson(jwkString, RSAPublicKey.class);
keystore.put(kid,rsaJwk); // TODO change key ?
break;
case "ES384" :
ECPublicKey ecJwk = new Gson().fromJson(jwkString, ECPublicKey.class);
keystore.put(kid,ecJwk); // TODO change key ?
break;
default:
throw new RuntimeException("Unsupported Algorithm");
}
return "ok";
}

/**
* Only allowed for connectathons users
*/
@PostMapping("/token")
public String smartJwtAuth(@RequestParam String client_assertion_type, @RequestParam String client_assertion) throws ParseException, JOSEException {
SignedJWT signedJWT = SignedJWT.parse(client_assertion);
/**
* Reading Jwt Headers
*/
String alg = signedJWT.getHeader().getAlgorithm().getName();
String typ = signedJWT.getHeader().getType().getType();
String kid = signedJWT.getHeader().getKeyID();
// String jku = signedJWT.getHeader().getJWKURL(); not supported

assert (typ.equals("JWT"));
assert (keystore.containsKey(kid));
JWSVerifier verifier;
switch (alg) {
case "RS384":
verifier = new RSASSAVerifier((RSAPublicKey) keystore.get(kid));
break;
case "ES384" :
verifier = new ECDSAVerifier((ECPublicKey) keystore.get(kid));
break;
default:
throw new RuntimeException("Unsupported Algorithm");
}
assert(signedJWT.verify(verifier));
assert(signedJWT.getJWTClaimsSet().getClaim("jti") != null);
/**
* check that the jti value has not been previously encountered for the given iss within the maximum allowed authentication JWT lifetime (e.g., 5 minutes). This check prevents replay attacks.
*/
SignedJWT olderJwt = jwtStore.get((String) signedJWT.getJWTClaimsSet().getClaim("jti"));
if (olderJwt != null && olderJwt.verify(verifier) && olderJwt.getJWTClaimsSet().getClaim("iss").equals(signedJWT.getJWTClaimsSet().getClaim("iss")) && ((long) olderJwt.getJWTClaimsSet().getClaim("exp")) < System.currentTimeMillis()) {
throw new RuntimeException("Token already used");
}
jwtStore.put((String) signedJWT.getJWTClaimsSet().getClaim("jti"), signedJWT);
Session dataSession = null;
try {
dataSession = PopServlet.getDataSession();
UserAccess userAccess = ServletHelper.authenticateUserAccessUsernamePassword("Connectathon","Connectathon",dataSession);
return jwtUtils.generateJwtToken(userAccess);
} finally {
if (dataSession != null) {
dataSession.close();
}
}
}

}
49 changes: 49 additions & 0 deletions src/main/java/org/immregistries/iis/kernal/JwtUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.immregistries.iis.kernal;

import io.jsonwebtoken.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtUtils {
private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);
@Value("${iis.api.app.jwtSecret}")
private String jwtSecret;
@Value("${iis.api.app.jwtExpirationMs}")
private int jwtExpirationMs;
public String generateJwtToken(Authentication authentication) {
UserDetails userPrincipal = (UserDetails) authentication.getPrincipal();
return Jwts.builder()
.setSubject((userPrincipal.getUsername()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
logger.error("Invalid JWT signature: {}", e.getMessage());
} catch (MalformedJwtException e) {
logger.error("Invalid JWT token: {}", e.getMessage());
} catch (ExpiredJwtException e) {
logger.error("JWT token is expired: {}", e.getMessage());
} catch (UnsupportedJwtException e) {
logger.error("JWT token is unsupported: {}", e.getMessage());
} catch (IllegalArgumentException e) {
logger.error("JWT claims string is empty: {}", e.getMessage());
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public SecurityFilterChain filterChain(HttpSecurity http, CustomOAuthSuccessHand
.antMatchers(HttpMethod.GET, "/", "/home", "/pop", "/SubscriptionTopic/**", "/img/**").permitAll()
.antMatchers("/loginForm", "/oauth2/**", "/login" ).permitAll()
// API AUTHORIZATION AND AUTHENTICATION SEPARATED
.antMatchers("/fhir/**", "/soap").permitAll()
.antMatchers("/fhir/**", "/soap","/.well-known/smart-configuration","/registerClient", "/token").permitAll()
.anyRequest().authenticated()
.and()
//USERNAME
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ management:
server:
servlet:
contextPath: "/iis"
iis:
api:
app:
secret: "LeVentNousPorterasT0utDizp4ra1tra"
jwtExpirationMs: 30000
# TODO change secret
spring:
security:
oauth2:
Expand Down

0 comments on commit d19cf0b

Please sign in to comment.