SOA专题

使用Shiro基于服务的多域身份验证和授权

  本文介绍如何使用开源产品 Apache Shiro 实现基于服务的multi realm身份验证和授权,Shiro是支持out-of-box的安全认证,相比于Tomcat等容器提供的授权认证体系要更灵活,可以直接使用在微服务架构中。

  为了实现基于服务也就是每个模块的安全体系,需要定制Shiro Realm来支持,它可以有很多种realm,如LDAP, JDBC, 基于文件realm和定制的realm,不同模块之间可以共享相同的Realm,如下图:

安全微服务

创建定制的Realm

  我们需要支持身份验证和授权两种目标,因此,定制一个realm需要继承Shiro类 AuthorizingRealm (它也是基础 AuthenticatingRealm),覆盖重写其方法:doGetAuthenticationInfo 和 doGetAuthorizationInfo。这两个方法是实现身份验证和授权,假设我们定制的Realm名称是StratioRealm,其包含服务名称属性和验证和授权的两个List:

private String service;
private List authenticatingRealms;
private List authorizingRealms;

在覆盖重写的方法中,我们基于已经配置的realm实现验证和授权,返回一个融合的结果,如果是验证返回的是principal,如果是授权返回角色和与其相连的权限:

@Override

protected StratioAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {

       StratioAuthenticationInfo authInfo = null;

       try {

              for (AuthenticatingRealm realm: authenticatingRealms) {

                     AuthenticationInfo auth = realm.getAuthenticationInfo(authenticationToken);

                     if (MergableAuthenticationInfo.class.isInstance(auth)) {

                            if (authInfo == null) {

                                   authInfo = new StratioAuthenticationInfo(auth);

                            } else {

                                   authInfo.merge(auth);

                            }

                     } else {

                            throw new AccountException("Impossible to merge AuthenticationInfo");

                     }

              }

              authInfo.setMainRealm(this);

              return authInfo;

       } catch (AuthenticationException e) {

              throw new AuthenticationException(e);

       }

}@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

       SimpleAuthorizationInfo authInfo = null;

       try {

              for (Realm realm: authorizingRealms) {

                    AuthorizingRealmWrapper authorizingRealmWrapper =

                                        new AuthorizingRealmWrapper((AuthorizingRealm) realm);

                     SimpleAuthorizationInfo auth =

                    (SimpleAuthorizationInfo) authorizingRealmWrapper.doGetAuthorizationInfo(principalCollection);

                     if (authInfo == null) {

                            authInfo = auth;

                     } else {

                            if (authInfo.getRoles() != null) {

                                   authInfo.addRoles(auth.getRoles());

                            }

                            if (authInfo.getStringPermissions() != null) {

                                   authInfo.addStringPermissions(auth.getStringPermissions());

                            }

                            if (authInfo.getObjectPermissions() != null) {

                                   authInfo.addObjectPermissions(auth.getObjectPermissions());

                            }

                     }

              }

              return authInfo;

       } catch (Exception e) {

              throw new AuthorizationException(e);

       }

}

实现身份验证策略

  在身份验证过程中,我们会引入一个新的参数如服务或模块的名称,这样我们得创建一个特殊的验证策略。正如我们上面所做,现在需要继承Shiro类AbstractAuthenticationStrategy,重写覆盖其方法,在这里,我们希望接受到一个验证token,其中包含用户名和密码以及服务名称,还有其他条件:服务名称必须匹配我们已经在realm中配置的授权可以访问的服务名称,否则,我们会抛出AuthenticationException ,用户将不会被允许使用这个系统。

@Override

public AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) {

       if (aggregate == null || CollectionUtils.isEmpty(aggregate.getPrincipals()) || !matchServiceRealm(aggregate,

       token)) {

              throw new AuthenticationException("Authentication token of type [" + token.getClass() + "] " +

                     "could not be authenticated by any configured realms. Please ensure that at least one realm can " +

                     "authenticate these tokens.");

       }

       return aggregate;

}

