Shiro框架针对不同登录界面和不同角色用户

开发思路

最近,项目经理分配的一个任务是:要求根据不同角色身份的用户设计不用的登录界面,同时,用户不能跨登录界面登录。原话,我忘记了,意思是:比如,管理员只能用管理员登录界面登录,普通用户只能用普通用户登录界面的登录。因为,我们的项目,登录时,shiro会对请求进行拦截,并根据绑定的realm完成校验… …现在我就根据代码详细的说明,如果有说错的地方,希望能不吝赐教。


Filter

请求被authc拦截,如果状态未登录,就会被跳到登录页面,登录成功后,会继续原请求页面,除非原请求就是successurl,才去successurl
/**
 * 表单验证(包含验证码)过滤类
 * @author jeeplus
 * @version 2014-5-19
 */
@Service
public class EmployeeAuthenticationFilter extends org.apache.shiro.web.filter.authc.FormAuthenticationFilter {
	

	public static final String DEFAULT_CAPTCHA_PARAM = "validateCode";
	public static final String DEFAULT_MOBILE_PARAM = "mobileLogin";
	public static final String DEFAULT_MESSAGE_PARAM = "message";
	private SystemService systemService;
	
	private String captchaParam = DEFAULT_CAPTCHA_PARAM;
	private String mobileLoginParam = DEFAULT_MOBILE_PARAM;
	private String messageParam = DEFAULT_MESSAGE_PARAM;

	protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
		String username = getUsername(request);
		String password = getPassword(request);
		if (password==null){
			password = "";
		}
		boolean rememberMe = isRememberMe(request);
		String host = StringUtils.getRemoteAddr((HttpServletRequest)request);
		String captcha = getCaptcha(request);
		boolean mobile = isMobileLogin(request);
		String loginType = "user";
		return new UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha, mobile,loginType);
	}
	/**
	 * 获取系统业务对象
	 */
	public SystemService getSystemService() {
		if (systemService == null){
			systemService = SpringContextHolder.getBean(SystemService.class);
		}
		return systemService;
	}

	public String getCaptchaParam() {
		return captchaParam;
	}

	protected String getCaptcha(ServletRequest request) {
		return WebUtils.getCleanParam(request, getCaptchaParam());
	}
	
	

	public String getMobileLoginParam() {
		return mobileLoginParam;
	}
	
	protected boolean isMobileLogin(ServletRequest request) {
        return WebUtils.isTrue(request, getMobileLoginParam());
    }
	
	public String getMessageParam() {
		return messageParam;
	}
	
	/**
	 * 登录成功之后跳转URL
	 */
	public String getSuccessUrl() {
		return super.getSuccessUrl();
	}
	
	@Override
	protected void issueSuccessRedirect(ServletRequest request,
			ServletResponse response) throws Exception {
		Principal p = UserUtils.getPrincipal();
		if (p != null && !p.isMobileLogin()){
			 WebUtils.issueRedirect(request, response, getSuccessUrl(), null, true);
		}else{
			//super.issueSuccessRedirect(request, response);//手机登录
			AjaxJson j = new AjaxJson();
			j.setSuccess(true);
			j.setMsg("登录成功!");
			j.put("username", p.getLoginName());
			j.put("name", p.getName());
			j.put("mobileLogin", p.isMobileLogin());
			j.put("JSESSIONID", p.getSessionid());
			PrintJSON.write((HttpServletResponse)response, j.getJsonStr());
		}
	}

	/**
	 * 登录失败调用事件
	 */
	@Override
	protected boolean onLoginFailure(AuthenticationToken token,
			AuthenticationException e, ServletRequest request, ServletResponse response) {
		String className = e.getClass().getName(), message = "";
		if (IncorrectCredentialsException.class.getName().equals(className)
				|| UnknownAccountException.class.getName().equals(className)){
			message = "用户或密码错误, 请重试.";
		}
		else if (e.getMessage() != null && StringUtils.startsWith(e.getMessage(), "msg:")){
			message = StringUtils.replace(e.getMessage(), "msg:", "");
		}
		else{
			message = "系统出现点问题,请稍后再试!";
			e.printStackTrace(); // 输出到控制台
		}
        request.setAttribute(getFailureKeyAttribute(), className);
        request.setAttribute(getMessageParam(), message);
        return true;
	}
	
}

