-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Smart authentication implementation being tested
- Loading branch information
Showing
6 changed files
with
207 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
120 changes: 120 additions & 0 deletions
120
src/main/java/org/immregistries/iis/kernal/JwtAuthProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters