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