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

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


		
			
				
				
			
		

	
	
	
		
		
	
	
	
		
		
	

在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;

/**
 * 

* Name:DefineModularRealmAuthenticator.java *

*

* Description: 自定义realm的验证规则 只要验证匹配直接返回 *

* * @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 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 defineRealms) { this.defineRealms = defineRealms; } public Map getDefineRealms() { return defineRealms; } }

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

 
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;
	}

}

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

前端登录关键代码:

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

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

CaptchaAuthenticationToken token = new CaptchaAuthenticationToken(username, password, false, host, captcha);
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配置与平常配置一样




你可能感兴趣的:(java web网站中使用shiro实现前后台登录功能)