黄a在线观看-黄a在线-黄a大片-黄色片在线看-黄色毛片免费-黄色大片网站

您的位置:首頁技術(shù)文章
文章詳情頁

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

瀏覽:2日期:2022-08-11 16:26:10
目錄前言一、何為鑒權(quán)服務(wù)二、利用servlet+jdbc實(shí)現(xiàn)簡單的用戶登錄程序1.明確思路2.手把手教你實(shí)現(xiàn)一個(gè)簡單的web登錄程序三、回顧1.密碼未加密裸奔2.登錄信息未存儲3.對于其他資源并未進(jìn)行權(quán)限管理四、優(yōu)化設(shè)計(jì)1.密碼加密存儲2.存儲登錄信息3.對資源進(jìn)行管理五、關(guān)于鑒權(quán)問題1.Cookie/Session機(jī)制2.用Cookie/Session解決鑒權(quán)問題?3.使用token機(jī)制解決鑒權(quán)問題六、用SpringBoot+SSM實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)(注冊,登錄,權(quán)限控制)1.注冊服務(wù)2.登錄服務(wù)3.權(quán)限控制(攔截器)七、效果展示1.注冊2.登錄3.訪問其他資源寫在最后前言

時(shí)遇JavaEE作業(yè),題目要求寫個(gè)簡單web登錄程序,按照老師的意思是用servlet、jsp和jdbc完成。本著要么不做,要做就要做好的原則,我開始著手完成此次作業(yè)(其實(shí)也是寫實(shí)訓(xùn)作業(yè)的用戶鑒權(quán)部分),而之前寫項(xiàng)目的時(shí)候也有相關(guān)經(jīng)驗(yàn),這次正好能派上用場。

一、何為鑒權(quán)服務(wù)

引用百度百科的話說

鑒權(quán)(authentication)是指驗(yàn)證用戶是否擁有訪問系統(tǒng)的權(quán)利。

鑒權(quán)包括兩個(gè)方面:

用戶鑒權(quán),網(wǎng)絡(luò)對用戶進(jìn)行鑒權(quán),防止非法用戶占用網(wǎng)絡(luò)資源。網(wǎng)絡(luò)鑒權(quán),用戶對網(wǎng)絡(luò)進(jìn)行鑒權(quán),防止用戶接入了非法的網(wǎng)絡(luò),被騙取關(guān)鍵信息。

而我們這里的鑒權(quán)主要指用戶鑒權(quán),即如何確認(rèn)“你是你”。最簡單的體現(xiàn)便是平常用的用戶登錄登出。

現(xiàn)今大部分系統(tǒng)都會有自己的鑒權(quán)服務(wù),它是用戶與系統(tǒng)交互的第一步,系統(tǒng)需要一系列步驟明白你是誰,你可以做哪些事,明白了這些之后它才能更好的服務(wù)于你。

二、利用servlet+jdbc實(shí)現(xiàn)簡單的用戶登錄程序1.明確思路

首先,我們要仔細(xì)思考一下我們到底需要什么?

先讓我們回想一下一般的登錄是如何做的呢?

對于網(wǎng)頁,首先會出現(xiàn)一個(gè)登錄頁面,然后呢,輸入賬號密碼,點(diǎn)擊登錄,就會彈出成功/失敗的頁面。

那如何去判斷成功/失敗呢?

思考一下,最簡單的方法便是拿到前端傳來的數(shù)據(jù)之后便將其拿到數(shù)據(jù)中去查,看看密碼是不是一樣,然后給前端回復(fù)說——我找到了,他就是XXX或者我找不到他的記錄,讓他重新輸入賬號密碼。

然后前端對此回復(fù)做出相應(yīng)的操作,比如登錄成功便跳轉(zhuǎn)到首頁,失敗讓用戶重新輸入。

2.手把手教你實(shí)現(xiàn)一個(gè)簡單的web登錄程序

出于某些原因,我這里手把手教你如何實(shí)現(xiàn)一個(gè)簡單的web登錄程序。

①創(chuàng)建web項(xiàng)目

打開idea,新建一個(gè)web項(xiàng)目

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

這里為了方便jar包的管理,選擇maven結(jié)構(gòu)的項(xiàng)目(至于什么是maven結(jié)構(gòu),不懂的可以百度,了解概念即可),然后選擇從原型創(chuàng)建,選擇webapp(這里只是方便,你也可以選擇空項(xiàng)目,不過會費(fèi)點(diǎn)時(shí)間)。

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

點(diǎn)擊下一步,輸入項(xiàng)目名稱

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

這里選擇相應(yīng)的maven,idea里有自帶的maven和jar包倉庫,不過我是自己去官網(wǎng)下了一個(gè)(不下也完全可以)。

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

選擇完成,這樣一個(gè)最簡單的項(xiàng)目結(jié)構(gòu)就出來了。

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

接下來需要配置一下pom.xml,因?yàn)橐玫絡(luò)dbc和tomcat的jar包(畢竟都是調(diào)用人家的接口(笑哭))

<dependencies> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-core</artifactId> <version>9.0.37</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> </dependencies>

(加在project標(biāo)簽里就行),上面配置的意思就是導(dǎo)入兩個(gè)第三方工具包

②編寫簡單的登錄頁面

這里我既想要好看,又想偷懶,所以用了layui框架的模板