private boolean matchServiceRealm(AuthenticationInfo aggregate, AuthenticationToken token) {

       UsernamePasswordServiceToken serviceToken = (UsernamePasswordServiceToken) token;

       StratioAuthenticationInfo auth = (StratioAuthenticationInfo) aggregate;

       return auth.getMainRealm().getService().equals(serviceToken.getService());

}

案例配置

  最后一步是将我们定制的这些方案放入Shiro能够协同工作,下面是一个Shiro配置文件:

# Define JDBC datasources

ds = com.mysql.jdbc.jdbc2.optional.MysqlDataSource

ds.serverName = server1

ds.user = stratio

ds.password = pwd1

ds.databaseName = authdb

 

ds2 = com.mysql.jdbc.jdbc2.optional.MysqlDataSource

ds2.serverName = server2

ds2.user = stratio

ds2.password = pwd2

ds2.databaseName = authdb2

 

# Define the authenticating realms

 

userLdapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm

userLdapRealm.userDnTemplate = uid={0},ou=users,dc=stratio,dc=com

userLdapRealm.contextFactory.url = ldap://ldap1

 

deepLdapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm

deepLdapRealm.userDnTemplate = uid={0},ou=deep,dc=stratio,dc=com

deepLdapRealm.contextFactory.url = ldap://ldap2

 

crossdataLdapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm

crossdataLdapRealm.userDnTemplate = uid={0},ou=admins,dc=stratio,dc=com

crossdataLdapRealm.contextFactory.url = ldap://ldap3

 

# Define the authorizing realms

 

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm

jdbcRealm.permissionsLookupEnabled=true

jdbcRealm.dataSource = $ds

 

jdbcRealm.userRolesQuery = SELECT role.shortcut FROM auth LEFT JOIN auth_role ON auth_role.auth_id = auth.id LEFT JOIN role ON role.id = auth_role.role_id WHERE auth.name = ?

jdbcRealm.permissionsQuery = SELECT permission.shortcut FROM role JOIN role_permission ON role_permission.role_id = role.id JOIN permission ON permission.id = role_permission.permission_id WHERE role.shortcut = ?

 

jdbcRealm2=org.apache.shiro.realm.jdbc.JdbcRealm

jdbcRealm2.permissionsLookupEnabled=true

jdbcRealm2.dataSource = $ds2

 

jdbcRealm2.authenticationQuery = SELECT name FROM auth WHERE name = ?

jdbcRealm2.userRolesQuery = SELECT role.shortcut FROM auth LEFT JOIN auth_role ON auth_role.auth_id = auth.id LEFT JOIN role ON role.id = auth_role.role_id WHERE auth.name = ?

jdbcRealm2.permissionsQuery = SELECT permission.shortcut FROM role JOIN role_permission ON role_permission.role_id = role.id JOIN permission ON permission.id = role_permission.permission_id WHERE role.shortcut = ?

 

# Define a realm for Stratio Deep Module with two authenticating realms and two authorizing realms.

deepRealm = com.stratio.datagov.security.authc.realm.StratioRealm

deepRealm.service = deep

deepRealm.authenticatingRealms = $deepLdapRealm, $userLdapRealm

deepRealm.authorizingRealms = $jdbcRealm, $jdbcRealm2

 

# Define a realm for Stratio Crossdata Module with two authenticating realms and an authorizing realm.

crossdataRealm = com.stratio.datagov.security.authc.realm.StratioRealm

crossdataRealm.service = crossdata

crossdataRealm.authenticatingRealms = $crossdataLdapRealm, $userLdapRealm

crossdataRealm.authorizingRealms = $jdbcRealm

 

# Configure the custom realms into Shiro’s Security Manager

securityManager.realms= $adminRealm, $crossdataRealm

 

#Configure the custom authentication strategy

authcStrategy = com.stratio.datagov.security.authc.pam.CustomAuthenticationStrategy

securityManager.authenticator.authenticationStrategy = $authcStrategy

 

基于Spring Security和数据库的身份验证和授权

Spring security安全机制深入使用

基于JavaEE容器安全验证和授权

基于Jdon框架的组件安全授权

权限专题

EDA