使用正则表达式进行密码验证

huangapple go评论101阅读模式
英文:

Password validation with regexp

问题

我正在尝试使用正则表达式编写密码验证函数,但不知道该如何做。

Go语言的标准API提供的正则表达式包与其他语言不同。

有人有想法,这个正则表达式模式应该是什么样的?

该模式应该验证:

/*
 * 密码规则:
 * 至少7个字母
 * 至少1个数字
 * 至少1个大写字母
 * 至少1个特殊字符
 */
英文:

I am trying to write password validation function with regexp and don't know how to do it.

The regex package provided by the standard API of the Go language is different to other languages.

Have someone an idea, how this regexp pattern should looks like?

The pattern should validate:

/*
 * Password rules:
 * at least 7 letters
 * at least 1 number
 * at least 1 upper case
 * at least 1 special character
 */

答案1

得分: 35

这实际上是不可能的,因为Go的正则表达式不支持回溯。然而,可以很容易实现一个简单的例子:

func verifyPassword(s string) (sevenOrMore, number, upper, special bool) {
    letters := 0
    for _, c := range s {
        switch {
        case unicode.IsNumber(c):
            number = true
        case unicode.IsUpper(c):
            upper = true
            letters++
        case unicode.IsPunct(c) || unicode.IsSymbol(c):
            special = true
        case unicode.IsLetter(c) || c == ' ':
            letters++
        default:
            //return false, false, false, false
        }
    }
    sevenOrMore = letters >= 7
    return
}

希望对你有帮助!

英文:

That's actually impossible since Go's regex doesn't support backtracking.

However, it's easy to implement, a simple example:

func verifyPassword(s string) (sevenOrMore, number, upper, special bool) {
	letters := 0
	for _, c := range s {
		switch {
		case unicode.IsNumber(c):
			number = true
		case unicode.IsUpper(c):
			upper = true
			letters++
		case unicode.IsPunct(c) || unicode.IsSymbol(c):
			special = true
		case unicode.IsLetter(c) || c == ' ':
			letters++
		default:
			//return false, false, false, false
		}
	}
	sevenOrMore = letters >= 7
	return
}

答案2

得分: 13

正确的正则表达式应该是...这里没有正则表达式。

您可以定义一个自定义函数来验证密码,并将其与其他帮助验证字段的框架结合使用,例如mccoyst/validate(在这个关于参数验证的讨论中提到)。

您还可以使用go-validator/validator来定义类似的验证(但我仍然建议使用自定义验证器而不是一个或多个正则表达式)。

注意:go regexp基于**re2**,这是一个高效、原则性的正则表达式库。

因此,主要的权衡是没有反向引用,例如:(abc)\1,以及没有匹配的向后查找
作为交换,您可以获得高速的正则表达式

英文:

The right regexp would be... no regexp here.

You can define a custom function that would validate the password, and combine it with other frameworks helping validating a field, like mccoyst/validate (mentioned in this discussion about parameter validation)

You also have go-validator/validator whic allows to define similar validations (but I would still use a custom validator instead of one or several regexps).


Note: go regexp is based on re2, an efficient, principled regular expression library).

> So the major trade offs are no back-references for example: (abc)\1 and no matching look-behinds.
In exchange you get high speed regex.

答案3

得分: 9

从邻居的答案中构建,我也写了一个对我来说很有效的辅助函数。这个函数假设密码的总长度是满意的。请看以下代码:

func isValid(s string) bool {
    var (
        hasMinLen  = false
        hasUpper   = false
        hasLower   = false
        hasNumber  = false
        hasSpecial = false
    )
    if len(s) >= 7 {
        hasMinLen = true
    }
    for _, char := range s {
        switch {
        case unicode.IsUpper(char):
            hasUpper = true
        case unicode.IsLower(char):
            hasLower = true
        case unicode.IsNumber(char):
            hasNumber = true
        case unicode.IsPunct(char) || unicode.IsSymbol(char):
            hasSpecial = true
        }
    }
    return hasMinLen && hasUpper && hasLower && hasNumber && hasSpecial
}
isValid("pass")     // false
isValid("password") // false
isValid("Password") // false
isValid("P@ssword") // false
isValid("P@ssw0rd") // true

Go Playground 示例

英文:

Building from a neighboring answer, I too wrote a helper function that works well for me. This one just assumes overall password length is satisfactory. Check out the following...

func isValid(s string) bool {
	var (
		hasMinLen  = false
		hasUpper   = false
		hasLower   = false
		hasNumber  = false
		hasSpecial = false
	)
	if len(s) >= 7 {
		hasMinLen = true
	}
	for _, char := range s {
		switch {
		case unicode.IsUpper(char):
			hasUpper = true
		case unicode.IsLower(char):
			hasLower = true
		case unicode.IsNumber(char):
			hasNumber = true
		case unicode.IsPunct(char) || unicode.IsSymbol(char):
			hasSpecial = true
		}
	}
	return hasMinLen && hasUpper && hasLower && hasNumber && hasSpecial
}

