SSM+Shiro+Bootstrap+Jquery项目实践之用户登陆

早在一年前,我就想着自己要写一个完整的Web项目出来,然后开源,供所有的Web开发者探讨当下互联网企业流行的技术,可是由于种种原因未能付诸实践,所以在新的一年,我要坚持下去,从现在开始,利用休息时间建立这个系统。这个项目的目的不是为了盲目追求技术,而是能快速、优雅地解决现实中的实际需求,希望广大Web爱好者和我共同实现这个目标。

 我没有写前端页面的基础,所以参考了一个比较成熟的框架AdminLTE,里面界面做的非常好。登陆界面很简单,就是一个form表单。

 我没有直接使用form的action+submit去提交这个表单,而是采用手动点击普通按钮button去触发login()。是的,一个
JavaScript函数来做这个提交动作,目的就是为了适应现实项目中实际需求,因为很多表单提交前的验证要放在action之前做。所以这里要引入一个之前写好的login.js

function login() {
    // 获取表单对象
    var bootstrapValidator = $('#login-form').data('bootstrapValidator');
    // 验证表单
    bootstrapValidator.validate();
    // 是否通过了验证
    if (!$('#login-form').data('bootstrapValidator').isValid()) {
	return;
    }
    var data = $('#login-form').serializeJSON();
    console.log(data);
    $.ajax({
	type : "POST",
	dataType : "json",
	contentType : "application/json;charset=utf-8",
	url : "/login",
	data : JSON.stringify(data),// 这里要传json字符串
	success : function(result) {
	    if (result.status == 200) {
		var resultdata = result.data;
		if (resultdata) {
		    // 保存临时的cookie
		    setCookie("username", resultdata.username);
		    setCookie("rolename", resultdata.rolename);
		    // 如果点击了记住我,那么存入localstorage
		    rememberMe($("input[name='rememberMe']").is(":checked"));
		}
		// 跳转主页
		window.location.href = "/";
	    } else {
		new LoginValidator({
		   code:result.status,
		   message:result.msg,
		   username:$("input[name='username']").val(),
		   password:$("input[name='password']").val()
		});
	    }
	},
	error : function() {
	    alert("异常!");
	}
    });
 
}
 
function LoginValidator(config) {
    this.code = config.code;
    this.message = config.message;
    this.userName = config.username;
    this.password = config.password;
    this.initValidator();
}
 
// 0 未授权 1 账号问题 2 密码错误 3 账号密码错误
LoginValidator.prototype.initValidator = function() {
    if (!this.code)
	return;
    if (this.code == 0) {
	this.addPasswordErrorMsg();
    } else if (this.code == 1) {
	this.addUserNameErrorStyle();
	this.addUserNameErrorMsg();
    } else if (this.code == 2) {
	this.addPasswordErrorStyle();
	this.addPasswordErrorMsg();
    } else if (this.code == 3) {
	this.addUserNameErrorStyle();
	this.addPasswordErrorStyle();
	this.addPasswordErrorMsg();
    }
    return;
}
 
LoginValidator.prototype.addUserNameErrorStyle = function() {
    this.addErrorStyle('username');
}
 
LoginValidator.prototype.addPasswordErrorStyle = function() {
    this.addErrorStyle('password');
}
 
LoginValidator.prototype.addUserNameErrorMsg = function() {
    this.addErrorMsg('username');
}
 
LoginValidator.prototype.addPasswordErrorMsg = function() {
    this.addErrorMsg('password');
}
 
LoginValidator.prototype.addErrorMsg = function(field) {
    // 清除掉之前的提示内容
    $("input[name='" + field + "']").parent().children('small').remove();
    // 更新错误提示信息
    $("input[name='" + field + "']").parent().append(
	    '' + this.message
		    + '');
}
 
LoginValidator.prototype.addErrorStyle = function(field) {
    $("input[name='" + field + "']").parent().addClass("has-error");
}
 
// 使用本地缓存记住用户名密码
function rememberMe(rm_flag) {
    // remember me
    if (rm_flag) {
	localStorage.username = $("input[name='username']").val();
	localStorage.password = $("input[name='password']").val();
	localStorage.rememberMe = 1;
    }
    // delete remember msg
    else {
	localStorage.userName = null;
	localStorage.password = null;
	localStorage.rememberMe = 0;
    }
}
// 记住回填
function fillbackLoginForm() {
    if (localStorage.rememberMe && localStorage.rememberMe == "1") {
	$("input[name='username']").val(localStorage.username);
	$("input[name='password']").val(localStorage.password);
	$("input[name='rememberMe']").iCheck('check');
	$("input[name='rememberMe']").iCheck('update');
    }
}

 按钮中click事件触发的就是上面的login()函数。刚才有提到的表单字段验证功能,我这里用到了一个非常流行、非常好用的验证插件:bootstrap-validator.js,在使用前,需要对它初始化验证配置,即我们要验证哪些字段,验证规则是什么,这里直接上代码。