由于代码比较多,我尽量粘贴全,以保证各位能够有清晰的了解;我会对主要的代码进行解释,其余的就不在过多叙述了。在输入完登录信息后,当我们点击登录,首先,Filter会进行过滤,首先,优先创建一个token(方法是:createToken),相信各位应该都能看懂,不过在此我要解释一下,loginType是我继承org.apache.shiro.authc.UsernamePasswordToken 然后新增的一个属性,因为,我要对不同页面进行辨别,这里,注意,因为我是两个登录页面,所以就需要两个Filter进行分别过滤,但是两者代码几乎相同,唯一不同的就是loginType=“admin”(loginType=“user”),所以就不粘贴了。

	/**
 * 自定义认证器
 * 
 * @author Sunny
 */
public class LoginRealmAuthenticator extends ModularRealmAuthenticator {
	
	private Map<String,Object> definedRealms;
	private SystemService systemService;
	
	public void setDefinedRealms(Map<String, Object> definedRealms) {
		this.definedRealms = definedRealms;
	}
	
	/**
	 * 获取系统业务对象
	 */
	public SystemService getSystemService() {
		if (systemService == null){
			systemService = SpringContextHolder.getBean(SystemService.class);
		}
		return systemService;
	}
	
	/**
	 * 根据用户类型判断使用哪个Realm
	 */
	@Override
	protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
			throws AuthenticationException {
		super.assertRealmsConfigured();
		Realm realm = null;
		// 使用自定义Token
		UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
		// 判断用户类型
		if ("admin".equals(token.getLoginType())) {
			realm = (Realm) this.definedRealms.get("systemAuthorizingRealm");
		} else if ("user".equals(token.getLoginType())) {
			realm = (Realm) this.definedRealms.get("loginAuthorizingRealm");
		}
		return this.doSingleRealmAuthentication(realm, authenticationToken);
	}
}

当创建完token后,shiro就会通过认证器(至于为什么会这样,因为,是我配置文件这么配置的),这代码逻辑就比较简单了,就是根据loginType去选择所对应的realm,就不过多解释了。

/**
 * 系统安全认证实现类
 * @author jeeplus
 * @version 2014-7-5
 */
@Service
//@DependsOn({"userDao","roleDao","menuDao"})
public class LoginAuthorizingRealm extends AuthorizingRealm {

	private Logger logger = LoggerFactory.getLogger(getClass());
	
	private SystemService systemService;
	
	@Autowired
	HttpServletRequest request;