isValid("pass")     // false
isValid("password") // false
isValid("Password") // false
isValid("P@ssword") // false
isValid("P@ssw0rd") // true

答案4

得分: 5

根据@OneOfOne的答案进行了一些错误消息的改进

package main

import (
	"fmt"
	"strings"
	"unicode"
)

func verifyPassword(password string) error {
	var uppercasePresent bool
	var lowercasePresent bool
	var numberPresent bool
	var specialCharPresent bool
	const minPassLength = 8
	const maxPassLength = 64
	var passLen int
	var errorString string

	for _, ch := range password {
		switch {
		case unicode.IsNumber(ch):
			numberPresent = true
			passLen++
		case unicode.IsUpper(ch):
			uppercasePresent = true
			passLen++
		case unicode.IsLower(ch):
			lowercasePresent = true
			passLen++
		case unicode.IsPunct(ch) || unicode.IsSymbol(ch):
			specialCharPresent = true
			passLen++
		case ch == ' ':
			passLen++
		}
	}
	appendError := func(err string) {
		if len(strings.TrimSpace(errorString)) != 0 {
			errorString += ", " + err
		} else {
			errorString = err
		}
	}
	if !lowercasePresent {
		appendError("缺少小写字母")
	}
	if !uppercasePresent {
		appendError("缺少大写字母")
	}
	if !numberPresent {
		appendError("至少需要一个数字字符")
	}
	if !specialCharPresent {
		appendError("缺少特殊字符")
	}
	if !(minPassLength <= passLen && passLen <= maxPassLength) {
		appendError(fmt.Sprintf("密码长度必须在%d到%d个字符之间", minPassLength, maxPassLength))
	}

	if len(errorString) != 0 {
		return fmt.Errorf(errorString)
	}
	return nil
}

// Let's test it
func main() {
	password := "Apple"
	err := verifyPassword(password)
	fmt.Println(password, " ", err)
}
英文:

Based on @OneOfOne's answer with some error message improvement

package main
import (
&quot;fmt&quot;
&quot;strings&quot;
&quot;unicode&quot;
)
func verifyPassword(password string) error {
var uppercasePresent bool
var lowercasePresent bool
var numberPresent bool
var specialCharPresent bool
const minPassLength = 8
const maxPassLength = 64
var passLen int
var errorString string
for _, ch := range password {
switch {
case unicode.IsNumber(ch):
numberPresent = true
passLen++
case unicode.IsUpper(ch):
uppercasePresent = true
passLen++
case unicode.IsLower(ch):
lowercasePresent = true
passLen++
case unicode.IsPunct(ch) || unicode.IsSymbol(ch):
specialCharPresent = true
passLen++
case ch == &#39; &#39;:
passLen++
}
}
appendError := func(err string) {
if len(strings.TrimSpace(errorString)) != 0 {
errorString += &quot;, &quot; + err
} else {
errorString = err
}
}
if !lowercasePresent {
appendError(&quot;lowercase letter missing&quot;)
}
if !uppercasePresent {
appendError(&quot;uppercase letter missing&quot;)
}
if !numberPresent {
appendError(&quot;atleast one numeric character required&quot;)
}
if !specialCharPresent {
appendError(&quot;special character missing&quot;)
}
if !(minPassLength &lt;= passLen &amp;&amp; passLen &lt;= maxPassLength) {
appendError(fmt.Sprintf(&quot;password length must be between %d to %d characters long&quot;, minPassLength, maxPassLength))
}
if len(errorString) != 0 {
return fmt.Errorf(errorString)
}
return nil
}
// Let&#39;s test it
func main() {
password := &quot;Apple&quot;
err := verifyPassword(password)
fmt.Println(password, &quot; &quot;, err)
}

答案5

得分: 2

有很多种方法可以实现相同的目标---其他答案似乎完全偏离了正则表达式,所以我想展示一下我用于简单的密码字符串的通过/失败测试的方法,这种方法适合我的思维方式。(请注意,这并不满足原始问题中“7个字母”的要求,但确实检查了整体长度。)对我来说,这段代码相当简单,比使用switch语句或一堆if语句更容易阅读:

password := "Pa$$w0rd"
secure := true
tests := []string{".{7,}", "[a-z]", "[A-Z]", "[0-9]", "[^\\d\\w]"}
for _, test := range tests {
t, _ := regexp.MatchString(test, password)
if !t {
secure = false
break
}
}
// 由于字符串"Pa$$w0rd"通过了所有的测试,secure将为true
英文:

There are many ways to skin a cat---The other answers seem to veer away from regex completely, so I thought I'd show my method for simple pass/fail testing of a password string, which is styled to suit my thinking. (Note that this doesn't meet the literal "7 letters" requirement in the original question, but does check overall length.) To me, this code is fairly simple and looks easier to read than doing switch statements or a bunch of if statements:

password := &quot;Pa$$w0rd&quot;
secure := true
tests := []string{&quot;.{7,}&quot;, &quot;[a-z]&quot;, &quot;[A-Z]&quot;, &quot;[0-9]&quot;, &quot;[^\\d\\w]&quot;}
for _, test := range tests {
t, _ := regexp.MatchString(test, password)
if !t {
secure = false
break
}
}
//secure will be true, since the string &quot;Pa$$w0rd&quot; passes all the tests

答案6

得分: 1

以下是我对上述答案的实现,使用自定义消息并以一种良好的方式进行了调整(性能感知代码)。

package main

import (
	"fmt"
	"strconv"
	"unicode"
)

func main() {
	pass := "12345678_Windrol"

	// 调用密码验证器,并给它字段名以供用户识别,密码以及最小和最大密码长度
	isValid, errs := isValidPassword("密码", pass, 8, 32)
	if isValid {
		fmt.Println("密码有效")
	} else {
		for _, v := range errs {
			fmt.Println(v)
		}
	}

}

func isValidPassword(field, s string, min, max int) (isValid bool, errs []string) {
	var (
		isMin   bool
		special bool
		number  bool
		upper   bool
		lower   bool
	)

	// 测试密码字符串所需的最大和最小字符数
	if len(s) < min || len(s) > max {
		isMin = false
		appendError("长度应为" + strconv.Itoa(min) + "到" + strconv.Itoa(max))
	}

	for _, c := range s {
		// 如果在到达末尾之前所有条件都为真,则优化性能
		if special && number && upper && lower && isMin {
			break
		}

		// 否则继续切换
		switch {
		case unicode.IsUpper(c):
			upper = true
		case unicode.IsLower(c):
			lower = true
		case unicode.IsNumber(c):
			number = true
		case unicode.IsPunct(c) || unicode.IsSymbol(c):
			special = true
		}
	}

	// 添加错误
	appendError := func(err string) {
		errs = append(errs, field+" "+err)
	}

	// 添加自定义错误消息
	if !special {
		appendError("应包含至少一个特殊字符")
	}
	if !number {
		appendError("应包含至少一个数字")
	}
	if !lower {
		appendError("应包含至少一个小写字母")
	}
	if !upper {
		appendError("应包含至少一个大写字母")
	}

	// 如果有任何错误
	if len(errs) > 0 {
		return false, errs
	}

	// 一切正常
	return true, errs
}

希望对你有帮助!

英文:

Below it's my implementation of the above answers with custom messages and somehow twisting to them in a good way(performance aware codes).

package main
import (
&quot;fmt&quot;
&quot;strconv&quot;
&quot;unicode&quot;
)
func main() {
pass := &quot;12345678_Windrol&quot;
// call the password validator and give it field name  to be known by the user, password, and the min and max password length
isValid, errs := isValidPassword(&quot;Password&quot;, pass, 8, 32)
if isValid {
fmt.Println(&quot;The password is valid&quot;)
} else {
for _, v := range errs {
fmt.Println(v)
}
}
}
func isValidPassword(field, s string, min, max int) (isValid bool, errs []string) {
var (
isMin   bool
special bool
number  bool
upper   bool
lower   bool
)
//test for the muximum and minimum characters required for the password string
if len(s) &lt; min || len(s) &gt; max {
isMin = false
appendError(&quot;length should be &quot; + strconv.Itoa(min) + &quot; to &quot; + strconv.Itoa(max))
}
for _, c := range s {
// Optimize perf if all become true before reaching the end
if special &amp;&amp; number &amp;&amp; upper &amp;&amp; lower &amp;&amp; isMin {
break
}
// else go on switching
switch {
case unicode.IsUpper(c):
upper = true
case unicode.IsLower(c):
lower = true
case unicode.IsNumber(c):
number = true
case unicode.IsPunct(c) || unicode.IsSymbol(c):
special = true
}
}
// append error
appendError := func(err string) {
errs = append(errs, field+&quot; &quot;+err)
}
// Add custom error messages
if !special {
appendError(&quot;should contain at least a single special character&quot;)
}
if !number {
appendError(&quot;should contain at least a single digit&quot;)
}
if !lower {
appendError(&quot;should contain at least a single lowercase letter&quot;)
}
if !upper {
appendError(&quot;should contain at least single uppercase letter&quot;)
}
// if there is any error
if len(errs) &gt; 0 {
return false, errs
}
// everyting is right
return true, errs
}

huangapple
  • 本文由 发表于 2014年9月15日 03:35:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/25837241.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定