// 初始化验证配置
	    $("#login-form").bootstrapValidator({
		message : '请输入用户名/密码',
		fields : {
		    username : {
			validators : {
			    notEmpty : {
				message : '登录邮箱、手机号、用户名不能为空'
			    }
			}
		    },
		    password : {
			validators : {
			    notEmpty : {
				message : '密码不能为空'
			    }
			}
		    }
		}
	    }); 

SSM+Shiro+Bootstrap+Jquery项目实践之用户登陆_第1张图片

这段代码是直接写在login.jsp这个页面body中的,页面加载完成后就会初始化它,后面考虑把它单独拿出来。实际

中的验证肯定不止上面的字段值是否为空,这里我们慢慢来,登陆错误的提示包含很多方面的知识,前端错误提示的

 动态变换我们还是用bootstrap-validator.js来做,代码也在上面login.js中,我封装好了,主要看后台。

 依赖jar:这里贴出pom.xml,顺便说一句,在以后的章节中我可能会详细的讲述如何构建一个标准的maven聚合工程

(标准的大型电商项目开发结构),我这里就不多说了。


	
1.4.0
	

		

			
				org.apache.shiro
				shiro-core
				${shiro.version}
			
			
				org.apache.shiro
				shiro-web
				${shiro.version}
			
			
				org.apache.shiro
				shiro-spring
				${shiro.version}
			
			
				org.apache.shiro
				shiro-ehcache
				${shiro.version}
			
		
	

web模块pom.xml引入以下下依赖:


		
				org.apache.shiro
				shiro-core
			
			
				org.apache.shiro
				shiro-web
			
			
				org.apache.shiro
				shiro-spring
			
			
				org.apache.shiro
				shiro-ehcache
			

apache shiro是现在很流行、很棒的权限框架,包括完整的登陆验证、权限管理,与spring完美整合,简单易懂的api,

