Shiro 管理多个realm 实现前后台分离

时间:2022-10-23 15:24:05

使用shiro 由于公司的业务上的需求前后台公用的一张表,要实现前台用户和后台用户的分离拦截需要书写多个realm 用来验证前台用户还是后台用户。直接上代码
1.书写一个自定的token UsernamePasswordUsertypeToken 继承UsernamePasswordToken 用来判断用户类型UsernamePasswordUsertypeToken 多出一个字段用来区分用户类型

package com.jscredit.zxypt.shiro;

import org.apache.shiro.authc.UsernamePasswordToken;

/**
* 拓展shiro 中的UsernamePasswordToken
* @author liyaqiang
*
*/


public class UsernamePasswordUsertypeToken extends UsernamePasswordToken {

private static final long serialVersionUID = 1L;
private String usertype ;

public String getUsertype() {
return usertype;
}
public void setUsertype(String usertype) {
this.usertype = usertype;
}

public UsernamePasswordUsertypeToken(String loginName, String password, String usertype) {

super(loginName, password);

this.usertype = usertype;

}

}

定义前台的验证的realm

public class UserLoginRealm extends AuthorizingRealm{

private final Logger LOGGER = LoggerFactory.getLogger(UserLoginRealm.class);

private static final String ALGORITHM = "MD5";

@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Autowired
private UserKzService userKzService;

public UserLoginRealm(){
super();
}


/**
* 为当前登录的Subject授予角色和权限 ,该方法的调用时机为需授权资源被访问时
* 并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
* 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache
* 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache
*/


@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroLoginUser shiroLoginUser = (ShiroLoginUser) principals.fromRealm(getName()).iterator().next();
String username = shiroLoginUser.getAccount();
if (username != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

// 查询用户授权信息
List<Permission> perList = permissionService.getShiro("O");

if (perList != null && perList.size() != 0) {
for (Permission permission : perList) {
info.addStringPermission(permission.getPmsnCode());
}
return info;
}
}
return null;
}


/**
* 验证前台当前登录的Subject 本例中该方法的调用时机为UserLoginController.login()方法中执行Subject.login()时
*/


@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
// 这个authcToken是从userLoginController里面currentUser.login(token)传过来的
UsernamePasswordUsertypeToken fronttoken = (UsernamePasswordUsertypeToken) authcToken;
LOGGER.debug("验证当前Subject时获取到token为"
+ ReflectionToStringBuilder.toString(fronttoken, ToStringStyle.MULTI_LINE_STYLE));
User users = userService.getUserByName(fronttoken.getUsername());
UserKz userKz= userKzService.selectByPrimaryKey(users.getUserId());
String usertype =userKz.getUserType();
// Shiro完成对比逻辑,返回和令牌相关的正确的验证信息,第一个参数填登录用户名,第二个参数填合法的登录密码
if (users != null&&usertype!="00") {
ShiroLoginUser shiroLoginUser = new ShiroLoginUser(users.getUserId(), users.getAccount(),userKz.getUserType());
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroLoginUser,users.getPassword(), getName());
this.setSession("shiroLoginUser", shiroLoginUser);
return authcInfo;
} else {
throw new AuthenticationException();
}
// 没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常

}


/**
* 更新用户授权信息缓存.
*/

public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}

/**
* 清除所有用户授权信息缓存.
*/

public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}

@PostConstruct
public void initCredentialsMatcher() {// MD5加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ALGORITHM);
setCredentialsMatcher(matcher);
}

/**
* 将一些数据放到ShiroSession中,以便于其它地方使用
*
* 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
*/

private void setSession(Object key, Object value) {
Subject subject = SecurityUtils.getSubject();
if (null != subject) {
Session session = subject.getSession();
LOGGER.debug("Session默认超时时间为[" + session.getTimeout() + "]毫秒");
if (null != session) {
session.setAttribute(key, value);
}
}
}





}

2.后台realm

public class LoginRealm extends AuthorizingRealm {

private final Logger LOGGER = LoggerFactory.getLogger(LoginRealm.class);

private static final String ALGORITHM = "MD5";

@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Autowired
private UserKzService userKzService;

public LoginRealm() {
super();
}

/**
* 验证当前登录的Subject 本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()时
*/

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
// 这个authcToken是从LoginController里面currentUser.login(token)传过来的
UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authcToken;
LOGGER.debug("验证当前Subject时获取到token为"
+ ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
User users = userService.getUserByName(token.getUsername());
UserKz userKz =userKzService.selectByPrimaryKey(users.getUserId());
String usertype =userKz.getUserType();
// Shiro完成对比逻辑,返回和令牌相关的正确的验证信息,第一个参数填登录用户名,第二个参数填合法的登录密码
if (users != null&&usertype.equals("00")) {
ShiroUser shiroUser = new ShiroUser(users.getUserId(), users.getAccount());
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroUser,
users.getPassword(), getName());
this.setSession("shiroUser", shiroUser);
return authcInfo;
} else {
throw new AuthenticationException();
}
// 没有返回登录用户名对应的SimpleAuthenticationInfo对象时,就会在LoginController中抛出UnknownAccountException异常


}

