[DISCUSS] Proposal for v2 API change in AuthorizingRealm.java

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

[DISCUSS] Proposal for v2 API change in AuthorizingRealm.java

Benjamin Marwell-2
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
Reply | Threaded
Open this post in threaded view
|

Re: [DISCUSS] Proposal for v2 API change in AuthorizingRealm.java

fpapon
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
[hidden email]

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
Reply | Threaded
Open this post in threaded view
|

Re: [DISCUSS] Proposal for v2 API change in AuthorizingRealm.java

Mookkiah, Mahendran
Hello,

+1 for upgrading the API to support OAuth2 / JWT Authenticating Realm

I have implemented custom realm to support OAuth2 login when using Shiro 1.5.2 which support OAuth2 flow as well as REST API carrying JWT.

This project (https://github.com/zhangkaitao/shiro-example/blob/master/shiro-example-chapter17-client/src/main/) helped me to start the implementation.

After reading this thread, I thought I should share some of the requirements I have received while implementing it.

We should be able to configure the name of the header which is carrying the JWT token. For example, WSO2 allows it to configure this header and by default it is X-JWT-Assertion (Reference: https://docs.wso2.com/display/AM200/Passing+Enduser+Attributes+to+the+Backend+Using+JWT)

+ 1 for using replace method as in our scenario, the header value will not have this text.

bearerToken.getToken().replaceFirst("Bearer ", "")

We should have option to configure the JWT verification with JWKS.

As JWT carries more information about the subject, we should make it available on thread/session context so that business logic can get the JWT claims if required.

Please let me know if you need clarity. Happy to help!

Thanks,
Mahendran.


On 9/11/20, 2:06 AM, "Francois Papon" <[hidden email]> wrote:

    NOTE: This message is from an EXTERNAL SENDER - be CAUTIOUS, particularly with links and attachments.
    Please contact Information Security team for suspicious content/activity.


    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
    [hidden email]

    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


NVOCC Services are provided by CEVA as agents for and on behalf of Pyramid Lines Limited trading as Pyramid Lines.
This e-mail message is intended for the above named recipient(s) only. It may contain confidential information that is privileged. If you are not the intended recipient, you are hereby notified that any dissemination, distribution or copying of this e-mail and any attachment(s) is strictly prohibited. If you have received this e-mail by error, please immediately notify the sender by replying to this e-mail and deleting the message including any attachment(s) from your system. Thank you in advance for your cooperation and assistance. Although the company has taken reasonable precautions to ensure no viruses are present in this email, the company cannot accept responsibility for any loss or damage arising from the use of this email or attachments.
Reply | Threaded
Open this post in threaded view
|

Re: [DISCUSS] Proposal for v2 API change in AuthorizingRealm.java

Brian Demers
One of the traditional ways of doing something like this is to use a custom
principal when doing "authc". Your custom principal would store the
information you need for later "authz" calls
https://github.com/oktadeveloper/okta-shiro-plugin/blob/master/core/src/main/java/com/okta/shiro/realm/OktaResourceServerRealm.java#L104

Then pull the JWT info out of the principal for "authz":
https://github.com/oktadeveloper/okta-shiro-plugin/blob/master/core/src/main/java/com/okta/shiro/realm/OktaResourceServerRealm.java#L118

This allows the request to be stateless, however, while I think this
pattern works.  I don't think it's overly intuitive, and it is something we
could change in v2.

Maybe just adding some type information would help too, instead of using a
PrincipalCollection and a list of principal Objects, maybe we had some
other type of context object that could store attributes directly?
In this case maybe something like
`PrincipalCollection.getAttribute("accessToken")`.  Maybe tweaking the name
of `PincipalCollection` too?

This would allow arbitrarily metadata to be attached to the Principal, in
many cases, you can get the list of permissions/roles at the time of
Authentication (i.e. a single LDAP query). Adding an attribute _could_ make
it more intuitive.

NOTE: there are a few problems with my suggestion above, like how do you
know which Realm set which attribute, It's potentially easier to run into
serialization issues, if you are just sticking stuff into a map.
I'm just throwing out an idea :D

-Brian


On Fri, Sep 11, 2020 at 7:01 AM Mookkiah, Mahendran
<[hidden email]> wrote:

> Hello,
>
> +1 for upgrading the API to support OAuth2 / JWT Authenticating Realm
>
> I have implemented custom realm to support OAuth2 login when using Shiro
> 1.5.2 which support OAuth2 flow as well as REST API carrying JWT.
>
> This project (
> https://github.com/zhangkaitao/shiro-example/blob/master/shiro-example-chapter17-client/src/main/)
> helped me to start the implementation.
>
> After reading this thread, I thought I should share some of the
> requirements I have received while implementing it.
>
> We should be able to configure the name of the header which is carrying
> the JWT token. For example, WSO2 allows it to configure this header and by
> default it is X-JWT-Assertion (Reference:
> https://docs.wso2.com/display/AM200/Passing+Enduser+Attributes+to+the+Backend+Using+JWT
> )
>
> + 1 for using replace method as in our scenario, the header value will not
> have this text.
>
> bearerToken.getToken().replaceFirst("Bearer ", "")
>
> We should have option to configure the JWT verification with JWKS.
>
> As JWT carries more information about the subject, we should make it
> available on thread/session context so that business logic can get the JWT
> claims if required.
>
> Please let me know if you need clarity. Happy to help!
>
> Thanks,
> Mahendran.
>
>
> On 9/11/20, 2:06 AM, "Francois Papon" <[hidden email]>
> wrote:
>
>     NOTE: This message is from an EXTERNAL SENDER - be CAUTIOUS,
> particularly with links and attachments.
>     Please contact Information Security team for suspicious
> content/activity.
>
>
>     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
>     [hidden email]
>
>     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
>
>
> NVOCC Services are provided by CEVA as agents for and on behalf of Pyramid
> Lines Limited trading as Pyramid Lines.
> This e-mail message is intended for the above named recipient(s) only. It
> may contain confidential information that is privileged. If you are not the
> intended recipient, you are hereby notified that any dissemination,
> distribution or copying of this e-mail and any attachment(s) is strictly
> prohibited. If you have received this e-mail by error, please immediately
> notify the sender by replying to this e-mail and deleting the message
> including any attachment(s) from your system. Thank you in advance for your
> cooperation and assistance. Although the company has taken reasonable
> precautions to ensure no viruses are present in this email, the company
> cannot accept responsibility for any loss or damage arising from the use of
> this email or attachments.
>
Reply | Threaded
Open this post in threaded view
|

Re: [DISCUSS] Proposal for v2 API change in AuthorizingRealm.java

Jean-Baptiste Onofré
In reply to this post by Benjamin Marwell-2
+1 for API update.

Thanks for the proposal.

Regards
JB

> Le 10 sept. 2020 à 22:21, Benjamin Marwell <[hidden email]> 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