<!DOCTYPE html><html><head> <meta charset='UTF-8'> <title>后臺管理-登陸</title> <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'> <meta http-equiv='Access-Control-Allow-Origin' content='*'> <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1'> <meta name='apple-mobile-web-app-status-bar-style' content='black'> <meta name='apple-mobile-web-app-capable' content='yes'> <meta name='format-detection' content='telephone=no'> <link rel='stylesheet' href='http://m.propowerdrill.cn/lib/layui-v2.6.3/css/layui.css' media='all'> <!--[if lt IE 9]> <script src='https://cdn.staticfile.org/html5shiv/r29/html5.min.js'></script> <script src='https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js'></script> <![endif]--> <style>.main-body {top:50%;left:50%;position:absolute;-webkit-transform:translate(-50%,-50%);-moz-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);-o-transform:translate(-50%,-50%);transform:translate(-50%,-50%);overflow:hidden;}.login-main .login-bottom .center .item input {display:inline-block;width:227px;height:22px;padding:0;position:absolute;border:0;outline:0;font-size:14px;letter-spacing:0;}.login-main .login-bottom .center .item .icon-1 {background:url(../images/icon-login.png) no-repeat 1px 0;}.login-main .login-bottom .center .item .icon-2 {background:url(../images/icon-login.png) no-repeat -54px 0;}.login-main .login-bottom .center .item .icon-3 {background:url(../images/icon-login.png) no-repeat -106px 0;}.login-main .login-bottom .center .item .icon-4 {background:url(../images/icon-login.png) no-repeat 0 -43px;position:absolute;right:-10px;cursor:pointer;}.login-main .login-bottom .center .item .icon-5 {background:url(../images/icon-login.png) no-repeat -55px -43px;}.login-main .login-bottom .center .item .icon-6 {background:url(../images/icon-login.png) no-repeat 0 -93px;position:absolute;right:-10px;margin-top:8px;cursor:pointer;}.login-main .login-bottom .tip .icon-nocheck {display:inline-block;width:10px;height:10px;border-radius:2px;border:solid 1px #9abcda;position:relative;top:2px;margin:1px 8px 1px 1px;cursor:pointer;}.login-main .login-bottom .tip .icon-check {margin:0 7px 0 0;width:14px;height:14px;border:none;background:url(../images/icon-login.png) no-repeat -111px -48px;}.login-main .login-bottom .center .item .icon {display:inline-block;width:33px;height:22px;}.login-main .login-bottom .center .item {width:288px;height:35px;border-bottom:1px solid #dae1e6;margin-bottom:35px;}.login-main {width:428px;position:relative;float:left;}.login-main .login-top {height:117px;background-color:#148be4;border-radius:12px 12px 0 0;font-family:SourceHanSansCN-Regular;font-size:30px;font-weight:400;font-stretch:normal;letter-spacing:0;color:#fff;line-height:117px;text-align:center;overflow:hidden;-webkit-transform:rotate(0);-moz-transform:rotate(0);-ms-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0);}.login-main .login-top .bg1 {display:inline-block;width:74px;height:74px;background:#fff;opacity:.1;border-radius:0 74px 0 0;position:absolute;left:0;top:43px;}.login-main .login-top .bg2 {display:inline-block;width:94px;height:94px;background:#fff;opacity:.1;border-radius:50%;position:absolute;right:-16px;top:-16px;}.login-main .login-bottom {width:428px;background:#fff;border-radius:0 0 12px 12px;padding-bottom:53px;}.login-main .login-bottom .center {width:288px;margin:0 auto;padding-top:40px;padding-bottom:15px;position:relative;}.login-main .login-bottom .tip {clear:both;height:16px;line-height:16px;width:288px;margin:0 auto;}body {background:url(../images/loginbg.png) 0% 0% / cover no-repeat;position:static;font-size:12px;}input::-webkit-input-placeholder {color:#a6aebf;}input::-moz-placeholder {/* Mozilla Firefox 19+ */ color:#a6aebf;}input:-moz-placeholder {/* Mozilla Firefox 4 to 18 */ color:#a6aebf;}input:-ms-input-placeholder {/* Internet Explorer 10-11 */ color:#a6aebf;}input:-webkit-autofill {/* 取消Chrome記住密碼的背景顏色 */ -webkit-box-shadow:0 0 0 1000px white inset !important;}html {height:100%;}.login-main .login-bottom .tip {clear:both;height:16px;line-height:16px;width:288px;margin:0 auto;}.login-main .login-bottom .tip .login-tip {font-family:MicrosoftYaHei;font-size:12px;font-weight:400;font-stretch:normal;letter-spacing:0;color:#9abcda;cursor:pointer;}.login-main .login-bottom .tip .forget-password {font-stretch:normal;letter-spacing:0;color:#1391ff;text-decoration:none;position:absolute;right:62px;}.login-main .login-bottom .login-btn {width:288px;height:40px;background-color:#1E9FFF;border-radius:16px;margin:24px auto 0;text-align:center;line-height:40px;color:#fff;font-size:14px;letter-spacing:0;cursor:pointer;border:none;}.login-main .login-bottom .center .item .validateImg {position:absolute;right:1px;cursor:pointer;height:36px;border:1px solid #e6e6e6;}.footer {left:0;bottom:0;color:#fff;width:100%;position:absolute;text-align:center;line-height:30px;padding-bottom:10px;text-shadow:#000 0.1em 0.1em 0.1em;font-size:14px;}.padding-5 {padding:5px !important;}.footer a,.footer span {color:#fff;}@media screen and (max-width:428px) {.login-main {width:360px !important;} .login-main .login-top {width:360px !important;} .login-main .login-bottom {width:360px !important;}} </style></head><body><div class='main-body'> <div class='login-main'><div class='login-top'> <span>LayuiMini后臺登錄</span> <span class='bg1'></span> <span class='bg2'></span></div><form action='/login' method='post'> <div class='center'><div class='item'> <span class='icon icon-2'></span> <input type='text' name='uname' lay-verify='required' placeholder='請輸入登錄賬號' maxlength='24'/></div><div class='item'> <span class='icon icon-3'></span> <input type='password' name='pwd' lay-verify='required' placeholder='請輸入密碼' maxlength='20'> <span class='bind-password icon icon-4'></span></div> </div> <div class='tip'><span class='icon-nocheck'></span><span class='login-tip'>保持登錄</span><a href='javascript:' class='forget-password'>忘記密碼?</a> </div> <div style='text-align:center; width:100%;height:100%;margin:0px;'><button type='submit' lay-submit='' lay-filter='login'>立即登錄</button> </div></form> </div></div><div class='footer'> ©版權(quán)所有 2014-2018 叁貳柒工作室<span class='padding-5'>|</span><a target='_blank' >粵ICP備16006642號-2</a></div><script src='http://m.propowerdrill.cn/lib/layui-v2.6.3/layui.js' charset='utf-8'></script><script> //原本想用json的post發(fā)送,結(jié)果發(fā)現(xiàn)后端數(shù)據(jù)得自己解析,為了降低難度,直接用form表單的post提交,這樣后端直接拿數(shù)據(jù)即可(不然還得解析Json數(shù)據(jù)) // layui.use([’form’,’jquery’], function () { // var $ = layui.jquery, // form = layui.form, // layer = layui.layer; // // // 登錄過期的時(shí)候,跳出ifram框架 // if (top.location != self.location) top.location = self.location; // // $(’.bind-password’).on(’click’, function () { // if ($(this).hasClass(’icon-5’)) { // $(this).removeClass(’icon-5’); // $('input[name=’pwd’]').attr(’type’, ’password’); // } else { // $(this).addClass(’icon-5’); // $('input[name=’pwd’]').attr(’type’, ’text’); // } // }); // // $(’.icon-nocheck’).on(’click’, function () { // if ($(this).hasClass(’icon-check’)) { // $(this).removeClass(’icon-check’); // } else { // $(this).addClass(’icon-check’); // } // }); // // // 進(jìn)行登錄操作 // form.on(’submit(login)’, function (data) { // data = data.field; // if (data.uname == ’’) { // layer.msg(’用戶名不能為空’); // return false; // } // if (data.pwd == ’’) { // layer.msg(’密碼不能為空’); // return false; // } // $.ajax({ // url:’/login’, // method:’post’, // data:data, // dataType:’JSON’, // success:function(res){ // if (res.msg===’登錄成功’){ // layer.msg(’登錄成功’, function () { // window.location = ’../index.html’; // }); // }else { // layer.msg('登錄失敗'); // } // }, // error:function (data) { // } // }) ; // // // return false; // }); // });</script></body></html>

當(dāng)然以上代碼有一部分注釋掉了,原因是如果用JSON格式發(fā)送post請求,后端的servlet(準(zhǔn)確的說是Tomcat的解析)并沒有幫我們解析封裝這部分?jǐn)?shù)據(jù),所以我們無法直接get到,得自己另外解析數(shù)據(jù),當(dāng)然也有一些第三方的工具包可以幫我們做這些事情(如阿里的fastjson等),這里為了使其更加簡單,所以采用表單提交post請求的方式,這樣解析的工作就不用我們做了。

效果是這樣的:

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

如果你沒學(xué)過layui或者對前端不太行,你也可以這樣

<!DOCTYPE html><htmllang='en'><head> <meta charset='UTF-8'> <title>用戶登錄</title></head><body><form action='/login' method='post'> 用戶名:<input type='text' name='uname'> 密碼:<input type='password' name='pwd'> <input type='submit' value='login'></form> </body></html>

一樣的功能,不過看上去的效果就不怎么好了。

③編寫servlet程序

當(dāng)有了前端的頁面,看上去好了很多,但實(shí)質(zhì)校驗(yàn)的程序我們還沒有寫。

想象一下我們就是后端程序,當(dāng)前端的數(shù)據(jù)歷經(jīng)艱險(xiǎn),從錯綜復(fù)雜的網(wǎng)絡(luò)中到達(dá)我們的服務(wù)器,然后經(jīng)過系統(tǒng)分發(fā)到相應(yīng)端口,這時(shí)恰在此端口的tomcat程序接受到了HTTP請求并對其封裝,經(jīng)過一系列騷操作后分發(fā)到了我們手中,而我們要做的就是拿著這個(gè)封裝好的請求進(jìn)行校驗(yàn)操作,然后對返回對象進(jìn)行相應(yīng)修改。

而這也是servlet類所需要做的(如果你想更好的理解servlet,可以看看bravo1988的回答),

package com.dreamchaser.loginTest;import com.dreamchaser.loginTest.mapper.UserMapper;import javax.servlet.ServletException;import javax.servlet.ServletOutputStream;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class LoginServlet extends HttpServlet { static UserMapper userMapper=UserMapper.getUserMapper(); @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doGet(request,response); } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String uname=req.getParameter('uname');String pwd=req.getParameter('pwd');ServletOutputStream outputStream = resp.getOutputStream();String result;if (pwd.equals(userMapper.getPwdByName(uname))){ //響應(yīng) result='登錄成功';}else { result='登錄失敗';}outputStream.write(result.getBytes()); }}

你可能會疑惑這個(gè)UserMapper是什么,別急,后面會介紹。

④封裝jdbc操作,編寫簡單的數(shù)據(jù)庫連接池

在操作數(shù)據(jù)庫之前,最好寫個(gè)簡單的數(shù)據(jù)庫連接池。一個(gè)是簡化我們的操作,一個(gè)是節(jié)省開銷,提高性能(Connection是個(gè)非常耗費(fèi)資源的對象,頻繁的創(chuàng)建和回收將會是一筆巨大的開銷)

package com.dreamchaser.loginTest.utils;import java.sql.Connection;import java.sql.Driver;import java.sql.DriverManager;import java.sql.SQLException;import java.util.HashMap;import java.util.Map;/** * 一個(gè)簡單的數(shù)據(jù)庫連接池 */public class Pool { private static Driver driver; static {try { driver = new com.mysql.cj.jdbc.Driver(); DriverManager.registerDriver(driver);} catch (SQLException throwables) { throwables.printStackTrace();} } private static Map<Connection,Integer> pool=new HashMap<>(); private static String url='jdbc:mysql://localhost:3306/depository?serverTimezone=Asia/Shanghai'; private static String user='root'; private static String password='jinhaolin'; /** * 從連接池中獲取一個(gè)空閑連接,如果沒有則創(chuàng)建一個(gè)新的連接返回 * synchronized確保并發(fā)請求時(shí),數(shù)據(jù)庫連接的正確性 * @return */ public synchronized static Connection getConnection(){for (Map.Entry entry:pool.entrySet()){ if (entry.getValue().equals(1)) {entry.setValue(0);return (Connection) entry.getKey(); }}Connection connection=null;try { connection=DriverManager.getConnection(url,user,password); pool.put(connection,0);} catch (SQLException throwables) { throwables.printStackTrace();}return connection; } /** * 釋放connection連接對象 * @param connection */ public synchronized static void releaseConnection(Connection connection){pool.put(connection,1); }}

當(dāng)然上述實(shí)現(xiàn)非常簡陋,并發(fā)性能也不是很好,高并發(fā)時(shí)可能還會發(fā)生OOM,不過湊活著用吧(笑哭)。

⑤操作數(shù)據(jù)庫

package com.dreamchaser.loginTest.mapper;import com.dreamchaser.loginTest.utils.Pool;import java.sql.*;/** * 查詢用戶的Mapper */public class UserMapper { static UserMapper userMapper=new UserMapper(); //單例 public static UserMapper getUserMapper(){return userMapper; } private UserMapper(){ } //默認(rèn)數(shù)據(jù)庫中用戶名唯一 public String getPwdByName(String name){Connection connection= Pool.getConnection();try { PreparedStatement statement=connection.prepareStatement('select pwd from `user` where uname=?'); statement.setString(1,name); ResultSet rs=statement.executeQuery(); //resultSet初始下標(biāo)無法訪問,要調(diào)用next方法后移一位 rs.next(); return rs.getString(1);} catch (SQLException throwables) { throwables.printStackTrace();}return null; }}

這里采用單例的設(shè)計(jì)模式,保證UserMapper對象只有一個(gè)。(非常簡陋,實(shí)現(xiàn)也不優(yōu)雅,看著自己的代碼,突然感覺框架好方便啊(笑哭))

這里的作用就是根據(jù)用戶名查詢密碼。

⑥配置web.xml

雖然寫了servlet,但是tomcat并不知道你這個(gè)servlet的類在哪啊,所以必須讓tomcat知道,配置web.xml的目的就是通知tomcat在哪(更準(zhǔn)確的說是servlet容器)的一種方式(當(dāng)然也可以用注解)。配置如下:

<!DOCTYPE web-app PUBLIC '-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN' 'http://java.sun.com/dtd/web-app_2_3.dtd' ><web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.dreamchaser.loginTest.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/login</url-pattern> </servlet-mapping></web-app>

servlet-class里寫你這個(gè)Servlet類的路徑即可。

⑦idea運(yùn)行配置

idea配置還是比較方便的。

點(diǎn)擊編輯配置,

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

點(diǎn)擊+(添加按鈕),選擇tomcat服務(wù)器(選哪個(gè)都可以,我選了tomcat本地)

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

然后選擇相應(yīng)的服務(wù)器程序,配置項(xiàng)目訪問的端口,就是tomcat在哪個(gè)端口運(yùn)行(注意不要占用已有端口,默認(rèn)8080,我這里是因?yàn)?080被占了,所以用了9090)

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

這里還得配置一下工件,因?yàn)轫?xiàng)目要運(yùn)行一般有兩種方式:

一種是打成war包放在tomcat的webapps目錄下 一種是打成jar包直接運(yùn)行(SpringBoot就是用這種方式,因?yàn)樗鼉?nèi)置tomcat)

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

這里工件的作用就是打成war包,至于每次運(yùn)行部署?idea都會幫你搞定!是不是很方便?

這里那個(gè)應(yīng)用程序上下文的作用就是給訪問路徑加個(gè)前綴

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

一般直接寫成'/'就行了,這樣我們要訪問login.html,只需訪問http://localhost:9090/login.html就行了,是不是很方便?

⑧運(yùn)行程序

點(diǎn)擊運(yùn)行

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

訪問localhost:9090/login.html(我因?yàn)槭窃趌ogin.html外面放了一個(gè)pages包,所以路徑是http://localhost:9090/pages/login.html)

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

訪問成功,試試賬號密碼

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

我現(xiàn)在數(shù)據(jù)庫里只有root這一條數(shù)據(jù),試試效果

輸入錯誤的密碼

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

輸入正確的密碼

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

到這里,我們松了一口氣,終于完成了簡單的登錄功能。

三、回顧

別急,我們雖然實(shí)現(xiàn)了登錄這個(gè)功能,但是這個(gè)實(shí)現(xiàn)是在太簡陋了,各方各面都沒考慮,返回頁面也只登錄成功,登錄失敗的提示。我們回顧一下,仔細(xì)想想有哪些問題。

1.密碼未加密裸奔

我們在做上面的登錄時(shí)查詢時(shí),密碼是查詢出來直接比對的,也就是說數(shù)據(jù)庫的密碼是明文存儲,而注冊登錄請求中密碼都是明文傳輸,這樣做的安全性極低,當(dāng)黑客破解進(jìn)入了你的數(shù)據(jù)庫時(shí),你的數(shù)據(jù)庫的賬戶信息都在“裸奔”,比如前些年的csdn密碼泄露事件

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

如果我們存儲在數(shù)據(jù)庫的用戶密碼是加密過的,那么就算黑客進(jìn)入了你的數(shù)據(jù)庫,損失也不會像明文存儲那樣大。

2.登錄信息未存儲

對于這個(gè)登錄操作,登錄成功后并未做其他處理,也就是說每次訪問都要登錄(如果對請求進(jìn)行了攔截),或者這個(gè)登錄操作就是擺設(shè),用戶訪問其他資源依舊暢通無阻。

3.對于其他資源并未進(jìn)行權(quán)限管理

對于其他資源,如果不進(jìn)行權(quán)限管理,那么登錄認(rèn)證便失去了意義,不如做成一個(gè)靜態(tài)網(wǎng)頁來的省事。

四、優(yōu)化設(shè)計(jì)

針對上述缺點(diǎn),我們可以進(jìn)行以下改進(jìn):

1.密碼加密存儲

針對密碼未加密裸奔的問題,我們可以選擇在注冊的時(shí)候?qū)γ艽a進(jìn)行加密,然后存儲;對于登錄功能,我們對前端傳過來的密碼進(jìn)行加密,再根據(jù)這個(gè)密碼去數(shù)據(jù)庫中取數(shù)據(jù),這樣我們就實(shí)現(xiàn)了對密碼的加密存儲

2.存儲登錄信息

對于登錄操作,我們必須記錄下此次登錄狀態(tài),并在該用戶繼續(xù)訪問其他資源時(shí)予以放行,避免用戶多次進(jìn)行登錄操作

3.對資源進(jìn)行管理

對于系統(tǒng)資源我們必須進(jìn)行管理,在用戶沒有相應(yīng)權(quán)限時(shí)拒絕用戶的訪問請求,這個(gè)可以用過濾器或者SpringBoot的攔截器實(shí)現(xiàn)。

五、關(guān)于鑒權(quán)問題

在正式講思路之前,我還是想聊聊鑒權(quán)問題。

1.Cookie/Session機(jī)制

關(guān)于這個(gè)問題我不得不說說cookie/session機(jī)制(想了解的具體可以看這篇cookie和session的詳解與區(qū)別)。

總的來說,就是瀏覽器中有個(gè)叫做cookie的東西(其實(shí)就是個(gè)文件),它可以用來存儲一些信息,每次發(fā)送請求時(shí),瀏覽器會自動把cookie字段信息加在請求頭里發(fā)送出去

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

這有什么用呢?

學(xué)過計(jì)算機(jī)網(wǎng)絡(luò)的人應(yīng)該都清楚我們的http請求是無法保存狀態(tài)的,通俗點(diǎn)來講就是這次的請求無法知道上次的請求是什么,而這也對一些場景帶來的一些不便,就比如說登錄,我們就需要保存上次登錄的信息。

可http請求無法保存狀態(tài),所以我們必須把一些信息寫入到下次的請求里,保證服務(wù)器知道之前的關(guān)鍵信息,以便對之后的請求做出特定的操作。

而cookie便是解決這個(gè)問題而出現(xiàn)的,當(dāng)我們需要存儲一些信息(狀態(tài)),就可以把信息存入cookie,瀏覽器每次發(fā)送請求時(shí)都會把cookie放在請求頭中(但這個(gè)要注意跨域問題,cookie在遇到跨域訪問時(shí)會失效,不過這個(gè)無關(guān)此次主題,就不細(xì)講了,感興趣的自行百度吧)。

總而言之,cookie就是存儲在瀏覽器(客戶端)的數(shù)據(jù)(文件),每次訪問時(shí)會帶上對應(yīng)的cookie。

而session是什么呢?session和cookie類似,也是用來存放信息的,不過它是放在服務(wù)器上的。不過呢,session的本質(zhì)是存在于服務(wù)器內(nèi)存中的對象,閱讀源碼我們可以發(fā)現(xiàn)其對應(yīng)的就是一個(gè)ConcurrentMap(線程安全的map容器)

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

每一個(gè)客戶端會對應(yīng)服務(wù)端一個(gè)session對象,而如何得到的關(guān)鍵就在于cookie中的JSESSIONID(tomcat默認(rèn)是這個(gè)名字,名稱可以變,但用法是一樣的),其值便對應(yīng)這map容器的鍵,而map的值便是session對象。這樣每次用戶發(fā)送請求來時(shí),服務(wù)器就能準(zhǔn)確的找到對應(yīng)的session對象了。

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

2.用Cookie/Session解決鑒權(quán)問題?

明白了Cookie/Session的機(jī)制以后,我們不難設(shè)計(jì)出一套簡單的登錄方案——登錄成功后在對應(yīng)的session對象中存放User信息并設(shè)置失效時(shí)間,每次訪問資源都看看session中有沒有對應(yīng)user對象,如果有就說明之前登錄過了,直接通過即可,否則說明未登錄,此時(shí)可以跳轉(zhuǎn)至登錄頁面讓用戶進(jìn)行登錄。

這一切看似都很完美,從某種角度上來說確實(shí)如此,但它沒有缺點(diǎn)嗎?

Cookie/Session機(jī)制的缺點(diǎn)

1.無法解決跨域問題

在跨域訪問時(shí),cookie會失效,這是為了防止csrf攻擊(跨站請求偽造),但對于開發(fā)者來說造成了一定的困擾,因?yàn)楝F(xiàn)實(shí)中的服務(wù)器不可能只有一臺,大概率是集群分布,雖然可以用反向代理避免跨域訪問,但終究是有局限之處的。

2.session機(jī)制依賴于cookie

從cookie/session機(jī)制中我們不難看出,session的實(shí)現(xiàn)依賴于前端的cookie,因?yàn)槠鋝ession的確定必須要前端請求中cookie,沒有了cookie,session是無法確定的。

而這會帶來什么問題呢?那就是對于多端訪問,如手機(jī)App端,其并沒有cookie的直接實(shí)現(xiàn)(可以實(shí)現(xiàn),其實(shí)也就是在請求頭中加入cookie字段,但使用此方式并不普遍,也挺麻煩的),如果cookie很難使用,那么session也無法使用。

3.可拓展性不強(qiáng)

如果將來搭建了多個(gè)服務(wù)器,雖然每個(gè)服務(wù)器都執(zhí)行的是同樣的業(yè)務(wù)邏輯,但是session數(shù)據(jù)是保存在內(nèi)存中的(不是共享的),用戶第一次訪問的是服務(wù)器1,當(dāng)用戶再次請求時(shí)可能訪問的是另外一臺服務(wù)器2,服務(wù)器2獲取不到session信息,就判定用戶沒有登陸過。