	/**
	 * 认证回调函数, 登录时调用
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		
		int activeSessionSize = getSystemService().getSessionDao().getActiveSessions(false).size();
		if (logger.isDebugEnabled()){
			logger.debug("login submit, active session size: {}, username: {}", activeSessionSize, token.getUsername());
		}
		
		boolean mobile = WebUtils.isTrue(request, FormAuthenticationFilter.DEFAULT_MOBILE_PARAM);
		// 校验登录验证码
		if (!mobile && LoginController.isValidateCodeLogin(token.getUsername(), false, false)){
			Session session = UserUtils.getSession();
			String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE);
			if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){
				throw new AuthenticationException("msg:验证码错误, 请重试.");
			}
		}
		
		// 校验用户名密码
		User user = getSystemService().getUserByLoginName(token.getUsername());
		if (user != null) {
			if (Global.NO.equals(user.getLoginFlag())){
				throw new AuthenticationException("msg:该已帐号禁止登录.");
			}
			if("1".equals(user.getUserType())||"2".equals(user.getUserType())) {
				throw new AuthenticationException("msg:只能普通用户登录.");
			}
			byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16));
			return new SimpleAuthenticationInfo(new Principal(user, token.isMobileLogin()), 
					user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
		} else {
			return null;
		}
	}

	/**
	 * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		Principal principal = (Principal) getAvailablePrincipal(principals);
		// 获取当前已登录的用户
		if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))){
			Collection<Session> sessions = getSystemService().getSessionDao().getActiveSessions(true, principal, UserUtils.getSession());
			if (sessions.size() > 0){
				// 如果是登录进来的,则踢出已在线用户
				if (UserUtils.getSubject().isAuthenticated()){
					for (Session session : sessions){
						getSystemService().getSessionDao().delete(session);
					}
				}
				// 记住我进来的,并且当前用户已登录,则退出当前用户提示信息。
				else{
					UserUtils.getSubject().logout();
					throw new AuthenticationException("msg:账号已在其它地方登录,请重新登录。");
				}
			}
		}
		User user = getSystemService().getUserByLoginName(principal.getLoginName());
		if (user != null) {
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			List<Menu> list = UserUtils.getMenuList();
			for (Menu menu : list){
				if (StringUtils.isNotBlank(menu.getPermission())){
					// 添加基于Permission的权限信息
					for (String permission : StringUtils.split(menu.getPermission(),",")){
						info.addStringPermission(permission);
					}
				}
			}
			// 添加用户权限
			info.addStringPermission("user");
			// 添加用户角色信息 
			for (Role role : user.getRoleList()){
				info.addRole(role.getEnname());
			}
			// 更新登录IP和时间
			getSystemService().updateUserLoginInfo(user);
			// 记录登录日志
			LogUtils.saveLog(Servlets.getRequest(), "系统登录");
			return info;
		} else {
			return null;
		}
	}
	
	@Override
	protected void checkPermission(Permission permission, AuthorizationInfo info) {
		authorizationValidate(permission);
		super.checkPermission(permission, info);
	}
	
	@Override
	protected boolean[] isPermitted(List<Permission> permissions, AuthorizationInfo info) {
		if (permissions != null && !permissions.isEmpty()) {
            for (Permission permission : permissions) {
        		authorizationValidate(permission);
            }
        }
		return super.isPermitted(permissions, info);
	}
	
	@Override
	public boolean isPermitted(PrincipalCollection principals, Permission permission) {
		authorizationValidate(permission);
		return super.isPermitted(principals, permission);
	}
	
	@Override
	protected boolean isPermittedAll(Collection<Permission> permissions, AuthorizationInfo info) {
		if (permissions != null && !permissions.isEmpty()) {
            for (Permission permission : permissions) {
            	authorizationValidate(permission);
            }
        }
		return super.isPermittedAll(permissions, info);
	}
	
	/**
	 * 授权验证方法
	 * @param permission
	 */
	private void authorizationValidate(Permission permission){
		// 模块授权预留接口
	}
	
	/**
	 * 设定密码校验的Hash算法与迭代次数
	 */
	@PostConstruct
	public void initCredentialsMatcher() {
		HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SystemService.HASH_ALGORITHM);
		matcher.setHashIterations(SystemService.HASH_INTERATIONS);
		setCredentialsMatcher(matcher);
	}
	
//	/**
//	 * 清空用户关联权限认证,待下次使用时重新加载
//	 */
//	public void clearCachedAuthorizationInfo(Principal principal) {
//		SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
//		clearCachedAuthorizationInfo(principals);
//	}

	/**
	 * 清空所有关联认证
	 * @Deprecated 不需要清空,授权缓存保存到session中
	 */
	@Deprecated
	public void clearAllCachedAuthorizationInfo() {
//		Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
//		if (cache != null) {
//			for (Object key : cache.keys()) {
//				cache.remove(key);
//			}
//		}
	}

	/**
	 * 获取系统业务对象
	 */
	public SystemService getSystemService() {
		if (systemService == null){
			systemService = SpringContextHolder.getBean(SystemService.class);
		}
		return systemService;
	}
	
}