很适合快速开发。首先在web.xml添加它的拦截器,拦截所有(“/*”)的请求。


	
		shiroFilter
		org.springframework.web.filter.DelegatingFilterProxy
		
			targetFilterLifecycle
			true
		
		
			targetBeanName
			shiroFilter
		
	
	
		shiroFilter
		/*
	

 然后新建一个applicationContext-shiro.xml文件,来将shiro整合到spring中,最佩服的就是spring的这点,将Ioc做到了极致。配置文件内容如下,简单易懂:


	
		
		
		
		
		
		
		
		
		
		
			
				/css/** = anon
				/js/** = anon
				/login = anon
				/** = authc
			
		
	
 
	
	
	
	
	
	
	
		
	
	
	

 未来我可能会使用redis来管理session过期问题,目前暂时没有关心session过期问题,默认的是设置5分钟。好了,接下来ShiroDBRealm.java,这个类继承了AuthorizingRealm.java,主要重写它的用户验证和权限验证(权限验证后面会写,目前不写)。

public class ShiroDBRealm extends AuthorizingRealm {
 
	private static final String SESSION_USER_KEY = "taotao";
	
	@Autowired
	private UserService userService;
	
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();  
		return info;
	}
 
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) throws AuthenticationException {
		// 把token转换成User对象  
        TbUser userLogin = tokenToUser((UsernamePasswordToken) authcToken);  
        // 验证用户是否可以登录  
        TbUser ui = userService.login(userLogin); 
        if(ui == null){  
        	throw new UnknownAccountException(); // 异常处理,找不到数据
        }
        // 设置session  
        Session session = SecurityUtils.getSubject().getSession();  
        session.setAttribute(SESSION_USER_KEY, ui);
        //设置session有效时间5分钟
        session.setTimeout(300000);
        //当前 Realm 的 name  
        String realmName = this.getName();  
        //登陆的主要信息: 可以是一个实体类的对象, 但该实体类的对象一定是根据 token 的 username 查询得到的.  
        Object principal = authcToken.getPrincipal();  
        return new SimpleAuthenticationInfo(principal, ui.getPassword(), realmName); 
	}
 
	private TbUser tokenToUser(UsernamePasswordToken authcToken) {  
		TbUser user = new TbUser();  
        user.setUsername(authcToken.getUsername());  
        user.setPassword(String.valueOf(authcToken.getPassword()));  
        return user;  
    } 
}

 注入的service:
包含service接口和service实现类

 UserService.java

public interface UserService {
	public TbUser login(TbUser user);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {
 
	@Autowired
	private TbUserMapper tbUserMapper;
 
	@Override
	public TbUser login(TbUser user) {
		TbUserExample tbUserExample = new TbUserExample();
		Criteria createCriteria = tbUserExample.createCriteria();
		if (user != null) {
			String username = user.getUsername();
			createCriteria.andUsernameEqualTo(username);
			List selectByExample = tbUserMapper
					.selectByExample(tbUserExample);
			if (selectByExample != null && !selectByExample.isEmpty()) {
				return selectByExample.get(0);
			}
		}
		return null;
	}
 
}

注入的Dao:代码没有太大的价值,因为是使用mybatis逆向工程生成,所以就这么说说吧。

 Control层:LoginController.java

@Controller
public class LoginController {
 
	@Autowired
	@RequestMapping("/openlogin")
	public String openloginpage() {
		// 已经登录过,直接进入主页
		try {
			// 这里有bug,整个服务器重启后,会默认进这个handler,
			// 导致securityManager为null,所以加try避免
			if (isRelogin()) {
				// 如果已经登陆,无需重新登录,进入首页
				// 使用redirect:/是因为浏览器地址要改变
				return "redirect:/"; 
			}
		} catch (Exception e) {
		}
		return "login";
	}
 
	@RequestMapping(value = "/login", method = RequestMethod.POST)
	@ResponseBody
	public CommonResult login(@RequestBody TbUser user) {
		return loginUser(user);
	}
 
	private CommonResult loginUser(TbUser user) {
		return shiroLogin(user); // 调用shiro的登陆验证
	}
 
	private CommonResult shiroLogin(TbUser user) {
		// 组装token,包括客户公司名称、简称、客户编号、用户名称;密码
		UsernamePasswordToken token = new UsernamePasswordToken(
				user.getUsername(), user.getPassword().toCharArray(), null);
		// 记住这个登陆的信息
		token.setRememberMe(true);
		String msg;
		// shiro登陆验证
		try {
			SecurityUtils.getSubject().login(token);
			// 0 未授权 1 账号问题 2 密码错误 3 账号密码错误
		} catch (IncorrectCredentialsException e) {
			msg = "登录密码错误. Password for account " + token.getPrincipal()
					+ " was incorrect";
			return ResultGenerator.genLoginResult0(msg, 2, null);
		} catch (ExcessiveAttemptsException e) {
			msg = "登录失败次数过多";
			return ResultGenerator.genLoginResult0(msg, 3, null);
		} catch (LockedAccountException e) {
			msg = "帐号已被锁定. The account for username " + token.getPrincipal()
					+ " was locked.";
			return ResultGenerator.genLoginResult0(msg, 1, null);
		} catch (DisabledAccountException e) {
			msg = "帐号已被禁用. The account for username " + token.getPrincipal()
					+ " was disabled.";
			return ResultGenerator.genLoginResult0(msg, 1, null);
		} catch (ExpiredCredentialsException e) {
			msg = "帐号已过期. the account for username " + token.getPrincipal()
					+ "  was expired.";
			return ResultGenerator.genLoginResult0(msg, 1, null);
		} catch (UnknownAccountException e) {
			msg = "帐号不存在. There is no user with username of "
					+ token.getPrincipal();
			return ResultGenerator.genLoginResult0(msg, 1, null);
		} catch (UnauthorizedException e) {
			msg = "您没有得到相应的授权!" + e.getMessage();
			return ResultGenerator.genLoginResult0(msg, 1, null);
		} catch (AuthenticationException ex) {
			return ResultGenerator.genFailResult(ex.getMessage()); // 自定义报错信息
		} catch (Exception ex) {
			return ResultGenerator.genFailResult("内部错误,请重试");
		}
		Map data = new HashMap();
		data.put("username", user.getUsername());
		return ResultGenerator.genSuccessResult(data);
	}
 
	private boolean isRelogin() {
		Subject us = SecurityUtils.getSubject();
		if (us != null && us.isAuthenticated()) {
			return true; // 参数未改变,无需重新登录,默认为已经登录成功
		}
		return false; // 需要重新登陆
	}
}

 至此,前端提交表单,请求,响应已经全部完成。看看效果:

SSM+Shiro+Bootstrap+Jquery项目实践之用户登陆_第2张图片SSM+Shiro+Bootstrap+Jquery项目实践之用户登陆_第3张图片

你可能感兴趣的:(Java)