與此同時(shí),當(dāng)你使用session的時(shí)候你會發(fā)現(xiàn)一個(gè)很尷尬的事情——你無法直接獲取到存放session的map(除非你用反射),這樣就導(dǎo)致你的操作受限,比如你想以某個(gè)身份強(qiáng)制下線某個(gè)用戶時(shí),session將會變得力不從心。

4.服務(wù)器壓力增大

session存在于服務(wù)器內(nèi)存中,如果session很多,那么服務(wù)器壓力便會很大。會頻繁觸發(fā)gc操作,導(dǎo)致服務(wù)器響應(yīng)變慢,吞吐量下降。

5.安全性問題

Cookie/Session機(jī)制并不是絕對安全,你必須小心應(yīng)對,當(dāng)然我接下來說的token方式同樣也有這樣那樣的問題,但是我們要明白一件事情——沒有絕對安全的系統(tǒng)!

當(dāng)前的所謂安全措施不過是在增加黑客入侵系統(tǒng)的成本,但你要注意的是你在增加黑客入侵的難度和成本的同時(shí),也同樣在增加自己系統(tǒng)的維護(hù)成本,它必然是以一定的性能作為代價(jià)的

所以如何權(quán)衡安全和性能,這是永遠(yuǎn)是一件值得我們深思的事情

3.使用token機(jī)制解決鑒權(quán)問題

什么是token呢?

事實(shí)上它只是我們自己實(shí)現(xiàn)的一套類似cookie/Session的機(jī)制。

至于為啥叫token?

你也可以叫它c(diǎn)at,dog之類的,只要你喜歡,隨便你怎么取名字(笑哭)。

好了,開個(gè)玩笑,咱們回到正題,在我看來,token只是脫胎于cookie/session的一套機(jī)制,它的實(shí)現(xiàn)原理幾乎是和cookie/session一模一樣的(9成像,當(dāng)然也有很多根據(jù)自己業(yè)務(wù)的變種)。

如果說cookie/session機(jī)制可以描述為下圖:

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

那么token機(jī)制可以描述為以下形式:

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

怎么樣?是不是很像?其實(shí)它們核心原理是一樣的。

那token機(jī)制相較于cookie/session機(jī)制有啥好處呢?

1.可以直接操作token令牌池 2.對于手機(jī)App端友好 3.跨域問題可以間接解決 4.對于服務(wù)器集群,token令牌池可以放在redis數(shù)據(jù)庫中(當(dāng)然也可以是其他方案),這樣可以實(shí)現(xiàn)用戶登錄狀態(tài)多服務(wù)器共享

其實(shí),總的來說,就只有一條(笑哭),那就是靈活!因?yàn)閠oken機(jī)制是我們自己實(shí)現(xiàn)的(當(dāng)然也可以借助框架),這樣操作這些東西的時(shí)候就不必拘泥于條條框框,可以根據(jù)自己的業(yè)務(wù)需求制定適合的鑒權(quán)方案。

悄悄告訴你一句:csdn也是用token的哦!(不過具體實(shí)現(xiàn)可能并不一樣)

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

當(dāng)然,相較于cookie/session機(jī)制而言,它也有個(gè)巨大的弊端——在網(wǎng)頁應(yīng)用中,使用token機(jī)制會比使用cookie/session機(jī)制麻煩很多,所有都得“從頭再來”,不像cookie/session可以開箱即用。

六、用SpringBoot+SSM實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)(注冊,登錄,權(quán)限控制)

這里我是用token來實(shí)現(xiàn)鑒權(quán)服務(wù)的。以下是我畫的大致流程圖(可能有點(diǎn)丑,有點(diǎn)亂)

手把手教你用Java實(shí)現(xiàn)一套簡單的鑒權(quán)服務(wù)

在展示代碼實(shí)現(xiàn)時(shí),你可能會對某些類比較疑惑,以下是對這些類的說明:

RestResponse 這是我用來封裝響應(yīng)格式的,Status用來封裝響應(yīng)狀態(tài) CrudUtil 這是我用來封裝CRUD操作的工具類,該類主要為了簡化controller的響應(yīng)操作

同時(shí)我會省略Service層和Dao層實(shí)現(xiàn)

1.注冊服務(wù)

①注冊頁面