/**
* 为当前登录的Subject授予角色和权限 ,该方法的调用时机为需授权资源被访问时
* 并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
* 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache
* 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache
*/

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.fromRealm(getName()).iterator().next();
String username = shiroUser.getAccount();
if (username != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

// 查询用户授权信息
List<Permission> perList = permissionService.getShiro("O");

if (perList != null && perList.size() != 0) {
for (Permission permission : perList) {
info.addStringPermission(permission.getPmsnCode());
}
return info;
}
}
return null;
}

/**
* 更新用户授权信息缓存.
*/

public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}

/**
* 清除所有用户授权信息缓存.
*/

public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}

@PostConstruct
public void initCredentialsMatcher() {// MD5加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ALGORITHM);
setCredentialsMatcher(matcher);
}

/**
* 将一些数据放到ShiroSession中,以便于其它地方使用
*
* 比如Controller,使用时直接用HttpSession.getAttribute(key)就可以取到
*/

private void setSession(Object key, Object value) {
Subject subject = SecurityUtils.getSubject();
if (null != subject) {
Session session = subject.getSession();
LOGGER.debug("Session默认超时时间为[" + session.getTimeout() + "]毫秒");
if (null != session) {
session.setAttribute(key, value);
}
}
}
}

3.书写总的管理realm

public class DefautModularRealm extends org.apache.shiro.authc.pam.ModularRealmAuthenticator {

private Map<String, Object> definedRealms;

/**
* 多个realm实现
*/

@Override
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
return super.doMultiRealmAuthentication(realms, token);
}
/**
* 调用单个realm执行操作
*/

@Override
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm,AuthenticationToken token) {

// 如果该realms不支持(不能验证)当前token
if (!realm.supports(token)) {
throw new ShiroException("token错误!");
}
AuthenticationInfo info = null;
try {
info = realm.getAuthenticationInfo(token);

if (info == null) {
throw new ShiroException("token不存在!");
}
} catch (Exception e) {
throw new ShiroException("用户名或者密码错误!");
}
return info;
}


/**
* 判断登录类型执行操作
*/

@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {
this.assertRealmsConfigured();
Realm realm = null;
UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authenticationToken;
//判断是否是后台用户
if (token.getUsertype().equals("admin")) {
realm = (Realm) this.definedRealms.get("loginRealm");
}
else{
realm = (Realm) this.definedRealms.get("userloginRealm");
}

return this.doSingleRealmAuthentication(realm, authenticationToken);
}

/**
* 判断realm是否为空
*/

@Override
protected void assertRealmsConfigured() throws IllegalStateException {
this.definedRealms = this.getDefinedRealms();
if (CollectionUtils.isEmpty(this.definedRealms)) {
throw new ShiroException("值传递错误!");
}
}

public Map<String, Object> getDefinedRealms() {
return this.definedRealms;
}

public void setDefinedRealms(Map<String, Object> definedRealms) {
this.definedRealms = definedRealms;
}






}

4.书写包含用户类型的token继承 UsernamePasswordToken

public class UsernamePasswordUsertypeToken extends UsernamePasswordToken {

private static final long serialVersionUID = 1L;
private String usertype ;

public String getUsertype() {
return usertype;
}
public void setUsertype(String usertype) {
this.usertype = usertype;
}

public UsernamePasswordUsertypeToken(String loginName, String password, String usertype) {

super(loginName, password);

this.usertype = usertype;

}

}

4.将配置写入到shiro的配置文件中

 <!-- 用户授权信息Cache 缓存在本机内存,不支持集群 -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>

<!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java -->
<bean id="loginRealm" class="com.jscredit.base.shiro.LoginRealm">
<property name="cacheManager" ref="cacheManager"/>
</bean>

<!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证前台用户登录的类为自定义的ShiroDbRealm.java -->
<bean id="userloginRealm" class="com.jscredit.zxypt.shiro.UserLoginRealm">
<property name="cacheManager" ref="cacheManager"/>
</bean>

<!--多个realm 的集中管理 -->
<bean id="defineModularRealmAuthenticator" class=" com.jscredit.zxypt.shiro.DefautModularRealm">
<property name="definedRealms">
<map>
<entry key="loginRealm" value-ref="loginRealm" />
<entry key="userloginRealm" value-ref="userloginRealm" />
</map>
</property>
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />
</property>
</bean>
<!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->
<!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->
<!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="authenticator" ref="defineModularRealmAuthenticator" />
<!-- <property name="realm" ref="loginRealm"/> -->
<property name="realms" >
<list>
<bean id="loginRealm" class="com.jscredit.base.shiro.LoginRealm" />
<bean id="userloginRealm" class="com.jscredit.zxypt.shiro.UserLoginRealm" />
</list>
</property>
<property name="cacheManager" ref="cacheManager"/>
</bean>

这样就可以在controller 中根据前后台传入不同的type 值 ,然后总的realm 会根据当前类型来判断执行哪个realm 进而执行不同的验证。然后我们再在realm中进行前后台用户的区分。进而执行自己的业务逻辑啦