java|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; /** * 【java|java web网站中使用shiro实现前后台登录功能】 * 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 *WhoWhenWhat *------------------------------------------------------------------ *Jason2016年3月1日上午1:13:06TODO * * ** */ 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 publicString toString(){ return this.type.toString(); } }


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




    推荐阅读