傳統登錄的方式: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用戶時間。