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
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
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
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 }