经过认证器验证后,就会找到所对应的Realm,这时,就要执行doGetAuthenticationInfo方法,对登录用户信息进行验证了,首要对非手机登录和验证码登录进行判断,若是,则校验验证码是否正确;然后,通过用户的loginName获取用户信息,若不为空,就对密码进行加密并与数据库中加密密码比较,若,一致,则返回到Filter中,issueSuccessRedirect该方法就代表的校验成功时,会跳转到配置文件中定义的成功界面去;若,校验失败,就到Filter的onLoginFailure中,然后返回Controller定义的失败方法中,将失败信息返回给前端。
配置文件:

<!-- Shiro权限过滤过滤器定义 -->
	<bean name="shiroFilterChainDefinitions" class="java.lang.String">
		<constructor-arg>
			<value>
				/static/** = anon
				/userfiles/** = anon
				${adminPath}/sys/user/infoCareStatus = anon
				${adminPath}/sys/user/validateLoginName = anon
				${adminPath}/sys/user/validateMobile = anon
				${adminPath}/sys/user/validateMobileExist = anon
				${adminPath}/sys/user/resetPassword = anon
				${adminPath}/sys/register = anon
				${adminPath}/sys/register/registerUser = anon
				${adminPath}/sys/register/getRegisterCode = anon
				${adminPath}/sys/register/validateMobileCode = anon
				${adminPath}/soft/sysVersion/getAndroidVer = anon
				${adminPath}/soft/sysVersion/getIosVer = anon
				${adminPath}/cas = cas
				${adminPath}/login = authc
				${adminPath}/loginEmployee = membersAuthc
				${adminPath}/logout = anon
				${adminPath}/** = user
				/act/rest/service/editor/** = perms[act:model:edit]
				/act/rest/service/model/** = perms[act:model:edit]
				/act/rest/service/** = user
				/ReportServer/** = user
			</value>
		</constructor-arg>
	</bean>
	
<bean id="membersAuthc"
		class="com.jeeplus.modules.sys.security.EmployeeAuthenticationFilter">
		<property name="loginUrl" value="${adminPath}/loginEmployee" />
		<property name="successUrl" value="${adminPath}?loginEmployee" />
	</bean>

	
	<!-- 安全认证过滤器 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" /><!-- 
		<property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" /> -->
		<property name="loginUrl" value="${adminPath}/login" />
		<property name="successUrl" value="${adminPath}?login" />
		<property name="filters">
            <map>
                <entry key="cas" value-ref="casFilter"/>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
                <entry key="membersAuthc" value-ref="membersAuthc"/>
            </map>
        </property>
		<property name="filterChainDefinitions">
			<ref bean="shiroFilterChainDefinitions"/>
		</property>
	</bean>
	<bean id="definedRealms" class="org.springframework.beans.factory.config.MapFactoryBean">
        <property name="sourceMap">
            <map>
                <entry key="systemAuthorizingRealm" value-ref="systemAuthorizingRealm"/>
                <entry key="loginAuthorizingRealm" value-ref="loginAuthorizingRealm" />
            </map>
        </property>

    </bean>

	
	<bean id="loginRealmAuthenticator" class="com.jeeplus.modules.sys.security.LoginRealmAuthenticator">
	    <property name="definedRealms" ref="definedRealms"/> 
	</bean>
	
	<!-- 定义Shiro安全管理配置 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="authenticator" ref="loginRealmAuthenticator" />
		<property name="realms">
		<list>
			<ref bean="systemAuthorizingRealm" />
			<ref bean="loginAuthorizingRealm" />
		</list>
	</property>
		<property name="sessionManager" ref="sessionManager" />
		<property name="cacheManager" ref="shiroCacheManager" />
	</bean>

配置文件只是将登录Shiro认证的主要配置写在这里。
web.xml就不在这里贴了,但是,请别忘记将shiroFilter过滤器配置进去,否则,就是,无用功了。

离线写博客

这篇博客要感谢https://blog.csdn.net/z947511564/article/details/52261546?utm_source=itdadao&utm_medium=referral作者以及https://blog.csdn.net/mn1992602/article/details/73527889/作者等等,我是参考了众多博客楼主的思路,在结合我自己项目的实际需求,完成开发的,技术永远就是分享就是进步。

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值