传统登录的方式:javascript
1.登录时,进行验证,验证完毕后将用户信息存储到sessionhtml
/** * 用户登陆 * @param user * @return * HttpSession由于在Survey_2_Component中使用, * 因此在Survey_2_Component工程中加入JSP-API的依赖后才能使用 */ @RequestMapping(value="/guest/user/login",method=RequestMethod.POST) public String userLogin(User user,HttpSession httpSession){ User queryUser = userService.login(user); //将带有id的用户数据存进session域 httpSession.setAttribute(GlobalNames.LOGIN_USER, queryUser); return "redirect:/index.jsp"; }
2.其它系统的登录验证java
用拦截器判断用户是否登录node
public class AuthorityCheckInterceptor implements HandlerInterceptor{ @Autowired private ResService resService; public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1.放行静态资源 if(handler instanceof DefaultServletHttpRequestHandler){ return true; } //6.若是不是公共资源,检查登陆状态——区分guest/manager HttpSession session = request.getSession(); if(path.startsWith("/guest")){ User user = (User)session.getAttribute(GlobalNames.LOGIN_USER); if(user==null){ throw new AdminAccessForbiddenException(GlobalMessage.ADMIN_ACCESS_FORBIDDEN); } ..... } }
此方式在只有一个web工程时是没有问题。web
jsp的四个做用域page、request、session、application的做用范围ajax
都是同一个web工程中:redis
page:用户请求的当前页面;spring
Request:能够经过转发来进行传递。用户请求访问的当前组件,以及和当前web组件共享同一用户请求的web组件。数据库
如:被请求的jsp页面和该页面用<include>指令包含的页面以及<forward>标记包含的其它jsp页面; json
Session:同一个http会话,关闭浏览器就能够结束了。同一个http会话中的web组件共享它;
Application:整个web应用的所用web组件共享它。除非关闭tomcat,不然没法关闭。
单点登陆
解决多web工程间的相互登录问题。 即session共享问题
1、什么是单点登陆SSO(Single Sign-On)
SSO是一种统一认证和受权机制,指访问同一服务器不一样应用中的受保护资源的同一用户,只须要登陆一次,即经过一个应用中的安全验证后,再访问其余应用中的受保护资源时,再也不须要从新登陆验证。
2、单点登陆解决了什么问题
解决了用户只须要登陆一次就能够访问全部相互信任的应用系统,而不用重复登陆。
集群环境下会出现要求用户屡次登陆的状况。
解决方案:
可使用Session服务器,保存Session信息,使每一个节点是无状态。须要模拟Session。
单点登陆系统是使用redis模拟Session,实现Session的统一管理。
具体实施
<form id="regForm_mod" name="regForm_mod" method="post" > <li class="regMb30"> <label><font>*</font> 帐户名:</label> <span class="regM defaultBorder"> <input id="regName" name="username" class="regInput" type="text" onkeyup="mail_div(event);" onfocus="showtip('regName','userMamErr',8);" onblur="ckmem();" autocomplete="off" maxlength="80"/> <em></em> </span> <span class="regInput" id="userMamErr"></span> </li> <div node-type="layer" class="accountSearch" style="display:none;" id="person_mail"></div> <li> <label><font>*</font> 登陆密码:</label> <span class="regM defaultBorder"> <input id="pwd" name="password" class="regInput" autocomplete="off" type="password" onfocus="showPwdtip('password','passwordErr',2);" onkeyup="ckpwd(0,1);" onblur="ckpwd(0,0);"/> <em ></em> </span> <span class="regInput" id="passwordErr"></span> </li> <li class="safetyLayer regPl191" id="pwdStrong"> <font style="font-size:12px;">安全程度:</font><em class="default">弱</em><em class="default">中</em><em class="default">强</em> </li> <li class="regMb30"> <label><font>*</font> 确认密码:</label> <span class="regM defaultBorder"> <input id="pwdRepeat" name="password2" autocomplete="off" class="regInput" type="password" onfocus="showtip('password2','password2Err',3);" onblur="ckpwd2();"/> <em></em> </span> <span class="regInput" id="password2Err"></span> </li> <li class="regMb30"> <label><font>*</font> 验证手机:</label> <span class="regM defaultBorder"> <input id="phone" name="phone" autocomplete="off" class="regInput" type="text" maxlength="11" onfocus="showtip('phone','phoneErr',1);" onblur="$('#phoneErr').html('')"/> <em></em> </span> <span class="regInput" id="phoneErr"></span> </li> <li class="regPl88"> <span class="regM" style="margin-left:29px"> <input id="AgreeId" name="AgreeId" type="checkbox" checked="" onclick="ckAgree();"> <a href="https://passport.e3mall.cn/xy.html" target="_blank" class="checkTitle">我已阅读并赞成<font style="font-size:12px;">《宜立方商城用户注册协议》</font></a> </span> <span id="AgreeIdErr" ></span> </li> <li class="register regPl88 regMt10" id="sub_per" style="margin-left:29px"> <input type="hidden" id="tjuid" name="tjuid" value=""> <a href="javascript:void(0);" class="registerNow" id="reg_per_data" onclick="REGISTER.reg()">当即注册</a> </li> <br /><br /> </form>
1.触发注册
onclick="REGISTER.reg()"
2,script采用json变量存储代码
<script type="text/javascript"> var REGISTER={ param:{ //单点登陆系统的url surl:"" }, inputcheck:function(){ var flag = true; //不能为空检查 if ($("#regName").val() == "") { showError("regName","userMamErr",defaultArr[8]); flag = false; } if ($("#pwd").val() == "") { showError("pwd","passwordErr",pwdArr[0]); flag = false; } if ($("#phone").val() == "") { showError("phone","phoneErr",memArr[0]); flag = false; } //密码检查 if ($("#pwd").val() != $("#pwdRepeat").val()) { showError("pwdRepeat","password2Err",pwd2Arr[1]); flag = false; } return flag; }, beforeSubmit:function() { //检查用户是否已经被占用 //escape() 函数可对字符串进行编码,这样就能够在全部的计算机上读取该字符串。 //采用Math.random()为了防止浏览器默认为相同而缓存,包证每次url都不同 $.ajax({ url : REGISTER.param.surl + "/user/check/"+escape($("#regName").val())+"/1?r=" + Math.random(), success : function(data) { if (data.data) { //检查手机号是否存在 $.ajax({ url : REGISTER.param.surl + "/user/check/"+$("#phone").val()+"/2?r=" + Math.random(), success : function(data) { if (data.data) { REGISTER.doSubmit(); } else { showError("phone","phoneErr",defaultArr[9]); } } }); } else { showError("regName","userMamErr",defaultArr[10]); } } }); }, doSubmit:function() { $.post("/user/register",$("#regForm_mod").serialize(), function(data){ if(data.status == 200){ jAlert('用户注册成功,请登陆!',"提示", function(){ REGISTER.login(); }); } else { jAlert("注册失败!","提示"); } }); }, login:function() { location.href = "/page/login"; return false; }, reg:function() { if (this.inputcheck()) { this.beforeSubmit(); } } }; </script>
3.
REGISTER.reg();
3.1 先验证输入完整性;
this.inputcheck()
inputcheck:function(){ var flag = true; //不能为空检查 if ($("#regName").val() == "") { showError("regName","userMamErr",defaultArr[8]); flag = false; } if ($("#pwd").val() == "") { showError("pwd","passwordErr",pwdArr[0]); flag = false; } if ($("#phone").val() == "") { showError("phone","phoneErr",memArr[0]); flag = false; } //密码检查 if ($("#pwd").val() != $("#pwdRepeat").val()) { showError("pwdRepeat","password2Err",pwd2Arr[1]); flag = false; } return flag; }
3.2检查用户是否已经被占用(ajax)
beforeSubmit:function() { //检查用户是否已经被占用 //escape() 函数可对字符串进行编码,这样就能够在全部的计算机上读取该字符串。 //采用Math.random()为了防止浏览器默认为相同而缓存,包证每次url都不同 $.ajax({ url : REGISTER.param.surl + "/user/check/"+escape($("#regName").val())+"/1?r=" + Math.random(), success : function(data) { if (data.data) { //检查手机号是否存在 $.ajax({ url : REGISTER.param.surl + "/user/check/"+$("#phone").val()+"/2?r=" + Math.random(), success : function(data) { if (data.data) { REGISTER.doSubmit(); } else { showError("phone","phoneErr",defaultArr[9]); } } }); } else { showError("regName","userMamErr",defaultArr[10]); } } });
controller
@RequestMapping("/user/check/{param}/{type}") @ResponseBody public E3Result checkData(@PathVariable String param, @PathVariable Integer type) { E3Result e3Result = userService.checkData(param, type); return e3Result; }
public E3Result checkData(String param, Integer type) { // 一、从tb_user表中查询数据 UserExample example = new UserExample(); Criteria criteria = example.createCriteria(); // 二、查询条件根据参数动态生成。 //一、二、3分别表明username、phone、email if (type == 1) { criteria.andUsernameEqualTo(param); } else if (type == 2) { criteria.andPhoneEqualTo(param); } else if (type == 3) { criteria.andEmailEqualTo(param); } else { return E3Result.build(400, "非法的参数"); } //执行查询 List<User> list = userMapper.selectByExample(example); // 三、判断查询结果,若是查询到数据返回false。 if (list == null || list.size() == 0) { // 四、若是没有返回true。 return E3Result.ok(true); } // 五、使用e3Result包装,并返回。 return E3Result.ok(false); }
3.3 提交验证
REGISTER.doSubmit();
doSubmit:function() {
$.post("/user/register",$("#regForm_mod").serialize(), function(data){
if(data.status == 200){
jAlert('用户注册成功,请登陆!',"提示", function(){
REGISTER.login();
});
} else {
jAlert("注册失败!","提示");
}
});
}
/** * 注册 * @return */ @RequestMapping(value="/user/register",method=RequestMethod.POST) @ResponseBody public E3Result register(User user){ E3Result result = userService.createUser(user); return result; }
public E3Result createUser(User user) { // 一、使用TbUser接收提交的请求。 if (StringUtils.isBlank(user.getUsername())) { return E3Result.build(400, "用户名不能为空"); } if (StringUtils.isBlank(user.getPassword())) { return E3Result.build(400, "密码不能为空"); } //校验数据是否可用 E3Result result = checkData(user.getUsername(), 1); if (!(boolean) result.getData()) { return E3Result.build(400, "此用户名已经被使用"); } //校验电话是否能够 if (StringUtils.isNotBlank(user.getPhone())) { result = checkData(user.getPhone(), 2); if (!(boolean) result.getData()) { return E3Result.build(400, "此手机号已经被使用"); } } //校验email是否可用 if (StringUtils.isNotBlank(user.getEmail())) { result = checkData(user.getEmail(), 3); if (!(boolean) result.getData()) { return E3Result.build(400, "此邮件地址已经被使用"); } } // 二、补全TbUser其余属性。 user.setCreated(new Date()); user.setUpdated(new Date()); // 三、密码要进行MD5加密。 String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes()); user.setPassword(md5Pass); // 四、把用户信息插入到数据库中。 userMapper.insert(user); // 五、返回e3Result return E3Result.ok(); }
密码进行md5加密:import org.springframework.util.DigestUtils;
String md5Pass = DigestUtils.md5DigestAsHex(user.getPassword().getBytes());
登录功能
1.进行验证
获取用户名和密码
@RequestMapping(value="/user/login",method=RequestMethod.POST) @ResponseBody public E3Result login(String username, String password, HttpServletRequest request, HttpServletResponse response){ // 一、接收两个参数。 // 二、调用Service进行登陆。 E3Result result = userService.login(username, password); //判断是否登陆成功 if(result.getStatus()==200){ // 三、从返回结果中取token,写入cookie。Cookie要跨域。 String token = result.getData().toString(); CookieUtils.setCookie(request, response,TOKEN_KEY, token); } // 四、响应数据。Json数据。e3Result,其中包含Token。 return result; }
2. E3Result result = userService.login(username, password);
验证成功后,生成模拟的session存储于redis
public E3Result login(String username, String password) { // 一、判断用户名密码是否正确。 UserExample example = new UserExample(); Criteria criteria = example.createCriteria(); criteria.andUsernameEqualTo(username); //查询用户信息 List<User> list = userMapper.selectByExample(example); if (list == null || list.size() == 0) { return E3Result.build(400, "用户名或密码错误"); } User user = list.get(0); //校验密码 if (!user.getPassword().equals(DigestUtils.md5DigestAsHex(password.getBytes()))) { return E3Result.build(400, "用户名或密码错误"); } // 二、登陆成功后生成token。Token至关于原来的jsessionid,字符串,可使用uuid。 String token = UUID.randomUUID().toString(); // 三、把用户信息保存到redis。Key就是token,value就是TbUser对象转换成json。 // 四、使用String类型保存Session信息。可使用“前缀:token”为key user.setPassword(null); jedisClient.set("SESSION:" + token, JsonUtils.objectToJson(user)); // 五、设置key的过时时间。模拟Session的过时时间。通常半个小时。 jedisClient.expire( "SESSION:" + token, SESSION_EXPIRE); // 六、返回e3Result包装token。 return E3Result.ok(token); }
user.setPassword(null);//为了密码的安全性
3.将模拟session存储于cookie
//判断是否登陆成功 if(result.getStatus()==200){ // 三、从返回结果中取token,写入cookie。Cookie要跨域。 String token = result.getData().toString(); CookieUtils.setCookie(request, response,TOKEN_KEY, token); }
redis上的key值为了惟一性,key="SESSION:" +UUID.randomUUID().toString()
因此不用用户id
为何要存储到cookie?
cookie存储到客户端,至关于开启免登陆。若是存放到session是存放到服务器,每次都去取占用服务器资源。并且这里已经用redis模仿了session。
cookie 和session 的区别:
一、cookie数据存放在客户的浏览器上,session数据放在服务器上。
二、cookie不是很安全,别人能够分析存放在本地的COOKIE并进行COOKIE欺骗
考虑到安全应当使用session。
三、session会在必定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用COOKIE。
四、单个cookie保存的数据不能超过4K,不少浏览器都限制一个站点最多保存20个cookie。
五、因此我的建议:
将登录信息等重要信息存放为SESSION
其余信息若是须要保留,能够放在COOKIE中
CookieUtils.setCookie(request, response,TOKEN_KEY, token);//不设置不设置生效时间默认浏览器关闭即失效,也不编码
/**
* 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue) {
setCookie(request, response, cookieName, cookieValue, -1);
}
//这里isEncode=false
/**
* 设置Cookie的值 在指定时间内生效, 编码参数
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, boolean isEncode) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
}
/** * 设置Cookie的值,并使其在指定时间内生效 * * @param cookieMaxage cookie生效的最大秒数 */ private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) { try { if (cookieValue == null) { cookieValue = ""; } else if (isEncode) { cookieValue = URLEncoder.encode(cookieValue, "utf-8"); } Cookie cookie = new Cookie(cookieName, cookieValue);//设置cookie值 if (cookieMaxage > 0) cookie.setMaxAge(cookieMaxage);//设置cookie生存时间 if (null != request) {// 设置域名的cookie String domainName = getDomainName(request); System.out.println(domainName); if (!"localhost".equals(domainName)) { cookie.setDomain(domainName); } } cookie.setPath("/"); response.addCookie(cookie); } catch (Exception e) { e.printStackTrace(); } }
cookie要作到域名隔离。这个方法是为了提取
获取域名:(为了指定cookie的做用范围)
/** * 获得cookie的域名 */ private static final String getDomainName(HttpServletRequest request) { String domainName = null; String serverName = request.getRequestURL().toString(); if (serverName == null || serverName.equals("")) { domainName = ""; } else { serverName = serverName.toLowerCase(); serverName = serverName.substring(7); final int end = serverName.indexOf("/"); serverName = serverName.substring(0, end); final String[] domains = serverName.split("\\."); int len = domains.length; if (len > 3) { // www.xxx.com.cn domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1]; } else if (len <= 3 && len > 1) { // xxx.com or xxx.cn domainName = "." + domains[len - 2] + "." + domains[len - 1]; } else { domainName = serverName; } } if (domainName != null && domainName.indexOf(":") > 0) { String[] ary = domainName.split("\\:"); domainName = ary[0]; } return domainName; }
这里默认采用localhost做为域名设置,在不一样工程间传递,cookie任然有效
if (null != request) {// 设置域名的cookie String domainName = getDomainName(request); System.out.println(domainName); if (!"localhost".equals(domainName)) { cookie.setDomain(domainName);// }
cookie.setPath("/"); response.addCookie(cookie);//cookie the Cookie to return to the client返回的客户端的路径
一、当用户登陆成功后,在cookie中有token信息。
二、从cookie中取token根据token查询用户信息。
三、把用户名展现到首页。
根据token获取用户信息
cookie由于是在客户端,各个工程间是公用的,从cookie中获得token后去查询redis的用户数据。有两种方案:
1)在Controller中取cookie中的token数据,调用sso服务查询用户信息。(每一个controller都要修改麻烦)
2)当页面加载完成后使用js取token的数据,使用ajax请求查询用户信息。
因此采用方案2,但面临js跨域读取数据的问题。
问题:服务接口在sso系统中。Sso.e3.com(localhost:8088),在首页显示用户名称,首页的域名是www.e3.com(localhost:8082),使用ajax请求跨域了。
Js不能够跨域请求数据。
Js不能够跨域请求数据。
什么是跨域:
一、域名不一样
二、域名相同端口不一样。
解决js的跨域问题可使用jsonp。
Jsonp不是新技术,跨域的解决方案。使用js的特性绕过跨域请求。Js能够跨域加载js文件。
使用jQuery。
一、接收callback参数,取回调的js的方法名。
二、业务逻辑处理。
三、响应结果,拼接一个js语句。
跨域传输,其它不变,增长dataType:"jsonp" 就能够实现跨域传送数据了
下例中的是到另外的web工程获取数据(端口不一样)。
$.ajax({ url : "http://localhost:8089/user/token/" + _ticket, dataType : "jsonp", type : "GET", success : function(data){ if(data.status == 200){ var username = data.data.username; var html = username + ",欢迎来到宜立方购物网!<a href=\"http://www.e3mall.cn/user/logout.html\" class=\"link-logout\">[退出]</a>"; $("#loginbar").html(html); } } })
在跨域时,请求数据,默认会带参数callback
@RequestMapping("/user/token/{token}") @ResponseBody public Object getUserByToken(@PathVariable("token") String token,String callback){ E3Result result = userService.getUserByToken(token); //响应结果以前,判断是否为jsonp请求 if(StringUtils.isNotBlank(callback)){ //把结果封装成一个js语句响应 MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result); mappingJacksonValue.setJsonpFunction(callback); return mappingJacksonValue; } return result; }
在首页显示用户名查询cookie经过jQuery来实现:
<script type="text/javascript" src="/js/e3mall.js"></script>
json变量的js
var E3MALL = { checkLogin : function(){ var _ticket = $.cookie("token");//读取cookie:
if(!_ticket){//cokie值为空就返回空 return ; }
//不为空,就到redis获取 $.ajax({ url : "http://localhost:8089/user/token/" + _ticket, dataType : "jsonp", type : "GET", success : function(data){ if(data.status == 200){ var username = data.data.username; var html = username + ",欢迎来到宜立方购物网!<a href=\"http://www.e3mall.cn/user/logout.html\" class=\"link-logout\">[退出]</a>"; $("#loginbar").html(html); } } }); } } $(function(){ // 查看是否已经登陆,若是已经登陆查询登陆信息 E3MALL.checkLogin(); });
加载页面就检查是否登录
$(function(){ // 查看是否已经登陆,若是已经登陆查询登陆信息 E3MALL.checkLogin(); });
登录就经过redis获取数据从新设置redis模拟session数据存储时间后存储到cookie
@Override public E3Result getUserByToken(String token) { //从redis缓存中获取后存储到cookie中 String json = jedisClient.get("SESSION:"+token); if(StringUtils.isBlank(json)){ // 三、若是查询不到数据。返回用户已通过期。 return E3Result.build(400, "用户登陆已通过期,请从新登录"); } // 四、若是查询到数据,说明用户已经登陆 // 五、须要重置key的过时时间 jedisClient.expire("SESSION:"+token, SESSION_EXPIRE); //把json字符串转化为pojo User user = JsonUtils.jsonToPojo(json, User.class); return E3Result.ok(user); }
返回数据包装为js语句返回:(建立callback函数)
//把结果封装成一个js语句响应 MappingJacksonValue mappingJacksonValue = new MappingJacksonValue(result); mappingJacksonValue.setJsonpFunction(callback); return mappingJacksonValue;
成功后对数据进行处理:(将首页的请登陆html进行替换)
$.ajax({ url : "http://localhost:8089/user/token/" + _ticket, dataType : "jsonp", type : "GET", success : function(data){ if(data.status == 200){ var username = data.data.username; var html = username + ",欢迎来到宜立方购物网!<a href=\"http://www.e3mall.cn/user/logout.html\" class=\"link-logout\">[退出]</a>"; $("#loginbar").html(html); } } });
var html = username + ",欢迎来到宜立方购物网!<a href=\"http://www.e3mall.cn/user/logout.html\" class=\"link-logout\">[退出]</a>";
$("#loginbar").html(html);
替换掉如下的html
<span id="loginbar" style="margin-right: 15px;"> <a href="http://localhost:8089/page/login">请登陆</a> </span>
就这样实现了首页显示用户信息,同时也实现了人为刷新后,从新设置redis用户时间。