牛骨文教育服务平台(让学习变的简单)
博文笔记

java web网站中使用shiro实现前后台登录功能

创建时间:2016-03-29 投稿人: 浏览次数:12782

      年前公司网站需要重新开发一个新版本,在架构设计上废了一些劲,我项目架构大概是这样:Spring作为容器,Mybatis做数据持久层,SpringMVC做控制层,Shiro做为安全框架,页面使用tomdjs引擎,大概是这样,由于是功能型网站,需要有前后台登录,后台需要审核以及数据统计和分析等等,所以涉及到了两端登录,这里不多说,直接上关键代码:

<span style="font-size:14px;"><bean id="defineModularRealmAuthenticator"
		class="com.icc.base.shiro.authenticator.DefineModularRealmAuthenticator">
		<property name="defineRealms">
			<map>
				<entry key="customerRealm" value-ref="customerRealm" />
				<entry key="adminRealm" value-ref="adminRealm" />
			</map>
		</property>
</bean>
	
	<!-- 自定义前端验证规则 -->
	<bean id="customerRealm" class="com.icc.base.shiro.DefineRealm">
		<property name="credentialsMatcher" ref="credentialsMatcher" />
		<property name="customerService" ref="customerService" />
	</bean>
	<!-- 自定义后台验证规则 -->
	<bean id="adminRealm" class="com.icc.base.shiro.AdminRealm">
		<property name="credentialsMatcher" ref="credentialsMatcher" />
		<property name="usersService" ref="usersService" />
	</bean></span>

在shiro实现登录功能时,Subject的实现类会调用Authenticator这个接口的默认实现类ModularRealmAuthenticator来进行帐号密码以及验证码的验证,这个接口会取得Realm接口的实现类的List进行while循环验证,问题就在这,如果用List我们是搞不清楚前台后台应该用哪一个Realm来验证的,所以我重新实现了一下这个接口:

注:credentialsMatcher这个东西是密码的验证规则,代码没什么含量我就不贴了,大家查一下shiro的文档就明白

package com.icc.base.shiro.authenticator;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
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.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.icc.base.emun.LoginEnum;
import com.icc.base.exception.CaptchaException;
import com.icc.base.shiro.authc.CaptchaAuthenticationToken;

/**
 * <p>
 * Name:DefineModularRealmAuthenticator.java
 * </p>
 * <p>
 * Description: 自定义realm的验证规则 只要验证匹配直接返回
 * </p>
 * 
 * @author Jason
 * @see ModularRealmAuthenticator
 * @date 2016-1-25 下午12:46:24
 * @Modified By:
 * @Modified Date:
 * @version 1.0.0
 */
public class DefineModularRealmAuthenticator extends ModularRealmAuthenticator {

	private static final Logger log = LoggerFactory
			.getLogger(DefineModularRealmAuthenticator.class);
	
	/**
	 * 将Realm的实现类作为Map传入,这样便可以分清除前后台登录
	 */
	private Map<String, Object> defineRealms;

	/**
	 * 调用单个Realm来进行验证
	 */
	@Override
	protected AuthenticationInfo doSingleRealmAuthentication(Realm realm,
			AuthenticationToken token) {
		if (!realm.supports(token)) {
			String msg = "Realm ["
					+ realm
					+ "] does not support authentication token ["
					+ token
					+ "].  Please ensure that the appropriate Realm implementation is "
					+ "configured correctly or that the realm accepts AuthenticationTokens of this type.";
			throw new UnsupportedTokenException(msg);
		}
		AuthenticationInfo info = null;
		try {
			 info = realm.getAuthenticationInfo(token);
			if (info == null) {
				String msg = "Realm [" + realm
						+ "] was unable to find account data for the "
						+ "submitted AuthenticationToken [" + token + "].";
				throw new UnknownAccountException(msg);
			}
		}  catch (CaptchaException e) {
				throw e;
		} catch (IncorrectCredentialsException e) {
				throw e;
		}  catch (UnknownAccountException e) {
				throw e;
		}catch (Throwable throwable) {
			if (log.isDebugEnabled()) {
				String msg = "Realm ["
						+ realm
						+ "] threw an exception during a multi-realm authentication attempt:";
				log.debug(msg,throwable );
			}

		}
		
		return info;
	}
	
