Hi, +1 for an upgrade of the api!
I made a PoC with JWT in Shiro last year and I was using the cache to store the authorization information from the JWT in the principal during the authentication phase: import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.security.KeyFactory; import java.security.PublicKey; import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.util.*; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.Permission; import org.apache.shiro.authz.permission.WildcardPermission; import org.apache.shiro.codec.Hex; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.osgi.service.cm.ConfigurationException; import org.osgi.service.component.ComponentContext; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(immediate = true, name = "fr.openobject.labs.shiro.services.security", service = JwtRealm.class) public class JwtRealm extends AuthorizingRealm { private Logger logger = LoggerFactory.getLogger(JwtRealm.class); private PublicKey publicKey; private SignatureAlgorithm algorithm; public JwtRealm() { this.setAuthenticationTokenClass(BearerToken.class); } @Activate public void activate(ComponentContext componentContext) throws Exception { Dictionary<String, Object> properties = componentContext.getProperties(); String hexPublicKey = String.class.cast(properties.get("security.publicKey")); String propAlgorithm = String.class.cast(properties.get("security.algorithm")); if (propAlgorithm != null && !propAlgorithm.equals("")) { this.algorithm = SignatureAlgorithm.forName(propAlgorithm); } else { logger.info("No signature algorithm found, using RS512..."); this.algorithm = SignatureAlgorithm.RS512; } if (hexPublicKey == null || hexPublicKey.equals("")) { logger.info("Missing public key configuration!"); throw new ConfigurationException("security.publicKey", "Missing public key"); } X509EncodedKeySpec x509 = new X509EncodedKeySpec(Hex.decode(hexPublicKey)); this.publicKey = KeyFactory.getInstance(this.algorithm.getFamilyName()).generatePublic(x509); } @Deactivate public void deactivate() { // do nothing } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // not implemented, we are using the cache in the authentication info method return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { SimpleAccount account = null; BearerToken bearerToken = BearerToken.class.cast(authenticationToken); if (bearerToken != null) { try { Jws<Claims> jws = Jwts.parser() .setSigningKey(publicKey) .parseClaimsJws(bearerToken.getToken().replaceFirst("Bearer ", "")); if (jws != null && jws.getBody() != null && jws.getBody().getExpiration().after(Date.from(Instant.now()))) { Set<String> roles = new HashSet<>(jws.getBody().get("roles", ArrayList.class)); List<String> permissions = new ArrayList<>(jws.getBody().get("permissions", ArrayList.class)); Set<Permission> finalPermission = new HashSet<>(); permissions .stream() .forEach(perm -> finalPermission.add(new WildcardPermission(perm))); String customer = jws.getBody().get("customer", String.class); account = new SimpleAccount( customer + ":" + jws.getBody().getSubject(), jws.getBody().getId(), "MYAPP", roles, finalPermission); logger.debug(account.toString()); if (this.isAuthorizationCachingEnabled()) { Object cacheAuthzKey = this.getAuthorizationCacheKey(account.getPrincipals()); if (Optional.ofNullable(cacheAuthzKey).isPresent()) { this.getAuthorizationCache().put(cacheAuthzKey, account); } } } } catch (Exception exception) { logger.warn( "not a valid token! :: {} :: cause :: {}", bearerToken.getToken().replaceFirst("Bearer ", ""), exception.getMessage()); } } return account; } } regards, François fpa...@apache.org Le 10/09/2020 à 22:21, Benjamin Marwell a écrit : > Hi everyone, > > I would like to adapt shiro to be able to read authentication and > authorization data from JWT tokens. > It is quite easy for authentication. Next to > UsernamePasswordToken.java, we could create an JwtToken.java class, > which holds Jwt Authorization Data (the signature) and Authentication > data (claims). Maybe by pulling in the MicroProfile JWT API [1], or > maybe just an abstraction of it. > > For the authorization, it gets a little more complicated. > At the moment we have this following method in the file AuthorizingRealm.java: > > protected AuthorizationInfo > getAuthorizationInfo(PrincipalCollection principals); > > It takes a PrincipalCollection, because the user can be part of > multiple realms. However, the authorization data is (possibly!) stored > in the JWT, which is not available anymore. However, a simple API > change could make it available. I would like change it to: > > protected AuthorizationInfo > getAuthorizationInfo(PrincipalCollection principals, > AuthenticationToken token); > > Of course, the implementations would still be able to pull even more > authentication data (e.g. additional roles not stored in the JWT) from > a database or other external source. However, currently there is no > non-hacky way to pull in the JwtToken in the AuthorizingRealm.java > class. > > -- > > Parsing of such a token is also necessary. While MP-JWT is just an > API, one implementation must either be shipped or chosen by the user. > Or maybe shipped, and if the user wishes to use another > implementation, it can be excluded and the other dependency will be > pulled in. > > The API is simple [2] and allows easy migration of to those who do not > need inbuilt authentication. > > However, there are still several use cases for JWT in shiro: > * multi-server-readable authentication/authorization using a JWT. This > would make shiro-apps totally stateless without any shared state (e.g. > a shared session cache in a DB or via a memory grid). > * allowing multiple login methods (JWT and other realms, like LDAP) in > combination with the FirstSuccessfulStrategy. > * Using another JWT library, the application could create a single or > multiple JWTs itself after a user LDAP login to replace the cookie and > associated sessions. But better have an external (trusted) service to > issue tokens. > > Please let me know what you think. > > [1] > https://www.eclipse.org/community/eclipse_newsletter/2017/september/article2.php > > [2] https://www.tomitribe.com/blog/microprofile-json-web-token-jwt/ > > Regards, > Ben