年前公司网站需要重新开发一个新版本,在架构设计上废了一些劲,我项目架构大概是这样: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;
}
}
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配置与平常配置一样