View Javadoc
1   /*
2    * Copyright 2021 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.bremersee.security.core.userdetails;
18  
19  import java.nio.charset.StandardCharsets;
20  import java.security.MessageDigest;
21  import java.security.NoSuchAlgorithmException;
22  import java.util.Base64;
23  import lombok.AccessLevel;
24  import lombok.Getter;
25  import lombok.ToString;
26  import lombok.extern.slf4j.Slf4j;
27  import org.bremersee.exception.ServiceException;
28  import org.springframework.security.crypto.factory.PasswordEncoderFactories;
29  import org.springframework.security.crypto.password.PasswordEncoder;
30  import org.springframework.util.StringUtils;
31  
32  /**
33   * The ldaptive password encoder.
34   *
35   * @author Christian Bremer
36   */
37  @ToString
38  @Slf4j
39  public class LdaptivePasswordEncoder implements PasswordEncoder {
40  
41    /**
42     * Plain ldaptive password encoder.
43     *
44     * @return the ldaptive password encoder
45     */
46    public static LdaptivePasswordEncoder plain() {
47      return new LdaptivePasswordEncoder("plain", null);
48    }
49  
50    /**
51     * Plain with no label ldaptive password encoder.
52     *
53     * @return the ldaptive password encoder
54     */
55    public static LdaptivePasswordEncoder plainWithNoLabel() {
56      return new LdaptivePasswordEncoder(null, null);
57    }
58  
59    /**
60     * The constant delegate.
61     */
62    protected static final PasswordEncoder delegate = PasswordEncoderFactories.createDelegatingPasswordEncoder();
63  
64    @Getter(value = AccessLevel.PROTECTED)
65    private final String label;
66  
67    @Getter(value = AccessLevel.PROTECTED)
68    private final String algorithm;
69  
70    /**
71     * Instantiates a new ldaptive password encoder.
72     */
73    public LdaptivePasswordEncoder() {
74      this("SHA", "SHA");
75    }
76  
77    /**
78     * Instantiates a new ldaptive password encoder.
79     *
80     * @param label the label
81     * @param algorithm the algorithm
82     */
83    public LdaptivePasswordEncoder(String label, String algorithm) {
84      this.label = label;
85      this.algorithm = algorithm;
86    }
87  
88    @Override
89    public String encode(CharSequence rawPassword) {
90      String raw = rawPassword != null ? rawPassword.toString() : "";
91      StringBuilder sb = new StringBuilder();
92      if (StringUtils.hasText(getLabel())) {
93        sb.append('{').append(getLabel()).append('}');
94      }
95      if (!StringUtils.hasText(getAlgorithm()) || "plain".equalsIgnoreCase(getAlgorithm())) {
96        sb.append(rawPassword);
97      } else {
98        sb.append(encrypt(raw));
99      }
100     return sb.toString();
101   }
102 
103   private String encrypt(String raw) {
104     try {
105       final MessageDigest md = MessageDigest.getInstance(getAlgorithm());
106       md.update(raw.getBytes(StandardCharsets.UTF_8));
107       byte[] hash = md.digest();
108       return new String(Base64.getEncoder().encode(hash), StandardCharsets.UTF_8);
109 
110     } catch (NoSuchAlgorithmException e) {
111       throw ServiceException.internalServerError("Algorithm '" + getAlgorithm() + "' was not found.", e);
112     }
113   }
114 
115   @Override
116   public boolean matches(CharSequence rawPassword, String encodedPassword) {
117     String raw = rawPassword != null ? rawPassword.toString() : "";
118     String enc = encodedPassword != null ? encodedPassword : "";
119     int index = enc.indexOf('}');
120     if (enc.startsWith("{") && index > 0) {
121       String foundLabel = enc.substring(1, index);
122       if ("plain".equalsIgnoreCase(foundLabel)) {
123         return enc.equals(LdaptivePasswordEncoder.plain().encode(raw));
124       } else if (foundLabel.equals(getLabel())) {
125         return enc.equals(encode(raw));
126       } else {
127         return delegate.matches(raw, enc);
128       }
129     } else if (!StringUtils.hasText(getLabel())) {
130       return StringUtils.hasText(getAlgorithm())
131           ? enc.equals(encode(raw))
132           : enc.equals(LdaptivePasswordEncoder.plainWithNoLabel().encode(raw));
133     }
134     return false;
135   }
136 
137 }