开发者

Hibernate validator: @Email accepts ask@stackoverflow as valid?

开发者 https://www.devze.com 2023-01-30 16:54 出处:网络
I\'m using the @Email annotation to validate an e-mail address. The issue I\'m having is that it\'s accepting things like ask@stackoverflow as a valid e-m开发者_高级运维ail address.

I'm using the @Email annotation to validate an e-mail address. The issue I'm having is that it's accepting things like ask@stackoverflow as a valid e-m开发者_高级运维ail address. I guess this is because they want to support intranet addresses, but I can't seem to find a flag so it does check for an extension.

Do I really need to switch to @Pattern (and any recommendations for an e-mail pattern that's flexible) or am I missing something?


You can also use constraint composition as a work-around. In the example below, I rely on the @Email validator to do the main validation, and add a @Pattern validator to make sure the address is in the form of x@y.z (I don't recommend using just the @Pattern below for regular Email validation)

@Email(message="Please provide a valid email address")
@Pattern(regexp=".+@.+\\..+", message="Please provide a valid email address")
@Target( { METHOD, FIELD, ANNOTATION_TYPE })
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Documented
public @interface ExtendedEmailValidator {
    String message() default "Please provide a valid email address";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}


Actually, @Email from Hibernate Validator uses regexp internally. You can easily define your own constraint based on that regexp, modified as you need (note the + at the end of DOMAIN):

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {})
@Pattern(regexp = Constants.PATTERN, flags = Pattern.Flag.CASE_INSENSITIVE)
public @interface EmailWithTld {
    String message() default "Wrong email";
    Class<?>[] groups() default { };
    Class<? extends Payload>[] payload() default { };
}

interface Constants {
    static final String ATOM = "[a-z0-9!#$%&'*+/=?^_`{|}~-]";
    static final String DOMAIN = "(" + ATOM + "+(\\." + ATOM + "+)+";
    static final String IP_DOMAIN = "\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\]";

    static final String PATTERN =
            "^" + ATOM + "+(\\." + ATOM + "+)*@"
                    + DOMAIN
                    + "|"
                    + IP_DOMAIN
                    + ")$";
}


While it still possible to implement your own validator or compose a custom one that will aggregate @Email and @Pattern, you don't have to do this anymore!

In one of the recent releases (it's definitely is present in hibernate-validator 6.0.x), @Email has got new regexp attribute that is "an additional regular expression the annotated element must match". In other words, here is a new approach:

@Email(regexp = ".+@.+\\..+")
private String email;


Actually validating e-mail addresses is really complex. It is not possible to validate that an e-mail address is both syntactically correct and addresses the intended recipient in an annotation. The @Email annotation is a useful minimal check that doesn't suffer from the problem of false negatives.

The next step in validation should be sending an e-mail with a challenge that the user has to complete to establish that the user has access to the e-mail address.

It is better to be accept a few false positives in step 1 and allow some invalid e-mail addresses to pass through than to reject valid users. If you want to apply additional rules you can add more checks, but be really careful about what you assume to be a requirement of a valid e-mail address. For instance there is nothing in the RFCs that dictates that i@nl would be invalid, because nl is a registered country top-level domain.


Here's a javax.validation email validator using Apache Commons Validator

public class CommonsEmailValidator implements ConstraintValidator<Email, String> {

    private static final boolean ALLOW_LOCAL = false;
    private EmailValidator realValidator = EmailValidator.getInstance(ALLOW_LOCAL);

    @Override
    public void initialize(Email email) {

    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if( s == null ) return true;
        return realValidator.isValid(s);
    }
}

And the annotation:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE,  ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {CommonsEmailValidator.class})
@Documented
@ReportAsSingleViolation
public @interface Email {

    String message() default "{org.hibernate.validator.constraints.Email.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        Email[] value();
    }
}


Obviously I am late to the Party, Still I am replying to this question,

Why cant we use @Pattern annotation with regular expressions in our Validation class like this

public Class Sigunup {

    @NotNull
    @NotEmpty
    @Pattern((regexp="[A-Za-z0-9._%-+]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}")
    private String email;

}

Its easier.


If you are going to try the above solution https://stackoverflow.com/a/12515543/258544 add the @ReportAsSingleViolation in the annotation defination, this way you will avoid both validation message(one from @Email and one from @Pattern) as it is a composed annotation :

    @Email(message="Please provide a valid email address")
    @Pattern(regexp=".+@.+\\..+", message="Please provide a valid email address")
    @Target( { METHOD, FIELD, ANNOTATION_TYPE })
    @Retention(RUNTIME)
    @Constraint(validatedBy = {})
    @Documented
    @ReportAsSingleViolation

From @interface ReportAsSingleViolation javax.validation:validation-api:1.1.0.Final) annotation definition : "... Evaluation of composed constraints stops on the first validation error in case the composing constraint is annotated with ReportAsSingleViolation"


You can use Email regexp, also making sure that the validation doesn't fail when the email is empty.

@Email(regexp = ".+@.+\\..+|")
@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = {})
@Documented
public @interface ExtendedEmail {

  @OverridesAttribute(constraint = Email.class, name = "message")
  String message() default "{javax.validation.constraints.Email.message}";

  @OverridesAttribute(constraint = Email.class, name = "groups")
  Class<?>[] groups() default {};

  @OverridesAttribute(constraint = Email.class, name = "payload")
  Class<? extends Payload>[] payload() default {};
}


The constraint composition solution does not work. When Email is used in conjunction with Pattern, the Email regex is held in higher precedence. I believe this is because the Email annotation overrides a few Pattern attributes, namely flags and regexp (the key one here) If I remove @Email, only then will the @Pattern regular expression apply in validations.

/**
 * @return an additional regular expression the annotated string must match. The default is any string ('.*')
 */
@OverridesAttribute(constraint = Pattern.class, name = "regexp") String regexp() default ".*";

/**
 * @return used in combination with {@link #regexp()} in order to specify a regular expression option
 */
@OverridesAttribute(constraint = Pattern.class, name = "flags") Pattern.Flag[] flags() default { };
0

精彩评论

暂无评论...
验证码 换一张
取 消