本文介绍用户的认证,认证通过三个参数进行:用户名,密码和验证码。首先介绍认证的业务流程和实现方法,再介绍 shiro 的认证流程和原理,并加以实现。
1. 认证的流程和实现
1.1 前台发起校验的异步请求
- 将输入的账号,密码和验证码拼接成一个字符串 code,用逗号分隔
- 再发起一个“login_login”异步请求
- 如果认证不通过,后台返回校验信息在前台显示
- 如果认证通过则跳转 main/index 方法
function severCheck(){
if(check()){
var loginname = $("#loginname").val();
var password = $("#password").val();
var code = loginname+","+password+","+$("#code").val();
$.ajax({
type: "POST",
url: 'login_login',
data: {KEYDATA:code,tm:new Date().getTime()},
dataType:'json',
cache: false,
success: function(data){
if("success" == data.result){
saveCookie();
window.location.href="main/index";
}else if("usererror" == data.result){
$("#loginname").tips({
side : 1,
msg : "用户名或密码有误",
bg : '#FF5080',
time : 15
});
showfh();
$("#loginname").focus();
}else if("codeerror" == data.result){
$("#code").tips({
side : 1,
msg : "验证码输入有误",
bg : '#FF5080',
time : 15
});
showfh();
$("#code").focus();
}else{
$("#loginname").tips({
side : 1,
msg : "缺少参数",
bg : '#FF5080',
time : 15
});
showfh();
$("#loginname").focus();
}
}
});
}
}
1.2 后台校验
校验业务流程如下图所示
代码实现如下,只有最后一部分涉及 shiro 的认证,只想了解 shiro 认证直接看第二部分
@RequestMapping(value="login_login",produces="application/json;charset=UTF-8")//接收 json 格式数据 && 编码格式为 UTF-8
@ResponseBody
public Object login() throws Exception{
Map<String,String> map = new HashMap<String,String>();
PageData pd = new PageData();
pd = this.getPageData();// 获取页面参数,封装在一个 map 中,自己编写封装方法
String errInfo = ""; // 返回消息
String[] KEYDATA = pd.getString("KEYDATA").split(",");
if(KEYDATA != null && KEYDATA.length == 3){
// 获取 shiro 的 session:SecurityUtils.getSubject().getSession();
Session session = Jurisdiction.getSession();
String sessionCode = (String) session.getAttribute(Const.SESSION_SECURITY_CODE); //获取session 中的验证码
String code = KEYDATA[2]; // 输入的验证码
if(code == null || code.equals("")){
errInfo = "nullcode"; // 验证码为空
}else{
String USERNAME = KEYDATA[0];
String PASSWORD = KEYDATA[1];
pd.put("USERNAME", USERNAME);
if(Tools.notEmpty(sessionCode) && sessionCode.equals(code)){ // 验证码校验
//密码加密
String passwd = new SimpleHash("SHA-1", USERNAME, PASSWORD).toString();
pd.put("PASSWORD", passwd);
pd = userService.getUserByNameAndPwd(pd);
if(pd != null){
pd.put("LAST_LOGIN",DateUtil.getTime().toString());
userService.updateLastLogin(pd);
User user = new User();
user.setUSER_ID(pd.getString("USER_ID"));
user.setUSERNAME(pd.getString("USERNAME"));
user.setPASSWORD(pd.getString("PASSWORD"));
user.setNAME(pd.getString("NAME"));
user.setRIGHTS(pd.getString("RIGHTS"));
user.setROLE_ID(pd.getString("ROLE_ID"));
user.setLAST_LOGIN(pd.getString("LAST_LOGIN"));
user.setIP(pd.getString("IP"));
user.setSTATUS(pd.getString("STATUS"));
session.setAttribute(Const.SESSION_USER, user); //把用户信息放session中
session.removeAttribute(Const.SESSION_SECURITY_CODE); //清除登录验证码的session
//shiro加入身份验证
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(USERNAME, PASSWORD);
try {
subject.login(token);
} catch (AuthenticationException e) {
errInfo = "身份验证失败!";
}
}else{
errInfo = "usererror"; //用户名或者密码错误
}
}else{
errInfo = "codeerror"; // 验证码有误
}
if(Tools.isEmpty(errInfo)){
errInfo = "success"; //验证成功
}
}
}else{
errInfo = "error"; //缺少参数
}
map.put("result", errInfo);
return AppUtil.returnObject(new PageData(), map);
}
2. shiro 实现认证
上文认证流程,只有最后部分利用 shiro 进行身份认证
//shiro加入身份验证
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(USERNAME, PASSWORD);
try {
subject.login(token);
} catch (AuthenticationException e) {
errInfo = "身份验证失败!";
}
2.1 shiro 认证流程
1、应用程序构建了一个终端用户认证信息的AuthenticationToken 实例后,调用Subject.login方法。 2、Subject的实例通常是DelegatingSubject类(或子类)的实例对象,在认证开始时,会委托应用程序设置的securityManager实例调用securityManager.login(token)方法。 3、SecurityManager接受到token(令牌)信息后会委托内置的Authenticator的实例(通常都是ModularRealmAuthenticator类的实例)调用authenticator.authenticate(token). ModularRealmAuthenticator在认证过程中会对设置的一个或多个Realm实例进行适配,它实际上为Shiro提供了一个可拔插的认证机制。 4、如果在应用程序中配置了多个Realm,ModularRealmAuthenticator会根据配置的AuthenticationStrategy(认证策略)来进行多Realm的认证过程。在Realm被调用后,AuthenticationStrategy将对每一个Realm的结果作出响应。 注:如果应用程序中仅配置了一个Realm,Realm将被直接调用而无需再配置认证策略。 5、判断每一个Realm是否支持提交的token,如果支持,Realm将调用getAuthenticationInfo(token); getAuthenticationInfo 方法就是实际认证处理,我们通过覆盖Realm的doGetAuthenticationInfo方法来编写我们自定义的认证处理。
所以我们需要做两件事:
1. 添加自定义realm
2. 编写自定义 realm
2.2 配置文件添加
<!-- 自定义 realm 用于校验 -->
<bean id="shiroRealm" class="com.shuiyujie.interceptor.shiro.ShiroRealm" />
2.3 自定义 realm 中的认证方法
/*
* 登录信息和用户验证信息验证(non-Javadoc)
* @see org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String)token.getPrincipal(); //得到用户名
String password = new String((char[])token.getCredentials()); //得到密码
if(null != username && null != password){
return new SimpleAuthenticationInfo(username, password, getName());
}else{
return null;
}
}
2.4 用户退出
@RequestMapping(value="/logout")
public ModelAndView logout(){
String USERNAME = Jurisdiction.getUsername(); //当前登录的用户名
logBefore(logger, USERNAME+"退出系统");
ModelAndView mv = this.getModelAndView();
PageData pd = new PageData();
Session session = Jurisdiction.getSession(); //以下清除session缓存
session.removeAttribute(Const.SESSION_USER);
.
.
.
//shiro销毁登录
Subject subject = SecurityUtils.getSubject();
subject.logout();
// 跳转回登录界面
mv.setViewName("system/index/login");
mv.addObject("pd",pd);
return mv;
}
还差个认证成功跳转的方法,“/main/index”
-略-