写在前面
本文基于上一篇文章《 Shiro + Springboot + JWT 的整合》延续,示例项目也是在上篇文章的基础上扩展而来的。
一个系统一般都具有多种用户类型,如管理员、普通用户和运营者账号等,这些用户都存储在不同的表中。当然,通过对 Token
的内容进行条件判断,一个 Realm
是可以直接实现多个账号登录的,但是这样将导致不同用户的登录完全耦合在一起,这不是我们想看到的,所以有了多 Realm
的需求,并且 Shiro
是支持多 Realm
的。
1. 实现逻辑
1.1 分析
通过阅读 Shiro
部分源码可以发现,Shiro
是支持,但是 Shiro
的多 Realm
实现逻辑并不符合本文的场景,部分源码如下:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
这是 Shiro
认证步骤选择 Realm
进行认证的实现,可以看到,如果有多个 Realm
将对每个都进认证,而我们希望的是管理员。用户等都只调用自己的 Realm
,所以对这些地方的逻辑要稍微做些改动。
1.2 实现
主要改动包括三方面:
- 在
TokenFilter
中将当前Token
是属于那种用户类型的信息一同封装到AuthenticationToken
记录下来。 - 认证步骤要实现自己调用自己的
Realm
,需要复写ModularRealmAuthenticator
类以下方法。
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException;
- 授权步骤要实现自己调用自己的
Realm
,需要复写ModularRealmAuthorizer
类以下方法。
public boolean isPermitted(PrincipalCollection principals, String permission);
public boolean isPermitted(PrincipalCollection principals, Permission permission);
public boolean hasRole(PrincipalCollection principals, String roleIdentifier);
2. 实现源码
本文章针对于已经如何简单运用 Shiro
的人,对于创建的多个 Realm
类、登录逻辑的修改等代码段此处就没有再贴出,详细内容可看项目源码,可直接运行。
示例项目已经上传到 Github 直接可用:https://github.com/nineya/framework-study/tree/v0.2.0/shiro-study
2.1 自定义 AuthenticationToken
自定义 AuthenticationToken
,包含登录用户类型信息,并在 TokenFilter
中使用该类型。
package com.nineya.shiro.entity;
/**
* @author 殇雪话诀别
* 2021/2/17
*/
public enum LoginType {
USER, MANAGE;
}
package com.nineya.shiro.entity;
import org.apache.shiro.authc.BearerToken;
/**
* 继承实现一个自定义的带有登录类型的Token
* @author 殇雪话诀别
* 2021/2/17
*/
public class JwtToken extends BearerToken {
private final LoginType loginType;
public JwtToken(LoginType loginType, String token, String host) {
super(token, host);
this.loginType = loginType;
}
public LoginType getLoginType() {
return loginType;
}
}
/**
* 用户登录
* @param request
* @param response
* @return
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
LoginType loginType = null;
String token = httpServletRequest.getHeader(AUTHORIZATION_HEADER);
if (token != null) {
loginType = LoginType.USER;
} else {
token = httpServletRequest.getHeader(MANAGE_AUTHORIZATION);
if (token != null) {
loginType = LoginType.MANAGE;
}
}
JwtToken jwtToken = new JwtToken(loginType, token, request.getRemoteAddr());
getSubject(request, response).login(jwtToken);
return true;
}
2.2 修改认证步骤实现
package com.nineya.shiro.config;
import com.nineya.shiro.entity.JwtToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import java.util.Collection;
/**
* @author 殇雪话诀别
* 2021/2/17
*/
public class StudyModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
// 判断 Realm 是否为空
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
JwtToken jwtToken = (JwtToken) authenticationToken;
String loginType = jwtToken.getLoginType().name();
for (Realm realm : realms) {
if (realm.getName().equals(loginType)) {
return doSingleRealmAuthentication(realm, authenticationToken);
}
}
return null;
}
}
2.3 修改授权步骤实现
package com.nineya.shiro.config;
import org.apache.shiro.authz.Authorizer;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.Set;
/**
* @author 殇雪话诀别
* 2021/2/18
*/
public class StudyModularRealmAuthorizer extends ModularRealmAuthorizer {
@Override
public boolean isPermitted(PrincipalCollection principals, String permission) {
assertRealmsConfigured();
Set<String> realmNames = principals.getRealmNames();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
// 仅比较 realmName 对应得上的 realm
if (realmNames.contains(realm.getName())) {
return ((Authorizer) realm).isPermitted(principals, permission);
}
}
return false;
}
@Override
public boolean isPermitted(PrincipalCollection principals, Permission permission) {
assertRealmsConfigured();
Set<String> realmNames = principals.getRealmNames();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
// 仅比较 realmName 对应得上的 realm
if (realmNames.contains(realm.getName())) {
return ((Authorizer) realm).isPermitted(principals, permission);
}
}
return false;
}
@Override
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
assertRealmsConfigured();
Set<String> realmNames = principals.getRealmNames();
for (Realm realm : getRealms()) {
if (!(realm instanceof Authorizer)) continue;
// 仅比较 realmName 对应得上的 realm
if (realmNames.contains(realm.getName())) {
return ((Authorizer) realm).hasRole(principals, roleIdentifier);
}
}
return false;
}
}
2.4 配置中运用
/**
* 权限管理,配置主要是Realm的管理认证,同时可以配置缓存管理等
*
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager(List<Realm> realms) {
DefaultWebSecurityManager webSecurityManager = new DefaultWebSecurityManager();
webSecurityManager.setAuthenticator(modularRealmAuthenticator());
webSecurityManager.setAuthorizer(modularRealmAuthorizer());
//realm管理,必须在两个modular之后,因为会对这两个对象进行设值
webSecurityManager.setRealms(realms);
return webSecurityManager;
}
/**
* 针对多realm,用于认证阶段
*/
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator() {
//自己重写的ModularRealmAuthenticator
StudyModularRealmAuthenticator modularRealmAuthenticator = new StudyModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
/**
* 针对多realm,用于授权阶段
* @return
*/
@Bean
public ModularRealmAuthorizer modularRealmAuthorizer() {
return new StudyModularRealmAuthorizer();
}