	/**
	 * 判断Realm是不是null
	 */
	@Override
	protected void assertRealmsConfigured() throws IllegalStateException {
		defineRealms = getDefineRealms();
		if (CollectionUtils.isEmpty(defineRealms)) {
			String msg = "Configuration error:  No realms have been configured!  One or more realms must be "
					+ "present to execute an authentication attempt.";
			throw new IllegalStateException(msg);
		}
	}
	/**
	 * 这个方法比较重要,用来判断此次调用是前台还是后台
	 */
	@Override
	protected AuthenticationInfo doAuthenticate(
			AuthenticationToken authenticationToken)
			throws AuthenticationException {
		assertRealmsConfigured();
		CaptchaAuthenticationToken token = (CaptchaAuthenticationToken) authenticationToken;
		Realm realm = null;
		// 前端登录
		if (StringUtils.equals(token.getLoginType(),
				LoginEnum.CUSTOMER.toString())) {
			 realm = (Realm) defineRealms.get("customerRealm");
		}
		// 后台登录
		if (StringUtils
				.equals(token.getLoginType(), LoginEnum.ADMIN.toString())) {
			 realm = (Realm) defineRealms.get("adminRealm");
			
		}
		if(realm==null){
			return null;
		}
		 return doSingleRealmAuthentication(realm, authenticationToken);

	}

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

	public Map<String, Object> getDefineRealms() {
		return defineRealms;
	}
}

这里需要解释一下的是这个参数authenticationToken,我自己实现了这个接口,看代码:

<span style="font-size:14px;"> 
package com.icc.base.shiro.authc;

import org.apache.shiro.authc.UsernamePasswordToken;


/**
 * * 
 *  @Program Name  : IWill.com.icc.base.shiro.authc.CaptchaAuthenticationToken.java
 *  @Written by    : Jason
 *  @Creation Date : 2016年3月1日 上午1:13:06 
 *  @version       : v1.00
 *  @Description   :  封装登录数据
 *  
 *  
 *  
 *  @ModificationHistory  
 *  Who          When                What 
 *  --------     ----------          ------------------------------------------------ 
 *  Jason   2016年3月1日上午1:13:06      TODO
 *  
 *  
**
 */
public class CaptchaAuthenticationToken extends UsernamePasswordToken {

	private String captcha;
	/**
	 * 用来区分前后台登录的标记
	 */
	private String loginType;
	
	public CaptchaAuthenticationToken(String username, String password, boolean rememberMe, String host, String captcha) {
		super(username, password, rememberMe, host);
		this.captcha = captcha;
	}

	public String getCaptcha() {
		return captcha;
	}

	public void setCaptcha(String captcha) {
		this.captcha = captcha;
	}

	public String getLoginType() {
		return loginType;
	}

	public void setLoginType(String loginType) {
		this.loginType = loginType;
	}

}
</span>

下面看一下登录的代码,大家应该就都明白了:

前端登录关键代码:

CaptchaAuthenticationToken token = new CaptchaAuthenticationToken(username, password, false, host, captcha);
token.setLoginType(LoginEnum.CUSTOMER.toString());
这里传入了前端登陆的标记,结合上面代码doAuthenticate()方法的判断,应该没什么不明白的.

这是后端登录的关键代码:

CaptchaAuthenticationToken token = new CaptchaAuthenticationToken(username, password, false, host, captcha);</span>
<span style="font-size:14px;">token.setLoginType(LoginEnum.ADMIN.toString());
没什么问题吧,至于Realm的代码我就不贴了,跟大家写的一样,以上都是关键代码,应该不难懂.

本人原创,只想帮大家解决一个问题,不喜勿喷,有问题欢迎一起讨论!

补充:

有朋友需要LoginEnum的代码,其实功能很简单,就是用标识来区分2种登录的,下面看代码就明白了:

public enum LoginEnum {

	CUSTOMER("1"),ADMIN("2");
	
	private String type;
	
	private LoginEnum(String type){
		this.type = type;
	}
	@Override
	public  String toString(){
		return this.type.toString();
	}
}

很简单吧.

关于xml配置的问题,以上是全部shiro的配置,我的项目目录结构是每一个框架单独做一个xml文件,然后用一个总的xml来包含所有文件,最后在web.xml里加载一下这个总的xml就可以了,shiro的web.xml配置与平常配置一样




声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。