Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<div id="app">
<!--簡(jiǎn)單表達(dá)式 [類(lèi)型一樣直接加]=25-->
<h1>{{5+5}}</h1>
<!-- +:運(yùn)算,字符串連接 【類(lèi)型不一樣就是拼接】=5v5,55-->
<h1>{{5+"v5"}}</h1>
<h1>{{5+"5"}}</h1>
<!-- -:減法 "5"-"5" 兩個(gè)雙引號(hào) 自動(dòng)解析【類(lèi)型一樣直接算】=0,25-->
<h1>{{"5"-"5"}}</h1>
<h1>{{5*5}}</h1>
<!-- *:乘 【一樣類(lèi)型一樣直接乘】=25-->
<h1>{{"5"*"5"}}</h1>
<!-- / 除 【不說(shuō)了一樣】=1,1-->
<h1>{{5/5}}</h1>
<h1>{{"5"/"5"}}</h1>
</div>
</body>
<script>
var app=new Vue({
el:"#app"//掛載到id
});
</script>
<script src="../node_modules/vue/dist/vue.min.js"></script>
<body>
<div class="app">
{{show?"GG":"MM"}}
</div>
</body>
<script>
var app=new Vue({
el:".app",
data:{
show:true//true就是MM,false就是GG
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
{{message}}<br>
<!--長(zhǎng)度-->
{{message.length}}<br>
<!--截取根據(jù)下標(biāo)-->
{{message.substring(0,3)}}
<!--根據(jù)下標(biāo)從哪里開(kāi)始 【3456】-->
{{message.substring(2).toUpperCase()}}<br>
<!--獲取到下標(biāo) 【3】-->
{{message.charAt(2)}}
</div>
</body>
<script>
var app=new Vue({
el:"#app",
data:{
message:"123456"
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--123456-->
{{message}}<br>
<!--{ "name": "華雄", "age": 69 }重寫(xiě)toString,就變了-->
{{user}}<br>
<!--華雄-->
{{user.name}}<br>
<!--getName(){return this.name}-->
{{user.getName}}<br>
<!--toString(){return this.name}-->
{{user.toString}}<br>
<!--{"name":"華雄","age":69} -->
{{JSON.stringify(user)}}
<!--22 json轉(zhuǎn)成字符串了-->
{{JSON.stringify(user).length}}
</div>
</body>
<script>
var sss={
name:"華雄",
age:69,
getName(){return this.name},//{ "name": "華雄", "age": 69 }
//原toString---function toString() { [native code] }
toString(){return this.name}//重寫(xiě)toString,這樣獲取到就是華雄
}
var app=new Vue({
el:"#app",
data:{
message:"123456",
user:sss
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
{{woman}}<br>
{{woman[0]}}<br>
{{woman.length}}<br>
{{woman.toString()}}<br>
{{woman.join(" + ")}}
</div>
</body>
<script>
var app=new Vue({
el:"#app",
data:{
woman:["黃月英","蔡文姬","孫尚香","甄宓"]
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--純文本 是什么樣就展示什么-->
<span v-text="msg"></span><br>
<!--解析標(biāo)簽 會(huì)自動(dòng)解析標(biāo)簽-->
<span v-html="msg"></span>
</div>
</body>
<script>
new Vue({
el:"#app",
data:{
msg:"<h3>你好!中國(guó)</h3>"
}
})
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--就是直接循環(huán)-->
<ul>
<li v-for="a in woman">{{a}}</li>
</ul>
<!--循環(huán)a和下標(biāo)index-->
<ul>
<li v-for="(a,index) in woman">{{a}}---{{index}}</li>
</ul>
<!--搞一個(gè)表-->
<table border="1px black">
<!--表頭-->
<tr>
<th>名字</th>
<th>年齡</th>
</tr>
<!--循環(huán)里面的東西-->
<tr v-for="key in users">
<!--
aa in key aa:value值
aa,bb in key aa:value值 bb:屬性名
aa,bb,index,index aa:value值 bb:屬性名 index:下標(biāo)
-->
<td v-for="(aa,bb,index) in key">
{{aa}}----{{bb}}---{{index+1}}
</td>
</tr>
</table>
</div>
</body>
<script>
new Vue({
el:"#app",
data:{
woman:["黃月英","蔡文姬","孫尚香","甄宓"],
<!--List<user>-->
users:[{
name:"張三",
age:10
},{
name:"李四",
age:20
}]
}
})
</script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<!--原版以前這么寫(xiě)-->
<img src="123456.JPG" title="">
<!--新版 可以實(shí)現(xiàn)綁定,這樣就能寫(xiě)活了-->
<img v-bind:src="src" v-bind:title="sss">
<!--title就是鼠標(biāo)提示-->
<img :src="src" v-bind:title="sss">
</div>
</body>
<script>
new Vue({
el:"#app",
data:{
//下面的值現(xiàn)在是寫(xiě)死,以后從后臺(tái)獲取。
src:"123456.JPG",
sss:"手放哪呢?"
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<!--這個(gè)都是一些雙向綁定的案例,不好解釋?zhuān)闊┝耍⌒枰臅r(shí)候代碼考過(guò)去自己一看就明白了了-->
<body>
<div id="app">
<h3>綁定到type=text的input表單元素</h3>
姓名:<input type="text" v-model="inputValue"><br/>
data中的值:{{inputValue}}
<h3>綁定到type=checkbox的input表單元素</h3>
<!--v-model="checkboxValue" checkboxValue數(shù)組包含了當(dāng)前value值 就會(huì)默認(rèn)選中-->
打籃球:<input type="checkbox" v-model="checkboxValue" value="打籃球"><br/>
踢足球:<input type="checkbox" v-model="checkboxValue" value="踢足球"><br/>
data中的值:{{checkboxValue}}
<h3>綁定到type=radio的input表單元素</h3>
男:<input type="radio" v-model="radioValue" value="男"><br/>
女:<input type="radio" v-model="radioValue" value="女"><br/>
data中的值:{{radioValue}}
<h3>綁定到textarea的元素</h3>
個(gè)人簡(jiǎn)介:<textarea v-model="textareaValue"></textarea><br/>
data中的值:{{textareaValue}}
<h3>綁定到單選的select的元素</h3>
技能:<select v-model="skills">
<option value="java">java</option>
<option value="php">php</option>
<option value=".net">.net</option>
</select><br/>
data中的值:{{skills}}
</div>
</body>
<script>
var vue=new Vue({
el:"#app",
data:{
inputValue:"輸入框的值",
checkboxValue:["打籃球"],
radioValue:"女",
textareaValue:"文本域的值",
skills:"php"
}
});
</script>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../node_modules/vue/dist/vue.min.js"></script>
</head>
<body>
<div id="app">
<span v-show="show">顯示</span><br>
<span v-show="hidden">不顯示</span><br>
<span v-show="score<60">小于60分顯示</span><br>
<span v-show="score>60">大于60分顯示</span>
</div>
</body>
<script>
var app=new Vue({
el:"#app",
data:{
show:true,
hidden:false,
score:59
}
});
</script>
</html>
內(nèi)容是《Web前端開(kāi)發(fā)之Javascript視頻》的課件,請(qǐng)配合大師哥《Javascript》視頻課程學(xué)習(xí)。
為了便于操作基本類(lèi)型值,ECMAScript還提供了3個(gè)特殊的引用類(lèi)型:Boolean、Number和String;這些類(lèi)型與以上所說(shuō)的引用類(lèi)型相似,但同時(shí)也具有與各自的基本類(lèi)型相應(yīng)的特殊行為;實(shí)際上,每當(dāng)讀取一個(gè)基本類(lèi)型值的時(shí)候,后臺(tái)就會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的基本包裝類(lèi)型的對(duì)象,從而讓我們能夠用一些方法來(lái)操作這些數(shù)據(jù),如:
var s1="zero network";
var s2=s1.substring(4);
alert(s2);
基本數(shù)據(jù)類(lèi)型不是對(duì)象,所以從邏輯上它們不應(yīng)該有方法(實(shí)際上有)(原理:為了能夠?qū)崿F(xiàn)這種直觀的操作,后臺(tái)已經(jīng)自動(dòng)完成了一系列的處理:創(chuàng)建String對(duì)象實(shí)例,同樣適用于Boolean和Number類(lèi)型對(duì)應(yīng)的布爾值和數(shù)字值。
基本數(shù)據(jù)類(lèi)型與基本包裝類(lèi)型有本質(zhì)上的不同,產(chǎn)生從類(lèi)型的角度來(lái)講,它們就是不同的;其保存的位置也不同。
引用類(lèi)型與基本包裝類(lèi)型的區(qū)別就是對(duì)象的生存期:使用new操作符創(chuàng)建的引用類(lèi)型對(duì)象,在執(zhí)行流離開(kāi)當(dāng)前作用域之前都一直保存在內(nèi)存中,而自動(dòng)創(chuàng)建的基本包裝類(lèi)型的對(duì)象,則只存在于一行代碼的執(zhí)行瞬間,然后立即被銷(xiāo)毀;這意味著不能在運(yùn)行時(shí)為基本類(lèi)型值添加屬性和方法,如:
var s1="zero network";
s1.color="red";
alert(s1.color); // undefined
當(dāng)然可以使用Boolean, Number和String顯示的來(lái)創(chuàng)建基本包裝類(lèi)型的對(duì)象,但絕大部分情況下不需要,除非在絕對(duì)必要的情況下,因?yàn)檫@種做法很容易讓開(kāi)發(fā)者分不清是在處理基本類(lèi)型還是引用類(lèi)型的值;
對(duì)基本包裝類(lèi)型的實(shí)例調(diào)用typeof會(huì)返回object,而且所有基本包裝類(lèi)型的對(duì)象在轉(zhuǎn)換為布爾類(lèi)型時(shí)值都是true;如:
var s1=new Number(0);
// var s1=0;
alert(typeof s1);
alert(Boolean(s1));
Object構(gòu)造函數(shù),僅接受一個(gè)參數(shù),其會(huì)根據(jù)傳入值的類(lèi)型返回相應(yīng)基本包裝類(lèi)型的實(shí)例,如:
var obj=new Object("zeronetwork");
alert(obj instanceof String); // true
var n=new Object(5);
alert(n instanceof Number); // true
console.log(n.constructor);
說(shuō)明:傳遞的值是動(dòng)態(tài)的,直到運(yùn)行時(shí)才確定其類(lèi)型,Object()可能會(huì)調(diào)用另一個(gè)內(nèi)置函數(shù)來(lái)創(chuàng)建對(duì)象,并且返回了一個(gè)以不同構(gòu)造函數(shù)所創(chuàng)建的對(duì)象。
ES在必要的時(shí)會(huì)將包裝對(duì)象轉(zhuǎn)換成原始值,但不總是這樣;如:
var s1="zeronetwork";
var s2=new String("zeronetwork");
console.log(s1==s2); // true
console.log(s1===s2); // false
var n1=18;
var n2=new Number(18);
console.log(n1==n2); // true
console.log(n1===n2); // false
說(shuō)明:“==”運(yùn)算符會(huì)將原始值與包裝對(duì)象視為相等,但“===”全等會(huì)將它們視為不等,因?yàn)樗鼈兊念?lèi)型不同,可以使用typeof查看。
注意:使用new調(diào)用基本包裝類(lèi)型的構(gòu)造函數(shù),與直接調(diào)用同名的轉(zhuǎn)型函數(shù)是不一樣的,如:
var value="25";
var number=Number(value); // 轉(zhuǎn)型函數(shù)
alert(typeof number); // number
var obj=new Number(value); // 構(gòu)造函數(shù)
alert(typeof obj); // object
注:null與undefined沒(méi)有包裝對(duì)象,訪問(wèn)它們的屬性會(huì)造成一個(gè)類(lèi)型錯(cuò)誤。
盡管在實(shí)際場(chǎng)景中不太使用基本包裝類(lèi)型,并且也不建議使用,但它們操作基本類(lèi)型值的能力還是相當(dāng)重要的,而每個(gè)基本包裝類(lèi)型都提供了操作相應(yīng)值的便捷方法。
Boolean類(lèi)型:
Boolean類(lèi)型是與布爾值對(duì)應(yīng)的引用類(lèi)型,Boolean 對(duì)象表示兩個(gè)值:"true" 或 "false"。
創(chuàng)建Boolean對(duì)象:var oBool=new Boolean(true); // 傳入true或false值
注:如果邏輯對(duì)象無(wú)初始值或者其值為 0、-0、null、""、false、undefined 或者 NaN,那么對(duì)象的值為 false,否則,其值為 true;
在實(shí)際場(chǎng)景中,Boolean對(duì)象的用處不大,因?yàn)榻?jīng)常會(huì)造成一些誤解,其中最常見(jiàn)的問(wèn)題就是在布爾表達(dá)式中使用Boolean對(duì)象,如:
var bObj=new Boolean(false);
alert(bObj && true); // true
var bValue=false;
alert(bValue && true); // false
基本類(lèi)型與引用類(lèi)型還有兩個(gè)區(qū)別,首先,typeof操作符對(duì)基本類(lèi)型返回boolean,而對(duì)引用類(lèi)型返回object;其次,由于Boolean類(lèi)型的實(shí)例,所以使用instanceof操作符測(cè)試Boolean對(duì)象會(huì)返回true,而測(cè)試基本類(lèi)型的布爾值則返回false,如:
var falseObject=new Boolean(false);
var falseValue=false;
alert(typeof falseObject); // object
alert(typeof falseValue); // boolean
alert(falseObject instanceof Boolean); // true
alert(falseValue instanceof Boolean); // false
總結(jié):理解基本類(lèi)型的布爾值與Boolean對(duì)象之間的區(qū)別非常重要,建議不要使用,最好使用Boolean原始值;
可以使用Boolean(參數(shù))進(jìn)行數(shù)據(jù)類(lèi)型轉(zhuǎn)換;
Number對(duì)象:
Number 對(duì)象,是原始數(shù)值的包裝對(duì)象。在必要時(shí),JavaScript 會(huì)自動(dòng)地在原始數(shù)據(jù)和對(duì)象之間轉(zhuǎn)換;
可以用構(gòu)造函數(shù) Number() 明確地創(chuàng)建一個(gè) Number 對(duì)象:
var numberObject=new Number(10);
Number對(duì)象屬性:
console.log(Number.MAX_VALUE);
console.log(Number.MIN_VALUE);
var x=-Number.MAX_VALUE * 2;
if(x==Number.NEGATIVE_INFINITY)
console.log("X的值:" + x);
var y=Number.MAX_VALUE * 2;
if(y==Number.POSITIVE_INFINITY)
console.log("Y的值:" + y);
注: Number.NaN 是一個(gè)特殊值,說(shuō)明某些算術(shù)運(yùn)算(如求負(fù)數(shù)的平方根)的結(jié)果不是數(shù)字。方法 parseInt() 和 parseFloat() 在不能解析指定的字符串時(shí)就返回這個(gè)值。
Number類(lèi)或?qū)ο蠓椒ǎ?/p>
var number=new Number(1337);
console.log(number.toString(8));
var num=new Number(12.345678);
console.log(num.toFixed(2)); // 12.35
var num=1200.00;
console.log(num.toExponential(2));
var num=99;
console.log(num.toPrecision(1)); // 1e+2
console.log(num.toPrecision(2)); // 99
console.log(num.toPrecision(3)); // 99.0
num=10000;
console.log(num.toPrecision(1)); // 1e+4
console.log(num.toPrecision(4)); // 1.000e+4
注意:與Boolean對(duì)象類(lèi)似,Number對(duì)象也以后臺(tái)方式為數(shù)值提供了重要的功能;不建議直接實(shí)例化Number類(lèi)型,原因與Boolean一樣;一般情況下是使用數(shù)字的原始表示法;
var numberObject=new Number(10);
var numberValue=10;
alert(typeof numberObject); // object
alert(typeof numberValue); // number
alert(numberObject instanceof Number); // true
alert(numberValue instanceof Number); // false
String對(duì)象:
String類(lèi)型是字符串對(duì)象包裝類(lèi)型,用于處理文本(字符串)。語(yǔ)法:var str=new String(str); 如:
var oStr=new String();
var oStr=new String("零點(diǎn)程序員");
var oStr=String("525");
length屬性:返回字符串中的字符個(gè)數(shù) 如: str.length ;
注:即使字符串包含雙字節(jié)字符(不是占一個(gè)字節(jié)的ASCII字符),每個(gè)字符也仍然算一個(gè)字符;
String 類(lèi)定義了大量操作字符串的方法,例如從字符串中提取字符或子串,或者檢索字符或子串。
需要注意的是,JavaScript 的字符串是不可變的(immutable),String 類(lèi)定義的方法都不能改變字符串的內(nèi)容。像 String.toUpperCase() 這樣的方法,返回的是全新的字符串,而不是修改原始字符串。
1)字符方法:
用于訪問(wèn)字符串中特定字符的方法:charAt()和charCodeAt();這兩個(gè)方法都接收一個(gè)參數(shù),即基于0的字符位置;其中,charAt()方法以單字符字符串的形式返回給定位置的那個(gè)字符(ECMAScript中沒(méi)有字符類(lèi)型),如:
var str="zero network";
alert(str.charAt(2));
如果想得到字符編碼而不是字符時(shí),使用charCodeAt(),如:
var str="zero network";
alert(str.charCodeAt(2)); // 114
ECMAScript還定義了另一個(gè)訪問(wèn)字符的方法,即使用括號(hào)(類(lèi)似于訪問(wèn)數(shù)組元素)語(yǔ)法,使用數(shù)字索引來(lái)訪問(wèn)字符串中的特定字符,如:
var str="zero network";
alert(str[2]); // 2
2)字符串操作方法:
concat()方法:用于將一或多個(gè)字符串拼接起來(lái),返回拼接得到的新字符串,如:
var str = "zero";
var result = str.concat("network");
alert(str);
alert(result);
concat()方法可以接受多個(gè)任意參數(shù),即可以通過(guò)它可以拼接任意多個(gè)字符串,如:
var str = "zero";
var result = str.concat("network","!");
alert(result);
說(shuō)明:雖然concat()是專(zhuān)門(mén)用來(lái)拼接字符串的,但在實(shí)際場(chǎng)景中使用更多的還是加號(hào)操作符(+);而且,使用加號(hào)操作符在大多數(shù)情況下都比使用concat方法要簡(jiǎn)便易行,特別是在拼接多個(gè)字符串的情況下;
ECMAScript提供了三個(gè)基于字符串創(chuàng)建新字符串的方法:slice()、substr()和substring();這三個(gè)方法都會(huì)返回被操作字符串的一個(gè)子字符串,而且也都接受一或兩個(gè)參數(shù);第一個(gè)參數(shù)指定子字符串的開(kāi)始位置,第二個(gè)參數(shù)表示子字符串到哪里結(jié)束;具體來(lái)說(shuō),slice()和substring()的第二個(gè)參數(shù)指定的是子字符串最后一個(gè)字符后面的位置;而substr()的第二個(gè)參數(shù)指定的則是返回的字符個(gè)數(shù);如果沒(méi)有給這些方法傳遞第二個(gè)參數(shù),則將字符串的末尾作為結(jié)束位置,如:
var str = "zero network";
alert(str.slice(3));
alert(str.substring(3));
alert(str.substr(3));
alert(str.slice(3,6));
alert(str.substring(3,6));
alert(str.substr(3,6));
同時(shí)這些方法的值可以是負(fù)值;其中,slice()會(huì)將傳入的負(fù)值與字符串的長(zhǎng)度相加,substr()將負(fù)的第一個(gè)參數(shù)加上字符串的長(zhǎng)度,而將負(fù)數(shù)的第二個(gè)參數(shù)轉(zhuǎn)換為0,substring()方法會(huì)把所有負(fù)值參數(shù)都轉(zhuǎn)換為0,如:
var str = "zero network";
alert(str.slice(-3));
alert(str.substring(-3));
alert(str.substr(-3));
alert(str.slice(3,-4));
alert(str.substring(3,-4));
alert(str.substr(3,-4));
一個(gè)小示例:
<style>
#mydiv{width: 100px; height: 1.5em; border: 1px solid; overflow: hidden;}
</style>
<div id="mydiv"></div>
<script>
var msg="北京零點(diǎn)網(wǎng)絡(luò)科技有限公司";
var spacer="...";
var pos=0;
var mydiv = document.getElementById("mydiv");
function ScrollMsg(){
mydiv.innerHTML = msg.substring(pos,msg.length) + spacer + msg.substring(0, pos);
pos++;
if(pos>msg.length) pos=0;
window.setTimeout("ScrollMsg()",200);
}
ScrollMsg();
</script>
3)字符串位置方法:
有兩個(gè)可以從字符串中查找子字符串的方法:indexOf()和lastIndexOf();這兩個(gè)方法都是從一個(gè)字符串中搜索給定的子字符串,然后返回子字符串的位置,如果沒(méi)有找到子字符串,則返回-1;兩個(gè)方法的區(qū)別在于,一個(gè)從開(kāi)頭向后搜索子字符串,而lastIndexOf是從末尾向前搜索,如:
var str = "zero network";
alert(str.indexOf("o"));
alert(str.lastIndexOf("o"));
說(shuō)明:如果o在字符串只出現(xiàn)了一次,則兩個(gè)方法會(huì)返回相同的位置值;
這兩個(gè)方法都可以接受可選的第二個(gè)參數(shù),表示從字符串中的哪個(gè)位置開(kāi)始搜索;如:
var str = "zero network";
alert(str.indexOf("o",6));
alert(str.lastIndexOf("o",6));
在使用第二個(gè)參數(shù)的情況下,可以循環(huán)調(diào)用這兩個(gè)方法來(lái)找到所有匹配的子字符串,如:
var str = "lorem ipsum dolor sit amet, consectetur adipisicing elit";
var positions = new Array();
var pos = str.indexOf("e");
while(pos>-1){
positions.push(pos);
pos = str.indexOf("e",pos + 1);
}
alert(positions); // 3,24,32,35,52
4)trim()方法:
ECMAScript5為所有字符串定義了trim()方法;該方法會(huì)創(chuàng)建一個(gè)字符串的副本,刪除前置及后綴的所有空格,然后返回結(jié)果,如:
var str = " zero network ";
var trimStr = str.trim();
alert(str);
alert(trimStr);
此外,還有兩個(gè)非標(biāo)準(zhǔn)的trimLeft()和trimRight()方法,分別用于刪除字符串開(kāi)頭和末尾的空格;
var trimStr = str.trimLeft();
var trimStr = str.trimRight();
5)字符串大小寫(xiě)轉(zhuǎn)換方法:
涉及字符串大小寫(xiě)轉(zhuǎn)換的方法有4個(gè):
其中toLowerCase()和toUpperCase()是最常用的方法;而toLocaleLowerCase()和toLocaleUpperCase()則是針對(duì)特定地區(qū)的實(shí)現(xiàn),對(duì)有些地區(qū)來(lái)說(shuō),針對(duì)地區(qū)的方法與其通用方法得到的結(jié)果相同,但少數(shù)語(yǔ)言(如土耳其語(yǔ))會(huì)為Unicode大小寫(xiě)轉(zhuǎn)換應(yīng)用特殊的規(guī)則,這時(shí)候就必須使用針對(duì)地區(qū)的方法來(lái)保證實(shí)現(xiàn)正確的轉(zhuǎn)換,如:
var str = "ZERO network";
alert(str.toLocaleLowerCase());
alert(str.toLocaleUpperCase());
alert(str.toLowerCase());
alert(str.toUpperCase());
6)字符串的模式匹配方法:
String類(lèi)型定義了幾個(gè)用于在字符串中匹配模式的方法;
match()方法:本質(zhì)上與RegExp的exec()方法相同;其只接受一個(gè)參數(shù),要么是一個(gè)正則表達(dá)式,要么是一個(gè)RegExp對(duì)象,如:
var text = "cat, bat, sat, fat";
var pattern = /.at/;
var matches = text.match(pattern);
alert(matches);
alert(matches.index);
alert(matches[0]);
alert(pattern.lastIndex);
說(shuō)明:match()返回了一個(gè)數(shù)組,其第一項(xiàng)是與整個(gè)模式匹配的字符串,之后的每一項(xiàng)(如果有)保存著與正則表達(dá)式中的捕獲組匹配的字符串;
search()方法:接受的參數(shù)與match()一樣;該方法返回字符串中第一個(gè)匹配項(xiàng)的索引;如果沒(méi)有找到匹配項(xiàng),則返回-1;如:
var text = "cat, bat, sat, fat";
var pos = text.search(/at/);
alert(pos); // 1
replace()方法:此方法的目的是為了簡(jiǎn)化替換子字符串的操作;該方法接受兩個(gè)參數(shù),第一個(gè)參數(shù)可以是一個(gè)RegExp對(duì)象或一個(gè)字符串(這個(gè)字符串不會(huì)被轉(zhuǎn)換成正則表達(dá)式),第二個(gè)參數(shù)可以是一個(gè)字符串或一個(gè)函數(shù);如果第一個(gè)參數(shù)是字符串,那么只會(huì)替換第一個(gè)子字符串,要想替換所有子字符串,唯一的辦法就是提供一個(gè)正則表達(dá)式,而且要指定全局g標(biāo)志,如:
var text = "cat, bat, sat, fat";
var result = text.replace("at","ond");
alert(result); // cond, bat, sat, fat
result = text.replace(/at/g,"ond");
alert(result); // cond, bond, sond, fond
split()方法:
可以基于指定的分隔符將一個(gè)字符串分割成多個(gè)子字符串,并將結(jié)果放在一個(gè)數(shù)組中;分隔符可以是字符串,也可以是RegExp對(duì)象;其可以接受可選的第二個(gè)參數(shù),用于指定數(shù)組的大小,以便確保返回的數(shù)組不會(huì)超過(guò)既定大小,如:
var colorText = "red,blue,green,yellow";
var colors1 = colorText.split(",");
var colors2 = colorText.split(",",2);
var colors3 = colorText.split(/[^\,]+/);
alert(colors1); // red,blue,green,yellow
alert(colors2); // red,blue
alert(colors3); // ,,,,,,,
7)localeCompare()方法:
localeCompare()用本地特定的順序來(lái)比較兩個(gè)字符串,默認(rèn)返回下列值中的一個(gè):-1、0、1,如:
var str = "yellow";
alert(str.localeCompare("brick")); // 1
alert(str.localeCompare("yellow")); // 0
alert(str.localeCompare("zoo")); // -1
注:利用localeCompare()可以自定義返回的值,如:
function determineOrder(value){
var result = stringValue.localeCompare(value);
if(result < 0){
alert("yellow在'"+value+"'之前");
}else if(result > 0){
alert("yellow在'"+value+"'之后");
}else{
alert("yellow與'"+value+"'相等");
}
}
determineOrder("brick");
determineOrder("yellow");
determineOrder("zoo");
localeCompare()方法比較與眾不同的地方,就是實(shí)現(xiàn)所支持的地區(qū)(國(guó)家和語(yǔ)言)決定了這個(gè)方法的行為;如,美國(guó)以英語(yǔ)作為ECMAScript實(shí)現(xiàn)的標(biāo)準(zhǔn)語(yǔ)言,因此localeCompare()就是區(qū)分大小寫(xiě)的,于是大寫(xiě)字母在字母表中排在小寫(xiě)字母前頭就成為了一項(xiàng)決定性的比較規(guī)則;在其他地區(qū)有可能就不是這種情況了。
8)fromCharCode()方法:
String構(gòu)造函數(shù)本身還有一個(gè)靜態(tài)方法:fromCharCode(),這個(gè)方法的任務(wù)是接受一或多個(gè)字符編碼,然后將它們轉(zhuǎn)換成一個(gè)字符串;從本質(zhì)上看,這個(gè)方法與實(shí)例方法charCodeAt()執(zhí)行的是相反的操作,如:
alert(String.fromCharCode(104,101,108,108,111)); // hello
9)HTML方法:
早期的Web瀏覽器可以使用Javascript動(dòng)態(tài)格式化HTML,其擴(kuò)展了字符串的標(biāo)準(zhǔn),實(shí)現(xiàn)了一些專(zhuān)門(mén)用于簡(jiǎn)化常見(jiàn)HTML格式化任務(wù)的方法;但是,盡量不要使用這些方法,因?yàn)樗鼈儎?chuàng)建的標(biāo)記通常無(wú)法表達(dá)語(yǔ)義;
示例:檢測(cè)上傳文件后綴名:
<input type="file" id="myFile"/>
<input type="button" value="上傳" onclick="upFile()" />
<script>
function upFile(){
var fileName = document.getElementById('myFile').value;
if(fileName){
// console.log(fileName);
var pos = fileName.lastIndexOf('.'); // 找到路徑中最后出現(xiàn)“.”的位置
// console.log(pos);
var suffix = fileName.substr(pos + 1); // 找到后綴名
// console.log(suffix);
var suffixArr = ['jpg','png','gif']; // 圖片格式數(shù)組
// console.log((suffixArr.indexOf(suffix)));
if(suffixArr.indexOf(suffix) >= 0){ // 后綴名與格式數(shù)組作比較
// 上傳圖片的操作處理
console.log("上傳成功");
}else{
console.log("圖片格式不正確");
return;
}
}
}
</script>
示例:過(guò)濾臟話:
// 簡(jiǎn)單過(guò)濾
var str = "不要相信女人,女人太壞了";
var arr = ["壞","笨","傻"]; // 敏感字
for(var i = 0; i<arr.length; i++){
str = str.replace(arr[i],"*");
}
console.log(str);
// 復(fù)雜的過(guò)濾
var arrStr = [
"傻子,你的腦子是不是有病?你就是傻子一個(gè)!",
"你的眼睛是不是瞎了?",
"你是一個(gè)大壞蛋!"
];
var arr = ["病","傻子","瞎了","壞蛋"];
for(var i =0;i<arrStr.length; i++){
for(var j=0;j<arr.length; j++){
arrStr[i] = arrStr[i].replace(arr[j],"**");
// var reg = new RegExp(arr[j],"img");
// arrStr[i] = arrStr[i].replace(reg,"**");
}
}
console.log(arrStr);
Web前端開(kāi)發(fā)之Javascript-零點(diǎn)程序員-王唯
文源代碼獲取:文末
啟動(dòng)一個(gè)新項(xiàng)目并從頭開(kāi)始處理身份驗(yàn)證和授權(quán)可能會(huì)筋疲力盡。它通常涉及創(chuàng)建登錄和注冊(cè)功能,這可能很耗時(shí)。管理刷新令牌和實(shí)現(xiàn)雙因素身份驗(yàn)證等挑戰(zhàn)增加了復(fù)雜性。
值得慶幸的是,隨著 .NET 8 的到來(lái),這些任務(wù)大大簡(jiǎn)化,只需要最少的配置。
在本文中,我將指導(dǎo)你完成各種標(biāo)識(shí)配置方案,包括:
基本注冊(cè)和登錄。
使用持有者令牌保護(hù)終結(jié)點(diǎn)。
檢索用戶(hù)信息。
電子郵件確認(rèn)。
重新發(fā)送電子郵件確認(rèn)。
更改默認(rèn)設(shè)置并添加自定義用戶(hù)屬性。
配置雙因素身份驗(yàn)證 (MVC)。
先決條件:.NET 8 或更高版本。
在繼續(xù)之前,讓我概述一下我當(dāng)前的環(huán)境設(shè)置:
.NET Web API 應(yīng)用程序。
Visual Studio 2022 年。
利用SQLite模擬真實(shí)數(shù)據(jù)庫(kù),而不是內(nèi)存數(shù)據(jù)庫(kù)。
接下來(lái),讓我們安裝必要的 NuGet 包。
dotnet add package Microsoft.AspNetCore.Identity.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Tools
首先,首先創(chuàng)建所有身份生成的表的存儲(chǔ)位置。
// IdentityUser is the Microsoft base identity class.
// creates empty scheme with just all the identity tables.
class AppDbContext : IdentityDbContext<IdentityUser>
{
public AppDbContext(DbContextOptions options) : base(options)
{
}
}
接下來(lái),我們將注冊(cè)身份驗(yàn)證和授權(quán)中間件,從而靈活地在持有者令牌或 Cookie 之間進(jìn)行選擇。在此示例中,我們將選擇持有者令牌,以便于在登錄時(shí)檢索持有者令牌。
// Program.cs
builder.Services.AddAuthentication()
.AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddAuthorizationBuilder();
之后,我們將注冊(cè)我們的 ,在此示例中,它是一個(gè) SQLite 數(shù)據(jù)庫(kù)。
// Program.cs
builder.Services.AddDbContext<AppDbContext>(options=>
{
options.UseSqlite("DataSource=app.db");
});
之后,我們需要將 作為身份存儲(chǔ)連接,以便所有操作都指向 SQLite 數(shù)據(jù)庫(kù)。
// Program.cs
builder.Services.AddIdentityCore\<IdentityUser>()
.AddEntityFrameworkStores\<AppDbContext>()
.AddApiEndpoints();
最后,我們需要將所有端點(diǎn)添加為應(yīng)用程序端點(diǎn)的一部分。這可以通過(guò)在構(gòu)建生成器后映射標(biāo)識(shí)終結(jié)點(diǎn)來(lái)實(shí)現(xiàn)。
app.MapIdentityApi<IdentityUser>();
您的Program.cs文件現(xiàn)在應(yīng)類(lèi)似于此結(jié)構(gòu)。
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
var builder=WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication()
.AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddAuthorizationBuilder();
builder.Services.AddDbContext<AppDbContext>(options=>
{
options.UseSqlite("DataSource=app.db");
});
builder.Services.AddIdentityCore<IdentityUser>()
.AddEntityFrameworkStores<AppDbContext>()
.AddApiEndpoints();
var app=builder.Build();
app.MapIdentityApi<IdentityUser>();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
在運(yùn)行應(yīng)用程序之前,請(qǐng)不要忘記添加新的遷移,并通過(guò)從包管理器控制臺(tái)執(zhí)行以下命令來(lái)更新數(shù)據(jù)庫(kù)。
Add-Migration initial
Update-Database
現(xiàn)在,運(yùn)行應(yīng)用程序并打開(kāi) Swagger。您應(yīng)該會(huì)看到自動(dòng)生成的終結(jié)點(diǎn)集合。
生成的身份終結(jié)點(diǎn)。
嘗試注冊(cè)新用戶(hù)。如果嘗試使用無(wú)效的電子郵件地址或不符合條件的密碼創(chuàng)建新用戶(hù),則標(biāo)識(shí)將返回錯(cuò)誤以及發(fā)生的具體詳細(xì)信息。
現(xiàn)在,讓我們繼續(xù)登錄過(guò)程。如果提供的電子郵件或密碼無(wú)效,則身份將返回 401 未授權(quán)狀態(tài)。
將身份驗(yàn)證配置為返回持有者令牌后,我們需要將此持有者令牌用于任何需要授權(quán)的受保護(hù)終結(jié)點(diǎn)。
必須注意的是,生成的令牌不是標(biāo)準(zhǔn)的 JSON Web 令牌 (JWT)。此決定是有意為之的,因?yàn)閮?nèi)置標(biāo)識(shí)主要用于簡(jiǎn)單方案。令牌選項(xiàng)并不意味著充當(dāng)功能齊全的身份服務(wù)提供商或令牌服務(wù)器,而是無(wú)法使用 cookie 的客戶(hù)端的 cookie 選項(xiàng)的替代方案。
現(xiàn)在,讓我們嘗試訪問(wèn)授權(quán)端點(diǎn),特別是 /manage/info 端點(diǎn)。如果您嘗試從 Swagger 訪問(wèn)它,它將顯示未經(jīng)授權(quán)的錯(cuò)誤。接下來(lái),使用 Postman 登錄,保存 accessToken,并在調(diào)用 /manage/info 端點(diǎn)時(shí)包含它。這一次,它應(yīng)該返回所需的結(jié)果。
要隨時(shí)檢索當(dāng)前登錄用戶(hù)的信息,請(qǐng)使用存儲(chǔ)當(dāng)前登錄用戶(hù)信息的類(lèi)。ClaimsPrincipal
要使用最小 API 方法實(shí)現(xiàn)此目的,請(qǐng)執(zhí)行以下操作:
app.Map("/", (ClaimsPrincipal user)=> $"Hello {user.Identity!.Name}")
.RequireAuthorization();
對(duì)于控制器方法:
[ApiController]
[Route("[controller]")]
public class TestController: ControllerBase
{
[HttpGet]
[Authorize]
public string Get()
{
return User.Identity!.Name;
}
}
請(qǐng)務(wù)必注意,上述示例使用“**!”**運(yùn)算符,這意味著標(biāo)識(shí)應(yīng)始終存在,而不可能為 。此假設(shè)成立,因?yàn)閮烧叨际切枰孪鹊卿浀氖跈?quán)端點(diǎn)。但是,建議驗(yàn)證標(biāo)識(shí)是否存在,以防止?jié)撛诘?引用異常。
Microsoft建議使用SendGrid或其他電子郵件服務(wù)發(fā)送電子郵件,而不是SMTP。SMTP 很難保護(hù)和正確設(shè)置。
在本教程中,SendGrid 用于發(fā)送電子郵件。發(fā)送電子郵件需要 SendGrid 帳戶(hù)和密鑰。請(qǐng)參閱免費(fèi)開(kāi)始使用 SendGrid,注冊(cè)免費(fèi)的 SendGrid 帳戶(hù)。
1- 在文件中配置 SendGrid 所需信息。避免將這些設(shè)置直接添加到appsettings.json文件中,以降低安全風(fēng)險(xiǎn)。secrets.json
{
"SendGridKey": "your send-grid key",
"From": "your registered email",
"Name": "your registered name"
}
注冊(cè)電子郵件和姓名
SendGrid API 密鑰
2- 安裝必要的 NuGet 包。
dotnet add package SendGrid
dotnet add package SendGrid.Extensions.DependencyInjection
3-實(shí)現(xiàn)IEmailSender
要實(shí)現(xiàn),請(qǐng)使用類(lèi)似于以下內(nèi)容的代碼進(jìn)行創(chuàng)建:IEmailSenderEmailSender.cs
using Microsoft.AspNetCore.Identity.UI.Services;
using SendGrid;
using SendGrid.Helpers.Mail;
namespace Identity;
public class EmailSender : IEmailSender
{
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
public EmailSender(IConfiguration configuration, ILogger<EmailSender> logger)
{
_configuration=configuration;
_logger=logger;
}
public async Task SendEmailAsync(string toEmail, string subject, string message)
{
var sendGridKey=_configuration["SendGridKey"];
ArgumentException.ThrowIfOrEmpty(sendGridKey, nameof(sendGridKey));
await Execute(sendGridKey, subject, message, toEmail);
}
public async Task Execute(string apiKey, string subject, string message, string toEmail)
{
var client=new SendGridClient(apiKey);
var msg=new SendGridMessage()
{
From=new EmailAddress(_configuration["From"], _configuration["Name"]),
Subject=subject,
PlainTextContent=message,
HtmlContent=message
};
msg.AddTo(new EmailAddress(toEmail));
// Disable click tracking.
// See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
msg.SetClickTracking(false, false);
var response=await client.SendEmailAsync(msg);
_logger.LogInformation(response.IsSuccessStatusCode
? $"Email to {toEmail} queued successfully!"
: $"Failure Email to {toEmail}");
}
}
4- 將 EmailService 注入其中以啟用其使用。Program.cs
// Program.cs
builder.Services.AddTransient<IEmailSender, EmailSender>();
5- 強(qiáng)制登錄確認(rèn)并集成 SendGrid 服務(wù)。
現(xiàn)在,如果您嘗試再次注冊(cè),它將起作用,但您會(huì)注意到?jīng)]有發(fā)送電子郵件確認(rèn)。此外,用戶(hù)無(wú)需任何確認(rèn)即可登錄。
為了解決這個(gè)問(wèn)題并確保正確的流程,我們需要首先在簽名過(guò)程中要求電子郵件確認(rèn)。然后,我們需要集成 SendGrid 服務(wù),以便標(biāo)識(shí)框架可以利用它。
// Program.cs
builder.Services.AddIdentityCore<IdentityUser>(options=>
{
options.SignIn.RequireConfirmedEmail=true;
})
builder.Services.AddSendGrid(options=>
options.ApiKey=builder.Configuration["SendGridKey"]!
);
您的文件現(xiàn)在應(yīng)類(lèi)似于此結(jié)構(gòu)。Program.cs
using Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using SendGrid.Extensions.DependencyInjection;
using System.Security.Claims;
var builder=WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddAuthentication()
.AddBearerToken(IdentityConstants.BearerScheme);
builder.Services.AddSendGrid(options=>
options.ApiKey=builder.Configuration["SendGridKey"]!
);
builder.Services.AddTransient<IEmailSender, EmailSender>();
builder.Services.AddAuthorizationBuilder();
builder.Services.AddDbContext<AppDbContext>(options=>
{
options.UseSqlite("DataSource=app.db");
});
builder.Services.AddIdentityCore<IdentityUser>(options=>
{
options.SignIn.RequireConfirmedEmail=true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddApiEndpoints();
var app=builder.Build();
app.MapIdentityApi<IdentityUser>();
app.Map("/", (ClaimsPrincipal user)=> $"Hello {user.Identity!.Name} from minimal apis.")
.RequireAuthorization();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
現(xiàn)在,是時(shí)候測(cè)試我們的應(yīng)用程序了。嘗試注冊(cè)一個(gè)新用戶(hù),并檢查您的電子郵件以獲取確認(rèn)鏈接。單擊該鏈接應(yīng)將數(shù)據(jù)庫(kù)中的確認(rèn)狀態(tài)更改為 TRUE。
請(qǐng)記住檢查您的垃圾郵件文件夾,因?yàn)殡娮余]件通常最終會(huì)在那里。此外,請(qǐng)注意,電子郵件在發(fā)送之前會(huì)排隊(duì),因此預(yù)計(jì)會(huì)有幾秒鐘的輕微延遲。
注冊(cè)新用戶(hù)
Visual Studio 日志
在數(shù)據(jù)庫(kù)中創(chuàng)建的用戶(hù)
確認(rèn)鏈接電子郵件
通過(guò)單擊鏈接“單擊此處”,它應(yīng)該將您重定向回 localhost,并將用戶(hù)在數(shù)據(jù)庫(kù)中的確認(rèn)狀態(tài)更新為 TRUE。
確認(rèn)消息
確認(rèn)狀態(tài)更改為 TRUE
最后,通過(guò)注入 SendGrid 服務(wù),我們可以簡(jiǎn)化 EmailService,從而減少代碼行數(shù)。
這是該版本的更新版本,可以有效處理所有測(cè)試方案。EmailService
using Microsoft.AspNetCore.Identity.UI.Services;
using SendGrid;
using SendGrid.Helpers.Mail;
public class EmailSender : IEmailSender
{
private readonly ILogger _logger;
private readonly IConfiguration _configuration;
private readonly ISendGridClient _sendGridClient;
public EmailSender(IConfiguration configuration, ILogger<EmailSender> logger, ISendGridClient sendGridClient)
{
_configuration=configuration;
_logger=logger;
_sendGridClient=sendGridClient;
}
public async Task SendEmailAsync(string toEmail, string subject, string message)
{
var msg=new SendGridMessage()
{
From=new EmailAddress(_configuration["From"], _configuration["Name"\]),
Subject=subject,
PlainTextContent=message,
HtmlContent=message
};
msg.AddTo(new EmailAddress(toEmail));
var response=await _sendGridClient.SendEmailAsync(msg);
_logger.LogInformation(response.IsSuccessStatusCode
? $"Email to {toEmail} queued successfully!"
: $"Failure Email to {toEmail}");
}
}
您當(dāng)然需要重新發(fā)送確認(rèn)電子郵件的選項(xiàng),這對(duì)任何應(yīng)用程序都至關(guān)重要。.NET 8 Identity 提供重新發(fā)送確認(rèn)終結(jié)點(diǎn):只需調(diào)用并設(shè)置正文,如下所示:.NET 8 Identity provides a resend confirmation endpoint: just call and set the body as follows:/resendConfirmationEmail
{
"Email": "user email"
}
Microsoft 標(biāo)識(shí)還提供修改默認(rèn)設(shè)置或引入新的自定義屬性的功能。
調(diào)整默認(rèn)設(shè)置。
builder.Services.Configure<IdentityOptions>(options=>
{
// Password settings.
options.Password.RequireDigit=true;
options.Password.RequireLowercase=true;
options.Password.RequireNonAlphanumeric=true;
options.Password.RequireUppercase=true;
options.Password.RequiredLength=6;
options.Password.RequiredUniqueChars=1;
// Lockout settings.
options.Lockout.DefaultLockoutTimeSpan=TimeSpan.FromMinutes(5);
options.Lockout.MaxFailedAccessAttempts=5;
options.Lockout.AllowedForNewUsers=true;
// User settings.
options.User.AllowedUserNameCharacters="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-.\_@+";
options.User.RequireUniqueEmail=false;
});
將新的自定義屬性添加到用戶(hù)屬性中。這可以通過(guò)創(chuàng)建一個(gè)新的用戶(hù)類(lèi)來(lái)實(shí)現(xiàn),該類(lèi)繼承自 。
class AppUser : IdentityUser
{
public int MyProperty { get; set; }
}
隨后,需要添加新的遷移,并且應(yīng)將新屬性集成到表中。
請(qǐng)記住將應(yīng)用程序中出現(xiàn)的每個(gè)項(xiàng)更新為IdentityUserAppUser
在我們開(kāi)始之前,這里有一些概念,在我們開(kāi)始之前最好知道。
什么是多重身份驗(yàn)證 (MFA)?
多重身份驗(yàn)證 (MFA) 通過(guò)要求用戶(hù)在登錄過(guò)程中提供其他形式的身份標(biāo)識(shí)來(lái)增強(qiáng)安全性。這可能包括從手機(jī)輸入代碼、使用 FIDO2 密鑰或提供指紋掃描。通過(guò)要求第二種形式的身份驗(yàn)證,MFA 使攻擊者更難獲得未經(jīng)授權(quán)的訪問(wèn),因?yàn)楦郊右蛩夭蝗菀撰@得或復(fù)制。
什么是雙因素身份驗(yàn)證 (2FA)?
雙因素身份驗(yàn)證 (2FA) 類(lèi)似于 MFA 的子集,但不同之處在于 MFA 可能需要兩個(gè)或多個(gè)因素來(lái)證明身份。
什么是TOTP(基于時(shí)間的一次性密碼算法)?
使用 ASP.NET Core Identity 時(shí),默認(rèn)支持使用 TOTP 的 MFA。此方法可與任何合規(guī)的身份驗(yàn)證器應(yīng)用一起使用,包括:
Microsoft 身份驗(yàn)證器
谷歌身份驗(yàn)證器
什么是 MFA 短信?
與密碼身份驗(yàn)證(單因素)相比,帶有 SMS 的 MFA 大大提高了安全性。但是,不再建議使用 SMS 作為第二個(gè)因素。對(duì)于此類(lèi)實(shí)現(xiàn),存在太多已知的攻擊媒介。
什么是在線快速身份識(shí)別 (FIDO)?
FIDO 是無(wú)密碼身份驗(yàn)證的開(kāi)放標(biāo)準(zhǔn),允許用戶(hù)在沒(méi)有密碼的情況下登錄。FIDO2 密鑰(通常是 USB,但也包括藍(lán)牙或 NFC)通過(guò)消除密碼風(fēng)險(xiǎn)來(lái)增強(qiáng)安全性。
它們目前被認(rèn)為是最安全的 MFA 方法。
現(xiàn)在,讓我們開(kāi)始配置 2FA。
我們將利用 2FA 與 MVC 項(xiàng)目的集成。首次創(chuàng)建新的 MVC 項(xiàng)目時(shí),可以選擇從一開(kāi)始就生成標(biāo)識(shí)系統(tǒng),包括“注冊(cè)”、“登錄”和“2FA”。我們的第一步是創(chuàng)建一個(gè)新的 MVC 項(xiàng)目。
在 Visual Studio 2022 中,可以完成以下操作:
選擇個(gè)人帳戶(hù)標(biāo)識(shí)選項(xiàng)。
或者,您可以使用命令行。
dotnet new mvc -n "2FA" -au individual
創(chuàng)建的項(xiàng)目應(yīng)如下所示。
現(xiàn)在,運(yùn)行創(chuàng)建的項(xiàng)目。應(yīng)顯示一個(gè)帶有登錄和注冊(cè)按鈕的歡迎頁(yè)面。
單擊“注冊(cè)”以創(chuàng)建新用戶(hù)。
單擊“單擊此處確認(rèn)您的帳戶(hù)”以確認(rèn)您的電子郵件。
接下來(lái),注銷(xiāo),然后再次登錄。點(diǎn)擊右上角的電子郵件。
導(dǎo)航到“雙因素身份驗(yàn)證”部分。
單擊“添加身份驗(yàn)證器應(yīng)用程序” 應(yīng)顯示雙因素身份驗(yàn)證設(shè)置屏幕。
如您所見(jiàn),它是開(kāi)箱即用的,但沒(méi)有掃描二維碼的選項(xiàng);它僅適用于代碼。
現(xiàn)在,讓我們添加二維碼功能,允許用戶(hù)只需掃描二維碼即可自動(dòng)添加密鑰。
我們需要訪問(wèn)為我們創(chuàng)建的生成的 cshtml 頁(yè)面。默認(rèn)情況下,這些頁(yè)面位于目錄中。但是,如果尚未展開(kāi)目錄,則 dotnet 不會(huì)顯示文件,除非生成了文件。Areas/Identity/Pages
為此,請(qǐng)運(yùn)行該命令。
dotnet tool install -g dotnet-aspnet-codegenerator
接下來(lái),我們需要安裝所有必需的 NuGet 包。
dotnet add pacakge Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
然后,運(yùn)行代碼生成器并指定要檢索的頁(yè)面。默認(rèn)情況下,如果將其留空,它將生成許多頁(yè)面。但是,為了避免不必要的頁(yè)面使您的項(xiàng)目混亂,我們將指定我們需要的特定頁(yè)面。
cd "your project directory"
dotnet aspnet-codegenerator identity -dc _2FA.Data.ApplicationDbContext --files "Account.Manage.EnableAuthenticator"
若要查看可生成的所有文件,請(qǐng)運(yùn)行該命令。
dotnet aspnet-codegenerator identity -dc _2FA.Data.ApplicationDbContext -lf
以下是可以生成的所有文件的完整列表。
File List:
Account.\_StatusMessage
Account.AccessDenied
Account.ConfirmEmail
Account.ConfirmEmailChange
Account.ExternalLogin
Account.ForgotPassword
Account.ForgotPasswordConfirmation
Account.Lockout
Account.Login
Account.LoginWith2fa
Account.LoginWithRecoveryCode
Account.Logout
Account.Manage.\_Layout
Account.Manage.\_ManageNav
Account.Manage.\_StatusMessage
Account.Manage.ChangePassword
Account.Manage.DeletePersonalData
Account.Manage.Disable2fa
Account.Manage.DownloadPersonalData
Account.Manage.Email
Account.Manage.EnableAuthenticator
Account.Manage.ExternalLogins
Account.Manage.GenerateRecoveryCodes
Account.Manage.Index
Account.Manage.PersonalData
Account.Manage.ResetAuthenticator
Account.Manage.SetPassword
Account.Manage.ShowRecoveryCodes
Account.Manage.TwoFactorAuthentication
Account.Register
Account.RegisterConfirmation
Account.ResendEmailConfirmation
Account.ResetPassword
Account.ResetPasswordConfirmation
返回到 Visual Studio,現(xiàn)在應(yīng)該會(huì)看到已添加的文件。EnableAuthenticator.cshtml
在編輯文件之前,我們需要下載qrcode.js JavaScript庫(kù),它將為我們渲染并生成QR碼。將其保存在文件夾中。EnableAuthenticator.cshtmlwwwroot\lib
在這里,我將其重命名為qrcodejs
接下來(lái),創(chuàng)建一個(gè)新文件,使用以下代碼在 中調(diào)用它。qr.jswwwroot\js
window.addEventListener("load", ()=> {
const uri=document.getElementById("qrCodeData").getAttribute('data-url');
new QRCode(document.getElementById("qrCode"),
{
text: uri,
width: 150,
height: 150
});
});
最后,打開(kāi)文件,然后:EnableAuthenticator.cshtml
更新該部分以添加對(duì)以前下載的庫(kù)的引用。Scriptsqrcode.js
添加帶有調(diào)用的文件以生成二維碼。qr.js
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
<script type="text/javascript" src="~/lib/qrcodejs/qrcode.js"></script>
<script type="text/javascript" src="~/js/qr.js"></script>
}
再次運(yùn)行應(yīng)用程序,QR 碼現(xiàn)在應(yīng)該出現(xiàn)。
使用Authenticator應(yīng)用程序掃描它,它應(yīng)該可以完美運(yùn)行。
通過(guò)將其添加到身份驗(yàn)證器應(yīng)用,可以從應(yīng)用中生成的代碼中驗(yàn)證代碼。它應(yīng)該顯示 2FA 確認(rèn)。
現(xiàn)在,嘗試注銷(xiāo),然后重新登錄。將出現(xiàn) 2FA 表單,提示您輸入 2FA 代碼。
恭喜,您的 2FA 設(shè)置完美。
源代碼獲取:公眾號(hào)回復(fù)消息【code:19129
】
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。