<!DOCTYPE html><html lang='zh-CN' xmlns:th='http://www.thymeleaf.org'><head> <meta charset='utf-8'> <title>layui</title> <meta name='renderer' content='webkit'> <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'> <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1'> <link rel='stylesheet' href='http://m.propowerdrill.cn/bcjs/static/css/public.css'> <link rel='stylesheet' href='http://m.propowerdrill.cn/bcjs/static/lib/layui-v2.6.3/css/layui.css'> <style>body { background: url('static/images/loginbg.png') 0% 0% / cover no-repeat; position: static; font-size: 12px;} </style></head><body><div class='layui-container'> <div style='width: 500px;border-radius: 10px'><fieldset style='margin-top: 20%'> <legend style='font-size: 30px;padding-top: 20px;text-align: center'>用戶注冊</legend> <div class='layui-field-box'><div style='margin: 20px;margin-top: 30px'> <div class='layui-form-item'><label class='layui-form-label required'>用戶名</label><div class='layui-input-block'> <input type='text' name='uname' lay-verify='required' lay-reqtext='用戶名不能為空' placeholder='請輸入用戶名' value='' class='layui-input'> <tip>填寫自己真實(shí)姓名</tip></div> </div> <div class='layui-form-item'><label class='layui-form-label required'>性別</label><div class='layui-input-block'> <input type='radio' name='sex' value='男' checked=''> <input type='radio' name='sex' value='女'></div> </div> <div class='layui-form-item'><label class='layui-form-label required'>手機(jī)</label><div class='layui-input-block'> <input type='number' name='phone' lay-verify='phone' placeholder='請輸入手機(jī)號' value='' class='layui-input'></div> </div> <div class='layui-form-item'><label class='layui-form-label required'>郵箱</label><div class='layui-input-block'> <input type='email' name='email' lay-verify='email' placeholder='請輸入郵箱' value='' class='layui-input'></div> </div> <div class='layui-form-item'><label class='layui-form-label required'>密碼</label><div class='layui-input-block'> <input type='text' name='pwd' lay-verify='required' placeholder='請輸入密碼' value='' class='layui-input'></div> </div> <div class='layui-form-item'><label class='layui-form-label required'>入職時(shí)間</label><div class='layui-input-block'> <input type='text' name='entryDate' lay-verify='date' placeholder='請選擇入職時(shí)間' autocomplete='off' class='layui-input'></div> </div> <div class='layui-form-item'><label style='display: inline'>郵箱驗(yàn)證碼</label><input type='text' name='code' placeholder='請輸入驗(yàn)證碼' lay-verify='required' maxlength='5' style='width:160px;display: inline'><button lay-filter='saveBtn' style='display: inline;margin-left: 10px'>發(fā)送驗(yàn)證碼</button> </div> <div style='margin-top: 20px'><div class='layui-input-block'> <button lay-submit lay-filter='registerBtn'>注冊 </button></div> </div></div> </div></fieldset> </div></div><script src='http://m.propowerdrill.cn/bcjs/static/lib/layui-v2.6.3/layui.js' charset='utf-8'></script><script> layui.use([’form’, ’layer’, ’laydate’,’element’], function () {var form = layui.form, layer = layui.layer, laydate = layui.laydate, element=layui.element, $ = layui.$;//日期laydate.render({ elem: ’#date’});//監(jiān)聽提交$(’#saveBtn’).bind(’click’, function () { var email = $(’#email’).val(); if (email===’’||email==null){layer.msg('請輸入正確的郵箱!'); }else {$.ajax({ url: '/sendCode', data:’{'email':’+JSON.stringify(email)+’}’, type: 'post', dataType: ’JSON’, contentType: 'application/json;charset=utf-8', success: function (data) {if (data.status !== 200) { layer.msg(data.statusInfo.message);//失敗的表情 return;} else { layer.msg('驗(yàn)證碼發(fā)送成功,請前往郵箱查看', {icon: 6,//成功的表情time: 1000 //1秒關(guān)閉(如果不配置,默認(rèn)是3秒) }, function () { });} }}); }});//監(jiān)聽提交form.on(’submit(registerBtn)’, function (data) { $.ajax({url: '/register',data: JSON.stringify(data.field),type: 'post',dataType: ’JSON’,contentType: 'application/json;charset=utf-8',success: function (data) { if (data.status !== 200) {layer.msg(data.statusInfo.message);//失敗的表情return; } else {layer.msg('注冊成功', { icon: 6,//成功的表情 time: 1000 //1秒關(guān)閉(如果不配置,默認(rèn)是3秒)}, function () { window.location = ’/login’;}); }} }); return false;}); });</script></body></html>

②發(fā)送驗(yàn)證碼

sendcode接口

/** * 驗(yàn)證是否有此賬號,然后發(fā)送驗(yàn)證碼 * @param map 主要認(rèn)證主體,如賬號,郵箱,qq的openID,wechat的code等 * @return restResponse,附帶憑證token */ @PostMapping('/sendCode') public RestResponse sendCode(@RequestBody Map<String,Object> map){if (userService.findUserByCondition(map)==null){ String principal; if (map.get('phone')!=null){principal=String.valueOf(map.get('phone')); }else if (map.get('email')!=null){principal=String.valueOf(map.get('email')); }else {return CrudUtil.ID_MISS_RESPONSE; } //創(chuàng)建一個(gè)驗(yàn)證碼 VerificationCode v=new VerificationCode(); //將驗(yàn)證碼存入驗(yàn)證碼等待池 VerificationCodePool.addCode(principal,v); //發(fā)送郵箱驗(yàn)證碼 sendEmail(principal,v.getCode()); return new RestResponse();}return new RestResponse('',304,new StatusInfo('發(fā)送驗(yàn)證碼失敗,該賬戶已存在!','發(fā)送驗(yàn)證碼失敗,該賬戶已存在!')); }

郵件發(fā)送方法(調(diào)用SpringBoot提供的mail服務(wù)(需要導(dǎo)包))

/** * 發(fā)送帶有驗(yàn)證碼的郵件信息 */ private void sendEmail(String email,String code){//發(fā)送驗(yàn)證郵件try { SimpleMailMessage mailMessage = new SimpleMailMessage(); //主題 mailMessage.setSubject('倉庫管理系統(tǒng)的驗(yàn)證碼郵件'); //內(nèi)容 mailMessage.setText('歡迎使用倉庫管理系統(tǒng),您正在注冊此賬戶。' + 'n您收到的驗(yàn)證碼是: '+code+' ,請不要將此驗(yàn)證碼透露給別人。'); //發(fā)送的郵箱地址 mailMessage.setTo(email); //默認(rèn)發(fā)送郵箱郵箱 mailMessage.setFrom(fromEmail); //發(fā)送 mailSender.send(mailMessage);}catch (Exception e){ throw new MyException(e.toString());} }

驗(yàn)證碼對象

package com.dreamchaser.depository_manage.security.bean;import lombok.Data;import java.time.Instant;import java.util.Random;/** * 驗(yàn)證碼,默認(rèn)有效期為五分鐘 * @author 金昊霖 */@Datapublic class VerificationCode { /** * 默認(rèn)持續(xù)時(shí)間 */ private final long DEFAULT_TERM=60*5; /** * 驗(yàn)證碼 */ private String code; /** * 創(chuàng)建時(shí)刻 */ private Instant instant; /** * 有效期 */ private long term; /** * 根據(jù)時(shí)間判斷是否有效 * @return boolean值 */ public boolean isValid(){return Instant.now().getEpochSecond()-instant.getEpochSecond()<=term; } public VerificationCode(Instant instant, long term) {//生成隨機(jī)驗(yàn)證碼codegenerateCode();this.instant = instant;this.term = term; } public VerificationCode(Instant instant) {//生成隨機(jī)驗(yàn)證碼codegenerateCode();this.instant = instant;this.term=DEFAULT_TERM; } public VerificationCode() {//生成隨機(jī)驗(yàn)證碼codegenerateCode();this.instant=Instant.now();this.term=DEFAULT_TERM; } private void generateCode(){StringBuilder codeNum = new StringBuilder();int [] numbers = {0,1,2,3,4,5,6,7,8,9};Random random = new Random();for (int i = 0; i < 5; i++) { //目的是產(chǎn)生足夠隨機(jī)的數(shù),避免產(chǎn)生的數(shù)字重復(fù)率高的問題 int next = random.nextInt(10000); codeNum.append(numbers[next % 10]);}this.code= codeNum.toString(); }}

驗(yàn)證碼池

package com.dreamchaser.depository_manage.security.pool;import com.dreamchaser.depository_manage.security.bean.VerificationCode;import java.util.Map;import java.util.concurrent.ConcurrentHashMap;/** * 驗(yàn)證碼等待池 * @author 金昊霖 */public class VerificationCodePool { private static Map<String, VerificationCode> pool=new ConcurrentHashMap<>(10); /** * 增加一條驗(yàn)證碼 * @param principal 主要內(nèi)容,如郵箱,電話號碼等 * @param verificationCode 驗(yàn)證碼 */ public static void addCode(String principal,VerificationCode verificationCode){pool.put(principal, verificationCode); } /** * 根據(jù)principal主要信息獲取未過期的驗(yàn)證碼,如果沒有未過期的令牌則返回null * @param principal 主要內(nèi)容,如郵箱,電話號碼等 * @return verificationCode 未過期的驗(yàn)證碼或者null */ public static VerificationCode getCode(String principal){VerificationCode verificationCode=pool.get(principal);//如果沒有相應(yīng)驗(yàn)證碼則直接返回nullif (verificationCode==null){ return null;}//判斷令牌是否過期if (verificationCode.isValid()){ //將驗(yàn)證碼取出 pool.remove(principal); return verificationCode;}else{ //清除過期驗(yàn)證碼 pool.remove(principal); return null;} } /** * 根據(jù)主要信息principal刪除對應(yīng)的驗(yàn)證碼 * @param principal 主要信息 */ public static void removeCode(String principal){pool.remove(principal); }}

③注冊用戶

MD5加密類

/* * Copyright (c) JForum Team All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1) Redistributions of * source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2) Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other materials provided with the distribution. 3) Neither the name of 'Rafael Steil' nor the names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE This file creation date: Mar 29, 2003 / * 1:15:50 AM The JForum Project http://www.jforum.net */package com.dreamchaser.depository_manage.utils;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;/** * MD5加密 */public class Md5 {/** * Encodes a string * @param str String to encode * @return Encoded String */public static String crypt(String str) {if (str == null || str.length() == 0) {throw new IllegalArgumentException('String to encript cannot be null or zero length');}StringBuilder hexString = new StringBuilder();try {MessageDigest md = MessageDigest.getInstance('MD5');md.update(str.getBytes());byte[] hash = md.digest();for (byte b : hash) {if ((0xff & b) < 0x10) {hexString.append('0').append(Integer.toHexString((0xFF & b)));} else {hexString.append(Integer.toHexString(0xFF & b));}}} catch (NoSuchAlgorithmException e) {e.printStackTrace();}return hexString.toString();}}

注冊用戶接口

/** * 注冊用戶(通常為手機(jī)或者郵箱注冊) * @param map 參數(shù)列表,包括賬號(手機(jī)注冊就是phone,郵箱就是email)、密碼 * @return 成功則返回憑證,否則返回驗(yàn)證失敗 */ @PostMapping('/register') public RestResponse register(@RequestBody Map<String,Object>map){String principal;Object password=map.get('pwd');Object code=map.get('code');UserToken userToken;//判斷必要參數(shù)是否滿足if (password==null||code==null){ return CrudUtil.ID_MISS_RESPONSE;}//從map中獲取對應(yīng)參數(shù)if (map.get('email')!=null){ principal=String.valueOf(map.get('email')); userToken=new UserToken(LoginType.EMAIl_PASSWORD,principal,String.valueOf(password));}else { return CrudUtil.ID_MISS_RESPONSE;}//驗(yàn)證碼正確且成功插入數(shù)據(jù)if (checkCode(principal,String.valueOf(code))){ //對密碼進(jìn)行加密然后存儲用戶信息 map.put('pwd',Md5.crypt(String.valueOf(map.get('pwd')))); //如果用戶記錄插入成功 if (userService.insertUser(map)==1){String token= Md5.crypt(userToken.getPrincipal()+userToken.getInstant());//返回憑證return new RestResponse().setData(token); }}else { //驗(yàn)證碼錯誤 return CrudUtil.CODE_ERROR;}return

這里的LoginType是登錄方式,這個(gè)之后會提到

檢驗(yàn)驗(yàn)證碼方法

/** * 用于注冊用戶的方法,主要為號碼驗(yàn)證和郵箱驗(yàn)證提供驗(yàn)證碼核對的服務(wù) * @param principal 認(rèn)證主體 * @param code 驗(yàn)證碼 * @return 是否驗(yàn)證通過 */ private boolean checkCode(String principal,String code){if (code!=null){ VerificationCode verificationCode=VerificationCodePool.getCode(principal); if (verificationCode!=null){return code.equals(verificationCode.getCode()); }}return false; }2.登錄服務(wù)

登錄界面

這里為了方便起見,我把token存儲在cookie中

<!DOCTYPE html><html lang='zh-CN' xmlns:th='http://www.thymeleaf.org'><head> <meta charset='UTF-8'> <title>后臺管理-登陸</title> <meta http-equiv='X-UA-Compatible' content='IE=edge,chrome=1'> <meta http-equiv='Access-Control-Allow-Origin' content='*'> <meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1'> <meta name='apple-mobile-web-app-status-bar-style' content='black'> <meta name='apple-mobile-web-app-capable' content='yes'> <meta name='format-detection' content='telephone=no'> <link rel='stylesheet' href='http://m.propowerdrill.cn/bcjs/static/lib/layui-v2.6.3/css/layui.css' media='all'> <!--[if lt IE 9]> <script src='https://cdn.staticfile.org/html5shiv/r29/html5.min.js'></script> <script src='https://cdn.staticfile.org/respond.js/1.4.2/respond.min.js'></script> <![endif]--> <style>html, body {width: 100%;height: 100%;overflow: hidden}body {background: #1E9FFF;}body:after {content:’’;background-repeat:no-repeat;background-size:cover;-webkit-filter:blur(3px);-moz-filter:blur(3px);-o-filter:blur(3px);-ms-filter:blur(3px);filter:blur(3px);position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1;}.layui-container {width: 100%;height: 100%;overflow: hidden}.admin-login-background {width:360px;height:300px;position:absolute;left:50%;top:40%;margin-left:-180px;margin-top:-100px;}.logo-title {text-align:center;letter-spacing:2px;padding:14px 0;}.logo-title h1 {color:#1E9FFF;font-size:25px;font-weight:bold;}.login-form {background-color:#fff;border:1px solid #fff;border-radius:3px;padding:14px 20px;box-shadow:0 0 8px #eeeeee;}.login-form .layui-form-item {position:relative;}.login-form .layui-form-item label {position:absolute;left:1px;top:1px;width:38px;line-height:36px;text-align:center;color:#d2d2d2;}.login-form .layui-form-item input {padding-left:36px;}.captcha {width:60%;display:inline-block;}.captcha-img {display:inline-block;width:34%;float:right;}.captcha-img img {height:34px;border:1px solid #e6e6e6;height:36px;width:100%;} </style></head><body><div class='layui-container'> <div class='admin-login-background'><div class='layui-form login-form'> <form action=''><div class='layui-form-item logo-title'> <h1>倉庫信息管理系統(tǒng)登錄</h1></div><div class='layui-form-item'> <label ></label> <input type='text' name='principal' lay-verify='required|account' placeholder='請輸入郵箱' autocomplete='off' ></div><div class='layui-form-item'> <label ></label> <input type='password' name='credentials' lay-verify='required|password' placeholder='密碼' autocomplete='off' class='layui-input'></div><!-- 徒有其表的驗(yàn)證碼,主要是不想另外弄了 --><div class='layui-form-item'> <label ></label> <input type='text' name='captcha' lay-verify='required|captcha' placeholder='圖形驗(yàn)證碼' autocomplete='off' class='layui-input verification captcha'> <div class='captcha-img'><img src='http://m.propowerdrill.cn/bcjs/static/images/captcha.jpg'> </div></div><div class='layui-form-item'> <input type='checkbox' name='rememberMe' value='true' lay-skin='primary'></div><div class='layui-form-item'> <button lay-submit='' lay-filter='login'>登 入</button></div> </form></div> </div></div><script src='http://m.propowerdrill.cn/bcjs/static/lib/jquery-3.4.1/jquery-3.4.1.min.js' charset='utf-8'></script><script src='http://m.propowerdrill.cn/bcjs/static/lib/layui-v2.6.3/layui.js' charset='utf-8'></script><script src='http://m.propowerdrill.cn/bcjs/static/lib/jq-module/jquery.particleground.min.js' charset='utf-8'></script><script src='http://m.propowerdrill.cn/bcjs/static/js/cookie.js' charset='utf-8'></script><script> layui.use([’layer’,’form’], function () {var form = layui.form, layer = layui.layer;// 登錄過期的時(shí)候,跳出ifram框架if (top.location != self.location) top.location = self.location;// 粒子線條背景$(document).ready(function(){ $(’.layui-container’).particleground({dotColor:’#7ec7fd’,lineColor:’#7ec7fd’ });});// 進(jìn)行登錄操作form.on(’submit(login)’, function (data) { data = data.field; if (data.principal === ’’) {layer.msg(’用戶名不能為空’);return false; } if (data.credentials === ’’) {layer.msg(’密碼不能為空’);return false; } if (data.captcha === ’’) {layer.msg(’驗(yàn)證碼不能為空’);return false; } data.loginType='email'; $.ajax({url:'/login',type:’post’,dataType:’json’,contentType: 'application/json;charset=utf-8',data:JSON.stringify(data),beforeSend:function () { this.layerIndex = layer.load(0, { shade: [0.5, ’#393D49’] });},success:function(data){ if(data.status !== 200){layer.msg(data.statusInfo.message);//失敗的表情return; }else{layer.msg('登錄成功', { icon: 6,//成功的表情 time: 1000 //1秒關(guān)閉(如果不配置,默認(rèn)是3秒)}, function(){ cookieUtil.createCookie('token',data.data) window.location = ’/index’;}); }},complete: function () { layer.close(this.layerIndex);} }) return false;}); });</script></body></html>

當(dāng)然這里封裝了cookie的操作

var cookieUtil={ createCookie:function (name,value,days){var expires='';if (days){ var date=new Date(); date.setTime(date.getTime()+(days*14*24*3600*1000)); expires=';expires='+date.toGMTString();}document.cookie=name+'='+value+expires+';path=/'; }, /*設(shè)置cookie*/ set:function(name,value,expires,path,domain,secure){var cookie=encodeURIComponent(name)+'='+encodeURIComponent(value);if(expires instanceof Date){ cookie+='; expires='+expires.toGMTString();}else{ var date=new Date(); date.setTime(date.getTime()+expires*24*3600*1000); cookie+='; expires='+date.toGMTString();}if(path){ cookie+='; path='+path;}if(domain){ cookie+='; domain='+domain;}if (secure) { cookie+='; '+secure;}document.cookie=cookie; }, /*獲取cookie*/ get:function(name){var cookieName=encodeURIComponent(name);/*正則表達(dá)式獲取cookie*/var restr='(^| )'+cookieName+'=([^;]*)(;|$)';var reg=new RegExp(restr);var cookieValue=document.cookie.match(reg)[2];/*字符串截取cookie*//*var cookieStart=document.cookie.indexOf(cookieName+“=”);var cookieValue=null;if(cookieStart>-1){ var cookieEnd=document.cookie.indexOf(';',cookieStart); if(cookieEnd==-1){cookieEnd=document.cookie.length; } cookieValue=decodeURIComponent(document.cookie.substring(cookieStart +cookieName.length,cookieEnd));}*/return cookieValue; }}

登錄接口

這里的token憑證是根據(jù)用戶密碼+當(dāng)前時(shí)刻(鹽)加密得到的

/** * 登錄接口 * @param map 登錄信息 * loginType 登錄方式,目前支持的有email,qq,wechat * principal 主要認(rèn)證主體,如賬號,郵箱,qq的openID,wechat的code等 * credentials 類似于密碼,如果是qq,wechat則不需要傳改參數(shù) * restResponse,附帶憑證token */ @PostMapping('/login') public RestResponse login(@RequestBody Map<String,String> map) {UserToken userToken=new UserToken(LoginType.getType(map.get('loginType')),map.get('principal'),map.get('credentials'));return login(userToken); }

認(rèn)證方法

/** * 將生成的令牌拿去認(rèn)證,如果認(rèn)證成功則返回帶有token憑證響應(yīng),否則返回用戶密碼錯誤的響應(yīng) * @param userToken 未認(rèn)證的令牌 * @return restResponse 如果認(rèn)證成功則返回帶有token憑證響應(yīng),否則返回用戶密碼錯誤的響應(yīng) */ private RestResponse login(UserToken userToken) {String token=loginRealms.authenticate(userToken);if (token!=null){ return new RestResponse(token);}else { return CrudUtil.NOT_EXIST_USER_OR_ERROR_PWD_RESPONSE;} }

登錄方式enum類

這里可以看到我里面有多種方式登錄,不過我的代碼里只實(shí)現(xiàn)了郵箱登錄,其余方式可以自己去實(shí)現(xiàn)拓展

package com.dreamchaser.depository_manage.security.bean;/** * 登錄方式枚舉類 * @author 金昊霖 */public enum LoginType { /** * 通用 */ COMMON('common_realm'), /** * 用戶密碼登錄 */ EMAIl_PASSWORD('user_password_realm'), /** * 手機(jī)驗(yàn)證碼登錄 */ USER_PHONE('user_phone_realm'), /** * 第三方登錄(微信登錄) */ WECHAT_LOGIN('wechat_login_realm'), /** * 第三方登錄(qq登錄) */ QQ_LOGIN('qq_login_realm'); private String type; LoginType(String type) {this.type = type; } public String getType() {return type; } /** * 根據(jù)簡單的字符串返回對應(yīng)的LoginType * @param s 簡單的字符串 * @return 對應(yīng)的LoginType */ public static LoginType getType(String s){switch (s) { case 'email':return EMAIl_PASSWORD; case 'qq':return QQ_LOGIN; case 'wechat':return WECHAT_LOGIN; case 'phone':return USER_PHONE; default:return null;} } @Override public String toString() {return this.type; }}

登錄方式類

這里面可以根據(jù)自己的業(yè)務(wù)拓展,我只實(shí)現(xiàn)了郵箱登錄

package com.dreamchaser.depository_manage.security.bean;import com.dreamchaser.depository_manage.entity.User;import com.dreamchaser.depository_manage.exception.MyExcept

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 日韩精品久久久久久久酒店 | 国产精品人人爽人人爽av | 精品国产黑色丝袜高跟鞋 | 不卡日韩| 51免费动漫网永久入口 | 一区二区三区免费观看 | 成人免费看黄yyy456 | 秋葵视频在线 | 黄色在线视频网站 | 午夜av不卡| 伊人网在线视频观看 | 亚洲成av人片在线观看无 | 中国老妇荡对白正在播放 | 日韩精品一区二区三区免费视频 | 搐搐国产丨区2区精品av | 中文字幕亚韩 | 成人h片在线观看 | 搐搐国产丨区2区精品av | 天天看片天天射 | 网站在线观看你懂的 | 国产精品一线二线三线 | 夜夜爽天天干 | 拔萝卜在线 | 五月丁香啪啪 | 99久久夜色精品国产亚洲 | 欧美久久久久久久久久久 | 成人精品免费在线观看 | 成人三级a做爰视频哪里看 成人三级k8经典网 成人三级黄色 | 免费在线看黄色片 | 亚洲男人av | 狠狠操欧美 | 91亚洲精华国产精华液 | 国产情侣一区二区三区 | 区美成人aaaaa | 四虎网站在线 | 狠狠亚洲超碰狼人久久 | 亚洲3p激情在线观看 | 久久久久久网站 | 懂色av中文一区二区三区 | www爱爱| 久久婷婷激情综合色综合俺也去 | 亚洲国产精品一区二区尤物区 | 精品人妻无码一区二区三区蜜桃一 | 超碰人人在线 | 欧美精品一区二区在线播放 | 蜜臀av在线观看 | 日本免费一区二区视频 | 一本一本久久a久久精品综合不卡 | 国产性生活视频 | 欧美精品自拍视频 | www成人国产 | 东京热一区二区三区无码视频 | 亚洲理论中文字幕 | 亚洲一区欧美二区 | 又色又爽又黄又刺激免费 | 国产精品久久久久久久免费看 | 国产免费一区二区三区香蕉精 | 日韩成人福利 | 麻豆影院免费夜夜爽日日澡 | 国产精品成人av在线观看春天 | 免费观看激色视频网站 | 久久日本香蕉一区二区三区 | jizz欧美性11| 亚洲国产精品久久久久久久久久 | 日本精品视频在线 | 天天操天天爽天天射 | 久久久久久久国产精品 | 亚洲国产高清视频 | 中文字幕在线视频一区二区三区 | 国产成人无码aⅴ片在线观看 | 天天躁夜夜躁天干天干2020 | 欧美xo影院 | 日韩伦人妻无码 | 国产专业剧情av在线 | 夜夜精品无码一区二区三区 | 搐搐国产丨区2区精品av | 日韩欧美精品在线 | 人妻丰满熟妇aⅴ无码 | 日韩av在线播放网址 | 一本色道久久综合无码人妻 | 日韩中文字幕亚洲精品欧美 | 青青草偷拍视频 | 国产一区二区精品丝袜 | 日韩精品视频在线观看一区二区三区 | 精品久久亚洲中文无码 | av一区二区三区 | 各种高潮超清特写tv | 国产h自拍| 国产午夜精品一区二区三区视频 | 日本在线高清不卡免费播放 | 性中国妓女毛茸茸视频 | 51妺嘿嘿午夜福利 | 九九久久精品国产 | 色婷婷综合久久久久中文 | 亚洲一在线 | 国产情侣久久 | www欧美精品 | 国产三a级三级日产三级野外 | 精品偷拍被偷拍在线观看 | 一级淫片免费看 | 最新中文字幕av无码不卡 | 中出中文字幕 | 国产欧美一区二区精品久久久 | 师尊双性精跪趴灌满h视频 湿女导航福利av导航 | av 一区二区三区 | 欧美日韩国产精品一区 | 少妇视频在线播放 | 亚洲欧美成人精品香蕉网 | 国产成人午夜精华液 | 中文在线免费观看入口 | 欧美性大战久久久久久久 | 在线精品观看 | 肥臀浪妇太爽了快点再快点 | 日本乱大交做爰 | 天天躁夜夜躁天干天干2022 | 三上悠亚久久爱一区 | 国产欧美日韩va另类在线播放 | 色多多导航 | 女人和拘做爰正片视频 | 爱爱视频免费网址 | 国内免费毛片 | 少妇乱淫aaa高清视频真爽 | 在线中文字幕网站 | 国产成人久久综合第一区 | 久久亚洲美女精品国产精品 | 国产一级一级片 | 日本丰满大乳免费xxxx | 伊人干综合| 最新无码a∨在线观看 | 久久av无码精品人妻出轨 | 亚洲乱码视频在线观看 | 黑人巨大精品欧美一区二区三区 | 亚洲免费天堂 | 乌克兰极品少妇xxxx做受小说 | 69国产成人精品二区 | 最近中文字幕免费视频 | 国产成人av一区二区三区 | 青青青在线视频人视频在线 | 免费中文字幕日韩 | 亚洲永久无码7777kkk | 亚洲欧美国产精品专区久久 | 永久免费看mv网站入口亚洲 | 日韩欧美在线一区二区三区 | 欧美三级影院 | 亚洲图片欧美 | 女医生大乳奶水 | 91精品国产一区二区在线观看 | 国产精品视频yy9299一区 | 天堂va蜜桃| 爱爱视频免费网址 | 亚洲一区二区三区国产好的精华液 | 国内精品写真在线观看 | 国产经典毛片 | 91精品国产福利一区二区三区 | 簧片av| 亚洲一区在线免费 | 欧美日韩中文国产一区 | 邻居少妇张开腿让我爽了一夜 | 国内毛片毛片毛片毛片毛片毛片 | 亚洲激情 | 国精产品999国精产品蜜臀 | jizz性欧美10 | 色88久久久久高潮综合影院 | 欧美日韩中出 | 综合久久给合久久狠狠狠97色 | 色乱码一区二区三在线看 | videos国产单亲乱 | 永久免费看成人av的动态图 | 黄网av | 人妻三级日本三级日本三级极 | 国产精品a成v人在线播放 | 全国最大成人网 | 久久久久琪琪去精品色无码 | 清纯唯美亚洲色图 | 亚洲永久无码7777kkk | 成人51网站 | 国产精品久久人 | 亚洲黄色天堂 | 久国产| 国产精品入口免费视频一 | 鸥美毛片| 亚洲国产综合色产精品色在线 | 不卡中文字幕在线观看 | 日本啪啪网 | 欧美巨大另类极品videosbest | 黄色一级二级 | 一级成人免费视频 | 久久成人国产精品 | 看免费真人视频网站 | 福利视频午夜 | 亚洲欧美日韩国产 | 久久一二三四区 | 亚洲经典久久 | 久久久噜噜噜久久 | 久久午夜国产精品www忘忧草 | 国产在线精品一区二区三区 | 午夜国人精品av免费看 | 男女一边摸一边做爽爽 | 国产成人无码a在线观看不卡 | 99激情网| 成年人看的黄色 | 香蕉视频性 | 亚洲 欧美 变态 另类 综合 | 亚洲国产精品一区二区尤物区 | 中文字幕大香视频蕉免费 | 亚洲免费看av | 男主和女配啪慎入h闺蜜宋冉 | 亚洲精品福利网站 | 国产美女亚洲精品久久久毛片小说 | 日产久久久久久 | 国产色视频在线观看免费 | 免费视频在线观看1 | 久久亚洲国产成人精品无码区 | 久久99久久99精品免观看软件 | 国产伦精品一区二区三区免费优势 | 免费jizzjizz在线播放 | www.youjizz在线| 激情综合区 | 午夜精品久久久久久久99热额 | 全部免费毛片在线播放 | 国产九九九 | 国产艳妇疯狂做爰视频 | 免费国产又色又爽又黄的网站 | 亚洲免费a视频 | 91精品国产综合久久精品性色 | 中文字幕日产无线码一区 | 男女做爰猛烈叫床无遮挡 | aa视频网站 | 欧美精品欧美精品系列 | 97超级碰碰碰 | 深夜福利免费在线观看 | 一级a性色生活片久久毛片明星 | 看曰本女人大战黑人视频 | 一级全黄少妇性色生活片 | www国产亚洲精品久久网站 | 欧美成人r级一区二区三区 欧美成人tv | 国产精品一区二区三区视频免费 | 久草青青视频 | 羞羞答答av成人免费看 | 欧美成人精品一区二区男人小说 | 日韩三级视频在线播放 | 国产欧美精品在线观看 | 精品国产黄色片 | 免费av不卡 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 亚洲美女屁股眼交3 | 午夜免费福利视频 | 国产又粗又猛又爽又黄91网站 | 亚洲香蕉中文网 | 精品国产品香蕉在线 | 国色天香中文字幕在线视频 | 久久精品亚洲一区二区三区浴池 | 中文字幕巨乳 | 免费观看av网站 | 久久综合av | 乱lun合集小可的奶水 | 狼人综合网 | 福利片一区二区三区 | 国产黄a三级三级三级 | 在线免费看a | 日本sm极度另类视频 | 中文字幕日韩精品有码视频 | 在线亚洲精品 | 丝袜 制服 国产 欧美 亚洲 | 国产综合99 | 级毛片内射视频 | 伊人久久久久久久久久 | 日韩a毛片| 日本高清视频www夜色资源 | 最新中文字幕在线观看 | 色吧婷婷 | www亚洲免费| 亚洲男人影院 | 精品人人妻人人澡人人爽牛牛 | 婷婷久久五月 | 免费大片黄在线观看 | 国产成人av免费 | 欧美成人在线免费视频 | 一级一级一级毛片 | 国产黄色免费片 | 正在播放国产真实哭都没用 | 天堂中文在线资源 | 国产精品亚洲成在人线 | tushy欧美激情在线看 | 亚洲一区二区三区中文字幕 | 亚洲乱码一二三四区 | 欧美视频网站www色 精品无码久久久久久久动漫 | 成人开心网| 免费看黄在线网站 | 免费放黄网站在线播放 | 亚洲自拍偷拍综合 | 亚洲色大成网站www永久男同 | 天天色综合5 | 性xx无遮挡 | 嫩草综合| 午夜精品久久久久久久爽 | 亚洲春色av无码专区在线播放 | 男女日批免费视频 | 国产特黄aaa大片免费观看 | 成人av地址| 国产无遮无挡120秒 国产无遮掩 | 久久伊人草 | 综合人人 | 波多野结衣亚洲一区 | 99精品视频在线观看免费 | 亚洲欧洲精品一区二区三区 | 久久久久久久久99精品 | 精品福利视频一区二区三区 | 91户外露出一区二区 | 麻豆乱淫一区二区三区 | 成人午夜毛片 | 56av国产精品久久久久久久 | 亚洲调教欧美在线 | av免费播放网站 | 精品国产品香蕉在线 | 国产另类xxxxhd高清 | 色福利视频| 国产第七页 | 人体写真福利视频 | 亚洲高清视频在线 | 日本一区二区三区在线观看 | 欧美日韩一区二区免费视频 | 中国一级毛片黄 | 久热在线视频 | 亚洲国产欧美日韩精品一区二区三区 | 色综合久久久久综合体桃花网 | 国产成人网 | 日韩av午夜在线观看 | 爱情岛aqdlt国产论坛 | 日韩国产一级片 | 高h猛烈做哭你尿进去了网站 | 国产成人午夜 | 丝袜+亚洲+另类+欧美+变态 | 日本无遮挡大尺度床戏网站 | 天干夜天干天天天爽视频 | 亚洲欧洲精品专线 | 亚洲v日韩v综合v精品v | 国产午夜福利100集发布 | 成人综合网亚洲伊人 | 欧美日韩中文在线视频 | 欧美、另类亚洲日本一区二区 | 欧美三级a做爰在线观看 | 国产99久久 | 四虎在线影院 | 午夜快播 | 无码国模国产在线观看 | 人人妻人人澡人人爽欧美精品 | 免费特级毛片 | 色婷婷av一区二区三区之e本道 | 久久久嫩草 | 国产成人久久久精品免费澳门 | 天天透天天干 | 玉米视频成人免费看 | 日韩黄色免费观看 | 人妻无码αv中文字幕久久琪琪布 | 色接久久| 国产亚洲精品久久久久蜜臀 | 欧美性折磨bdsm激情另类视频 | 国产精品嫩草影院入口日本一区二 | 日本极品丰满ⅹxxxhd | 思思99精品视频在线观看 | 免费在线成人网 | 欧美黄页 | 婷婷激情综合 | 中文在线字幕观 | www在线观看免费视频 | 色噜噜狠狠狠综合曰曰曰 | 天堂8资源8地址8 | 国产免费一级特黄录像 | 国产极品美女高潮无套嗷嗷叫酒店 | 亚洲精品欧美综合四区 | 黑人操bb | 免费观看毛片视频 | 日韩一级特黄 | 久久久久久久无码高潮 | 136导航fldh福利视频微拍 | 亚州中文字幕蜜桃视频 | yyyy11111少妇无码影院 | 国产精品传媒麻豆 | 老牛嫩草一区二区三区消防 | 深夜啪啪 | 18岁日韩内射颜射午夜久久成人 | 久久精品手机观看 | 92看片淫黄大片看国产片 | 色欧美与xxxxx | 妺妺窝人体色www聚色窝仙踪 | 国产女人18毛片水18精 | 国产91传媒 | 青草青草久热国产精品 | 祝英台艳史高h(np)小说全文 | 亚洲欧美色图视频 | 久久精品国产色蜜蜜麻豆 | 黄色性视频网站 | 国产精品一在线观看 | 韩国三级做爰视频 | 一本色道久久综合亚洲精品高清 | 亚洲国产成人无码av在线播放 | 日韩av影院在线观看 | 日韩三级理论 | 少妇高潮喷潮久久久影院 | 99re在线视频观看 | 毛片无码免费无码播放 | 人妻 色综合网站 | 最新亚洲人成无码网站 | 欧美 日韩 人妻 高清 中文 | 人妻有码中文字幕在线 | 一进一出一爽又粗又大 | 少妇av一区二区 | 欧美丰满熟妇xxxx性大屁股 | 老湿福利影院 | 免费无码鲁丝片一区二区 | 亚洲美女视频网站 | 日本欧美国产在线 | 在线看片网站 | 免费看黄色三级 | 亚洲蜜桃精久久久久久久久久久久 | av淘宝国产在线观看 | 性欧美牲交在线视频 | 精品国产乱码久久久久久88av | 欧美a在线看 | 噜噜噜久久亚洲精品国产品91 | 亚洲少妇第一页 | 天堂久久精品 | 手机永久免费av在线播放 | 欧美一级艳片视频免费观看 | 五月婷婷六月天 | 国内成人自拍视频 | 噼里啪啦国语影视 | 成人欧美一区二区三区 | 日韩av少妇 | 色综合天天色 | 91福利社在线观看 | 双性精跪趴灌满h室友4p视频 | 一二级毛片 | 亚洲成a人片在线观看久 | 毛片网站入口 | 日韩欧美国产综合 | 国产精品免费麻豆入口 | 丁香婷婷综合激情五月色 | 欧美三级一区二区三区 | 噜噜噜视频在线观看 | xxx一区二区 | 亚洲 小说区 图片区 都市 | 韩国三级丰满少妇高潮 | 无码国产精品一区二区免费i6 | 亚洲精品怡红院 | 秋霞一级视频 | 亚洲人成人无码网www国产 | 激情无码人妻又粗又大 | 国产女人与zoxxxx另类 | 久久成人久久爱 | 韩国一级黄色毛片 | 九色porny丨首页在线 | 免费看国产精品 | 国产极品探花一区二区三区 | 日批网址 | 粉嫩av一区二区三区免费看 | 自拍偷拍第3页 | 中文字幕日韩精 | 亚洲做受高潮软件 | 国产偷国产偷精品高清尤物 | 久久99精品久久久久久青青日本 | 亚洲黄色小视频在线观看 | 国产精品久久久久久久久久辛辛 | 未满十八18禁止免费无码网站 | 久久久久久久久久网 | 区一区二视频 | 亚洲中文字幕无码久久精品1 | 久国久产久精永久网页 | 99草草国产熟女视频在线 | 欧美不卡一区二区 | 久久精品国产一区二区三区不卡 | www.国产二区 | 成人性生交大全免费中文版 | 国产精品一区二区三区四 | 偷拍一女多男做爰免费视频 | 国产裸体无遮挡免费精品视频 | 免费观看性欧美大片无片 | 国产在线不卡一区 | 欧美精品一区二区三区制服首页 | 91久久国产成人精品 | 青青视频在线播放 | 色播视频在线 | 国产精品卡一卡二卡三 | 7777奇米四色眼影国产馆 | 天天噜噜噜在线视频 | 久操资源网 | 蜜桃视频在线观看免费视频网站www | 亚洲精品国产精品乱码不66 | 日韩欧美一区二区在线观看 | 国产亚洲精品久久久久久无几年桃 | 九九在线视频免费观看精彩 | 天天做天天爱天天综合网2021 | 日韩精品自拍偷拍 | 国产一区二区三区a | 超碰最新在线 | 北条麻妃精品久久中文字幕 | 亚洲成人aaa | 欧美日韩一区二区三区69堂 | 极品销魂美女少妇尤物 | 欧美性xxxxx极品少妇 | 国产精品一级在线 | 黄污视频在线播放 | 中文字幕在线观看网址 | 精品福利视频一区二区三区 | 欧美久久一区二区 | 国产免费乱淫av | 午夜插插插 | 国产高清免费av | 国产精品视频在线免费观看 | 欧美、另类亚洲日本一区二区 | 91 高清 在线 制服 偷拍 | 黄色网免费| 国产av永久无码天堂影院 | 国产av天堂无码一区二区三区 | 99久久无色码中文字幕人妻蜜柚 | 五月婷婷开心网 | 久久久噜噜噜 | 9l视频自拍九色9l视频成人 | 精品久久免费视频 | 久操热线 | jazzjazz国产精品久久 | 91网站最新地址 | 国产激情a | 日日干日日插 | www.青青草| 午夜福利一区二区三区在线观看 | 亚洲精品二区 | 亚洲国产精品综合久久网各 | 国产国拍亚洲精品av | 日韩精品无码不卡无码 | 亚洲综合久久一区二区 | 图书馆的女友在线观看 | 久久国产精品-国产精品 | 五月天堂婷婷 | 色偷偷91 | 国产cao| 日本三级黄色大片 | 久久精品一区二区三区不卡牛牛 | 国产一级大片在线观看 | 91午夜精品 | 女的被弄到高潮娇喘喷水视频 | www超碰久久com | 国产网站大全 | 亚洲男人的天堂av手机在线观看 | av手机网站 | 天堂欧美 | 国产黄色大片视频 | 国产三级网站 | 黑人爱爱视频 | 亚洲69| 亚洲日韩av无码一区二区三区人 | 天堂中文8 | 色欲久久久天天天综合网精品 | 男女啪啪永久免费观看网站 | 欧美成年人在线观看 | 国内精品久久久久久久久久久久 | 国产精品怕怕怕免费视频 | 狠狠色狠狠色综合人人 | 天天艹天天操 | gogo人体做爰aaaa | 日韩专区一区 | 亚洲欧美在线精品 | 日韩精品人妻系列无码专区 | 久久久久久亚洲国产精品 | 亚洲午夜网站 | 超碰男人的天堂 | 伊人网视频在线观看 | 国产精品日本欧美一区二区三区 | 久久久综合久久 | 希岛爱理和黑人中文字幕系列 | 免费成人黄色网址 | 正在播放酒店约少妇高潮 | 国产无套内射久久久国产 | 看黄色毛片 | 中国做受xxxxxaaaa | 久久久精 | av日韩av| 成人性生活大片免费看ⅰ软件 | 一级做性色α爱片久久毛片色 | 国产性生大片免费观看性 | 欧美色一区二区三区在线观看 | 国产精品三级视频 | 尤物视频在线免费观看 | 天堂网中文在线观看 | 亚洲精品久久久久久宅男 | 亚洲xxxxx| 久久久久久人妻一区二区三区 | 日少妇av| 在线观看福利网站 | 欧美大片www| 国产成人a亚洲精品 | 97久久精品人人澡人人爽古装 | 嫩草影院久久 | 日韩性xxxx | 在线地址一地址二免费看 | 在线播放免费播放av片 | 成人欧美一区二区三区黑人孕妇 | 国产精品丝袜久久久久久不卡 | 久久亚洲一区二区三区四区五区 | 三级国产三级在线 | 午夜影院视频 | 亚洲欧美日韩一区二区 | 国内露脸少妇精品视频 | 亚洲欧美系列 | 久久天堂av综合合色蜜桃网 | 激情久久久久久 | 日韩在线视频中文字幕 | 国产精品国产三级国产普通 | 人与嘼av免费 | 国产成人精品一区 | 国产精品99久久久久久久 | 国产一级二级在线观看 | 我看黄色一级片 | 少妇高潮惨叫久久久久久 |