需求背景
在做一个学习系统,接到需求要前后台用户通过不同的界面登录,前台用户不能通过他的账号登录后台,后台用户也不能用后台账号登录前台页面,前后台用户还有各自的权限级别划分。大概就这么个意思,这是一个比较常见的需求,大家心里能明白我说的就行了(虽然你可能并不明白…)
实现思路
该项目采用的是springBoot + Mybatis的技术框架,用户验证这一块采用的是shiro框架。shiro框架多种用户登录,实现的思路如下:
- Realm
一种用户创建一个Realm(有关Realm的知识大家可以去看看文档),因此我们这里就需要两个Realm,前台用户,后台用户各一个。
代码如下:
前台用户
package com.ruoyi.framework.shiro.realm;
import com.ruoyi.common.exception.user.*;
import com.ruoyi.framework.shiro.service.SysLoginService;
import com.ruoyi.framework.shiro.token.UserToken;
import com.ruoyi.framework.util.ShiroUtils;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysRoleService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
public class ReceptionRealm extends AuthorizingRealm {
private static final Logger log = LoggerFactory.getLogger(ReceptionRealm.class);
@Autowired
private ISysMenuService menuService;
@Autowired
private ISysRoleService roleService;
@Autowired
private SysLoginService loginService;
/**
* 授权逻辑
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
SysUser user = ShiroUtils.getSysUser();
// 角色列表
Set<String> roles = new HashSet<String>();
// 功能列表
Set<String> menus = new HashSet<String>();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
roles = roleService.selectRoleKeys(user.getUserId());
menus = menuService.selectPermsByUserId(user.getUserId());
// 角色加入AuthorizationInfo认证对象
info.setRoles(roles);
// 权限加入AuthorizationInfo认证对象
info.setStringPermissions(menus);
return info;
}
/**
* 登录逻辑
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UserToken upToken=(UserToken)token;
String username = upToken.getUsername();
String password = "";
if (upToken.getPassword() != null)
{
password = new String(upToken.getPassword());
}
SysUser user = null;
try
{
user = loginService.login(username, password);
}
catch (CaptchaException e)
{
throw new AuthenticationException(e.getMessage(), e);
}
catch (UserNotExistsException e)
{
throw new UnknownAccountException(e.getMessage(), e);
}
catch (UserPasswordNotMatchException e)
{
throw new IncorrectCredentialsException(e.getMessage(), e);
}
catch (UserPasswordRetryLimitExceedException e)
{
throw new ExcessiveAttemptsException(e.getMessage(), e);
}
catch (UserBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (RoleBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (Exception e)
{
log.info("对前台用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
throw new AuthenticationException(e.getMessage(), e);
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
/**
* 清理缓存权限
*/
public void clearCachedAuthorizationInfo()
{
this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
}
}
后台用户
package com.ruoyi.framework.shiro.realm;
import java.util.HashSet;
import java.util.Set;
import com.ruoyi.framework.shiro.token.UserToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.ruoyi.common.exception.user.CaptchaException;
import com.ruoyi.common.exception.user.RoleBlockedException;
import com.ruoyi.common.exception.user.UserBlockedException;
import com.ruoyi.common.exception.user.UserNotExistsException;
import com.ruoyi.common.exception.user.UserPasswordNotMatchException;
import com.ruoyi.common.exception.user.UserPasswordRetryLimitExceedException;
import com.ruoyi.framework.shiro.service.SysLoginService;
import com.ruoyi.framework.util.ShiroUtils;
import com.ruoyi.system.domain.SysUser;
import com.ruoyi.system.service.ISysMenuService;
import com.ruoyi.system.service.ISysRoleService;
/**
* 自定义Realm 处理登录 权限
*
* @author ruoyi
*/
public class UserRealm extends AuthorizingRealm
{
private static final Logger log = LoggerFactory.getLogger(UserRealm.class);
@Autowired
private ISysMenuService menuService;
@Autowired
private ISysRoleService roleService;
@Autowired
private SysLoginService loginService;
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0)
{
SysUser user = ShiroUtils.getSysUser();
// 角色列表
Set<String> roles = new HashSet<String>();
// 功能列表
Set<String> menus = new HashSet<String>();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 管理员拥有所有权限
if (user.isAdmin())
{
info.addRole("admin");
info.addStringPermission("*:*:*");
}
else
{
roles = roleService.selectRoleKeys(user.getUserId());
menus = menuService.selectPermsByUserId(user.getUserId());
// 角色加入AuthorizationInfo认证对象
info.setRoles(roles);
// 权限加入AuthorizationInfo认证对象
info.setStringPermissions(menus);
}
return info;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
UserToken upToken = (UserToken) token;
String username = upToken.getUsername();
String password = "";
if (upToken.getPassword() != null)
{
password = new String(upToken.getPassword());
}
SysUser user = null;
try
{
user = loginService.login(username, password);
}
catch (CaptchaException e)
{
throw new AuthenticationException(e.getMessage(), e);
}
catch (UserNotExistsException e)
{
throw new UnknownAccountException(e.getMessage(), e);
}
catch (UserPasswordNotMatchException e)
{
throw new IncorrectCredentialsException(e.getMessage(), e);
}
catch (UserPasswordRetryLimitExceedException e)
{
throw new ExcessiveAttemptsException(e.getMessage(), e);
}
catch (UserBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (RoleBlockedException e)
{
throw new LockedAccountException(e.getMessage(), e);
}
catch (Exception e)
{
log.info("对用户[" + username + "]进行登录验证..验证未通过{}", e.getMessage());
throw new AuthenticationException(e.getMessage(), e);
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
/**
* 清理缓存权限
*/
public void clearCachedAuthorizationInfo()
{
this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
}
}
- 新建Token
由于我们有两个Realm,所以默认的UsernamePasswordToken不能满足我们的需求,因为我们要在token中添加loginType来区分两个Realm,所以我们新建一个userToken继承UsernamePasswordToken:
package com.ruoyi.framework.shiro.token;
import org.apache.shiro.authc.UsernamePasswordToken;
public class UserToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1L;
public static final String ReceptionType="Reception";
public static final String AdminType="User";
private String loginType;
public UserToken(final String username, final String password, final Boolean rememberMe, String loginType) {
super(username, password,rememberMe);
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
- 新建ModularRealmAuthenticator
由于shiro在认证过程中会调用ModularRealmAuthenticator类中的doAuthenticate方法,由于我们现在有两个Realm,因此该方法中的逻辑有所不同,我们因此要重写一个该类中的方法:
package com.ruoyi.framework.shiro.service;
import com.ruoyi.framework.shiro.token.UserToken;
import org.apache.shiro.ShiroException;
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 org.apache.shiro.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
// 判断getRealms()是否返回为空
assertRealmsConfigured();
UserToken userToken = (UserToken) authenticationToken;
String loginType=userToken.getLoginType();
Collection<Realm> realms = getRealms();
List<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm.getName().contains(loginType)) {
typeRealms.add(realm);
}
}
// 判断是单realm还是多realm
if (typeRealms.size() == 1){
return doSingleRealmAuthentication(typeRealms.get(0), userToken);
}
else{
return doMultiRealmAuthentication(typeRealms, userToken);
}
}
}
4.配置shiroConfig
由于我这个项目是采用springBoot,因此是在配置类中将新建的Realm添加进去,在这其中最重要的就是配置 SecurityManager:
/**
* 安全管理器
*/
@Bean
public SecurityManager securityManager(UserRealm userRealm, ReceptionRealm receptionRealm, SpringSessionValidationScheduler springSessionValidationScheduler)
{
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm
securityManager.setAuthenticator(modularRealmAuthenticator());
List<Realm> realms = new ArrayList<>();
realms.add(userRealm);
realms.add(receptionRealm);
securityManager.setRealms(realms);
// 记住我
securityManager.setRememberMeManager(rememberMeManager());
// 注入缓存管理器;
securityManager.setCacheManager(getEhCacheManager());
// session管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
配置Realm:
/**
* 自定义Realm
*/
@Bean
public UserRealm userRealm(EhCacheManager cacheManager)
{
UserRealm userRealm = new UserRealm();
userRealm.setCacheManager(cacheManager);
return userRealm;
}
/**
* 前台登录用户Realm
*/
@Bean
public ReceptionRealm receptionRealm(EhCacheManager cacheManager)
{
ReceptionRealm receptionRealm = new ReceptionRealm();
receptionRealm.setCacheManager(cacheManager);
return receptionRealm;
}
配置Realm管理:
/**
* Realm管理,针对多realm
* @return
*/
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
前台用户的过滤器:
/**
* 前台数据过滤器
* @return
*/
public UserFilter receptionUserFilter() {
UserFilter receptionFilter=new UserFilter();
receptionFilter.setLoginUrl(receptionLoginUrl);
return receptionFilter;
}
另外还要在Shiro过滤器中将相关的资源配置到ShiroFilterFactoryBean中,至此差不多算是完成了,其余的一些工作要根据自己的项目来做调整,毕竟没有什么方法是能解决所有问题的。
向下一个bug进发!