View Javadoc
1   package cn.home1.oss.lib.security.api;
2   
3   import static cn.home1.oss.boot.autoconfigure.AppProperties.getEnvironment;
4   import static cn.home1.oss.boot.autoconfigure.AppProperties.getProdEnvironment;
5   import static cn.home1.oss.boot.autoconfigure.AppSecurityProperties.ENCRYPTED_FIELD_PREFIX;
6   import static com.google.common.base.Preconditions.checkState;
7   import static com.google.common.collect.Lists.newArrayListWithExpectedSize;
8   import static java.util.stream.Collectors.toSet;
9   import static org.apache.commons.lang3.StringUtils.isBlank;
10  import static org.apache.commons.lang3.StringUtils.isNotBlank;
11  
12  import com.google.common.collect.ImmutableMap;
13  import com.google.common.collect.ImmutableSet;
14  
15  import cn.home1.oss.lib.common.crypto.EncodeCipher;
16  import cn.home1.oss.lib.security.internal.BaseGrantedAuthority;
17  
18  import lombok.Getter;
19  import lombok.Setter;
20  import lombok.extern.slf4j.Slf4j;
21  
22  import org.springframework.beans.factory.annotation.Autowired;
23  import org.springframework.boot.autoconfigure.security.SecurityProperties;
24  import org.springframework.context.ApplicationListener;
25  import org.springframework.context.event.ContextRefreshedEvent;
26  import org.springframework.security.authentication.AuthenticationProvider;
27  import org.springframework.security.authentication.BadCredentialsException;
28  import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
29  import org.springframework.security.core.AuthenticationException;
30  import org.springframework.security.core.GrantedAuthority;
31  import org.springframework.security.core.userdetails.UserDetailsService;
32  import org.springframework.security.core.userdetails.UsernameNotFoundException;
33  import org.springframework.security.crypto.password.PasswordEncoder;
34  
35  import java.util.List;
36  import java.util.Set;
37  
38  /**
39   * Created by zhanghaolun on 16/7/6.
40   */
41  @Slf4j
42  public abstract class BaseUserDetailsAuthenticationProvider<U extends User>
43    extends org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider
44    implements AuthenticationProvider, UserDetailsService, ApplicationListener<ContextRefreshedEvent> {
45  
46    @Getter
47    private EncodeCipher cipher;
48    @Autowired(required = false)
49    @Setter
50    @Getter
51    private PasswordEncoder passwordEncoder;
52    @Autowired(required = false)
53    private SecurityProperties securityProperties;
54  
55    @Override
56    protected final void additionalAuthenticationChecks( //
57      final org.springframework.security.core.userdetails.UserDetails userDetails, //
58      final UsernamePasswordAuthenticationToken authentication //
59    ) throws AuthenticationException {
60      // no-op
61    }
62  
63    @Override
64    public final org.springframework.security.core.userdetails.UserDetails loadUserByUsername( //
65      final String username //
66    ) throws UsernameNotFoundException {
67      final GenericUser found = this.findEverywhere(username);
68      if (found != null) {
69        return found;
70      } else {
71        throw new UsernameNotFoundException(username);
72      }
73    }
74  
75    @Override
76    protected final org.springframework.security.core.userdetails.UserDetails retrieveUser( //
77      final String username, //
78      final UsernamePasswordAuthenticationToken authentication //
79    ) throws AuthenticationException {
80      if (log.isDebugEnabled()) {
81        log.debug("retrieveUser: {}", username);
82      }
83  
84      final String passwordInput = (String) authentication.getCredentials();
85      final String password = decryptIfEncrypted(passwordInput);
86      if (isBlank(password)) {
87        log.warn("Username {}: no password provided", username);
88        throw new BadCredentialsException("Please enter password");
89      }
90  
91      final GenericUser user = this.findEverywhere(username);
92      if (user == null) {
93        log.warn("Username {} password {}: user not found", username, password);
94        throw new UsernameNotFoundException(username);
95      }
96  
97      final PasswordEncoder passwordEncoder = this.passwordEncoder;
98      final Boolean passwordMatches;
99      if (passwordEncoder != null) {
100       passwordMatches = passwordEncoder.matches(password, user.getPassword());
101     } else {
102       passwordMatches = org.apache.commons.lang3.StringUtils.equals(password, user.getPassword());
103     }
104     if (!passwordMatches) {
105       log.warn("Username {} password {}: invalid password", username, password);
106       throw new BadCredentialsException("Invalid Login");
107     }
108 
109     if (!user.isEnabled()) {
110       log.warn("Username {}: disabled", username);
111       throw new BadCredentialsException("User disabled");
112     }
113 
114     return user;
115   }
116 
117   public final GenericUser findEverywhere(final String username) {
118     final User user = this.findByName(username);
119 
120     final GenericUser result;
121     if (user == null) {
122       result = this.findSecurityUser(username);
123     } else {
124       result = GenericUser.fromUser(user);
125     }
126     return result;
127   }
128 
129   protected final GenericUser findSecurityUser(final String username) {
130     final GenericUser result;
131     if (this.securityProperties == null) {
132       result = null;
133     } else {
134       final SecurityProperties.User securityUser = this.securityProperties.getUser();
135       if (securityUser != null && isNotBlank(securityUser.getName()) && securityUser.getName().equals(username)) {
136         final Set<GrantedAuthority> authorities = securityUser.getRole() //
137           .stream() //
138           .map(role -> new BaseGrantedAuthority(Security.ROLE_PREFIX + role)) //
139           .collect(toSet());
140         final PasswordEncoder passwordEncoder = this.passwordEncoder;
141         final String password = passwordEncoder != null ? //
142           passwordEncoder.encode(securityUser.getPassword()) : securityUser.getPassword();
143 
144         final UserDetails userDetails = UserDetails.userDetailsBuilder() //
145           .authorities(authorities) //
146           .enabled(true) //
147           .id("") //
148           .name(securityUser.getName()) //
149           .password(password) //
150           .properties(ImmutableMap.of()) //
151           .build();
152         result = GenericUser.fromUser(userDetails);
153       } else {
154         result = null;
155       }
156     }
157     return result;
158   }
159 
160   // ------------------------------ it users ------------------------------
161 
162   String decryptIfEncrypted(final String text) {
163     final String result;
164     if (text == null) {
165       result = null;
166     } else if (text.startsWith(ENCRYPTED_FIELD_PREFIX)) {
167       checkState(this.cipher != null, "Encrypt not supported.");
168       result = this.cipher.decrypt(text.substring(ENCRYPTED_FIELD_PREFIX.length()));
169     } else {
170       result = text;
171     }
172     return result;
173   }
174 
175   @Override
176   public void onApplicationEvent(final ContextRefreshedEvent event) {
177     final Boolean prodEnvironment = getProdEnvironment(getEnvironment(event.getApplicationContext().getEnvironment()));
178     if (!prodEnvironment) {
179       log.info("init test users on non-production environment.");
180       this.initTestUsers();
181     } else {
182       log.info("skip init test users on production environment.");
183     }
184   }
185 
186   protected abstract List<U> testUsers();
187 
188   public final List<U> initTestUsers() {
189     final List<U> users = this.testUsers();
190     final List<U> result = newArrayListWithExpectedSize(users.size());
191     for (final U user : users) {
192       final U found = this.findByName(user.getName());
193       if (found == null) {
194         final Set<GrantedAuthority> authorities = user.getAuthorities() != null ? //
195           user.getAuthorities() : //
196           ImmutableSet.of();
197         authorities.forEach(this::saveRole);
198         result.add(this.save(user));
199       } else if (!found.isEnabled() || !found.equals(user)) {
200         this.delete(found);
201         result.add(this.save(user));
202       } else {
203         result.add(found);
204       }
205     }
206     return result;
207   }
208 
209   public List<U> deleteTestUsers() {
210     final List<U> users = this.testUsers();
211     final List<U> result = newArrayListWithExpectedSize(users.size());
212     for (final U user : users) {
213       final U found = this.findByName(user.getName());
214       if (found != null) {
215         this.delete(found);
216       }
217     }
218     return result;
219   }
220 
221   public final void setCipher(final EncodeCipher cipher) {
222     this.cipher = cipher;
223   }
224 
225   protected abstract U findByName(String username);
226 
227   protected abstract U save(U user);
228 
229   protected abstract void delete(U user);
230 
231   protected abstract GrantedAuthority saveRole(GrantedAuthority authority);
232 }