1 package cn.home1.oss.lib.security.api;
2
3 import static cn.home1.oss.lib.common.CodecUtils.urlDecode;
4 import static cn.home1.oss.lib.common.CodecUtils.urlEncode;
5 import static cn.home1.oss.lib.security.api.OAuth2Utils.fromOAuth2Authentication;
6 import static cn.home1.oss.lib.security.api.OAuth2Utils.isOAuth2Authentication;
7 import static com.google.common.base.Preconditions.checkArgument;
8 import static com.google.common.base.Preconditions.checkNotNull;
9 import static java.lang.Boolean.FALSE;
10 import static java.lang.Thread.currentThread;
11 import static java.util.stream.Collectors.toSet;
12 import static lombok.AccessLevel.PACKAGE;
13 import static lombok.AccessLevel.PRIVATE;
14 import static org.apache.commons.lang3.StringUtils.isNotBlank;
15 import static org.apache.commons.lang3.StringUtils.splitPreserveAllTokens;
16
17 import com.google.common.collect.ImmutableSet;
18
19 import cn.home1.oss.lib.common.Defaults;
20 import cn.home1.oss.lib.common.JaxbMapAdapter;
21 import cn.home1.oss.lib.common.JaxbUtils;
22 import cn.home1.oss.lib.security.internal.BaseGrantedAuthority;
23
24 import com.fasterxml.jackson.annotation.JsonIgnore;
25
26 import io.swagger.annotations.ApiModelProperty;
27
28 import lombok.AllArgsConstructor;
29 import lombok.Builder;
30 import lombok.EqualsAndHashCode;
31 import lombok.Getter;
32 import lombok.NoArgsConstructor;
33 import lombok.NonNull;
34 import lombok.Setter;
35 import lombok.ToString;
36 import lombok.extern.slf4j.Slf4j;
37
38 import org.joda.time.DateTime;
39 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
40 import org.springframework.security.core.Authentication;
41 import org.springframework.security.core.CredentialsContainer;
42 import org.springframework.security.core.GrantedAuthority;
43 import org.springframework.security.core.context.SecurityContext;
44 import org.springframework.security.core.context.SecurityContextHolder;
45 import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
46 import org.springframework.util.ClassUtils;
47
48 import java.security.Principal;
49 import java.util.Map;
50 import java.util.Optional;
51 import java.util.Set;
52 import java.util.UUID;
53
54 import javax.xml.bind.annotation.XmlAccessType;
55 import javax.xml.bind.annotation.XmlAccessorType;
56 import javax.xml.bind.annotation.XmlElement;
57 import javax.xml.bind.annotation.XmlElementWrapper;
58 import javax.xml.bind.annotation.XmlRootElement;
59 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
60
61
62
63
64
65 @XmlRootElement(name = "genericUser")
66 @XmlAccessorType(XmlAccessType.FIELD)
67 @Builder(builderMethodName = "genericUserBuilder")
68 @AllArgsConstructor(access = PACKAGE)
69 @NoArgsConstructor(access = PRIVATE)
70 @EqualsAndHashCode(exclude = {"timestamp", "uuid"})
71 @ToString
72 @Setter(value = PRIVATE)
73 @Getter
74 @Slf4j
75 @SuppressWarnings({"PMD", "serial"})
76 public final class GenericUser
77 implements org.springframework.security.core.userdetails.UserDetails, CredentialsContainer {
78
79 public static final String GENERIC_USER_COOKIE = "genericUserCookie";
80 public static final String GENERIC_USER_TOKEN = "genericUserToken";
81 static final String DELIMITER = "+";
82 static final String USER_TYPE_UNKNOWN = "UNKNOWN";
83 private static final String CLASS_OAUTH2_AUTHENTICATION =
84 "org.springframework.security.oauth2.provider.OAuth2Authentication";
85 private static final Boolean OAUTH2_AUTHENTICATION_PRESENT;
86
87 static {
88 OAUTH2_AUTHENTICATION_PRESENT = ClassUtils.isPresent(
89 CLASS_OAUTH2_AUTHENTICATION, currentThread().getContextClassLoader());
90 }
91
92 private boolean accountNonExpired;
93 private boolean accountNonLocked;
94 @ApiModelProperty(dataType = "java.lang.String", example = "ADMIN,USER,OTHER")
95 @XmlElementWrapper(name = "authorities")
96 @XmlElement(name = "authority", type = BaseGrantedAuthority.class)
97 private Set<GrantedAuthority> authorities;
98 private boolean credentialsNonExpired;
99 private boolean enabled;
100 private String password;
101 private String username;
102
103 @XmlJavaTypeAdapter(value = JaxbMapAdapter.class, type = Map.class)
104 private Map<String, String> properties;
105 @ApiModelProperty(hidden = true)
106 @NonNull
107 @XmlJavaTypeAdapter(value = JaxbUtils.DatimeAdapter.class, type = DateTime.class)
108 private DateTime timestamp;
109 @NonNull
110 private String uuid;
111
112 public static GenericUser fromPrincipal(final Principal principal) {
113 final GenericUser result;
114 if (principal == null) {
115 result = null;
116 } else {
117 if (principal instanceof PreAuthenticatedAuthenticationToken) {
118 final PreAuthenticatedAuthenticationToken token = (PreAuthenticatedAuthenticationToken) principal;
119 result = isGenericUser(token.getPrincipal()) ? (GenericUser) token.getPrincipal() : null;
120 } else if (principal instanceof UsernamePasswordAuthenticationToken) {
121 final UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) principal;
122 result = isGenericUser(token.getPrincipal()) ? (GenericUser) token.getPrincipal() : null;
123 } else if (OAUTH2_AUTHENTICATION_PRESENT && isOAuth2Authentication(principal)) {
124 result = fromOAuth2Authentication(principal);
125 } else if (principal instanceof Authentication) {
126 final Authentication authentication = (Authentication) principal;
127 result = isGenericUser(authentication.getPrincipal()) ? (GenericUser) authentication.getPrincipal() : null;
128 } else {
129 if (log.isInfoEnabled()) {
130 log.info("unknown principal: {}:{}", principal.getClass(), principal);
131 }
132 result = null;
133 }
134 }
135 return result;
136 }
137
138 public static Optional<GenericUser> fromSecurityContext() {
139 final SecurityContext securityContext = SecurityContextHolder.getContext();
140 final Authentication authentication = securityContext.getAuthentication();
141 return Optional.ofNullable(fromPrincipal(authentication));
142 }
143
144 public static GenericUser fromUser(final User user) {
145 return user != null ? fromUser(user, uuid()) : null;
146 }
147
148 private static GenericUser fromUser(final User user, final String uuid) {
149 final GenericUser output;
150 if (user == null) {
151 output = null;
152 } else {
153 final Set<GrantedAuthority> authorities = user.getAuthorities() != null ?
154 user.getAuthorities().stream()
155 .map(authority -> new BaseGrantedAuthority(authority.getAuthority()))
156 .collect(toSet()) : ImmutableSet.of();
157 output = new GenericUser();
158 output.setAccountNonExpired(user.isAccountNonExpired());
159 output.setAccountNonLocked(user.isAccountNonLocked());
160 output.setAuthorities(authorities);
161 output.setCredentialsNonExpired(user.isCredentialsNonExpired());
162 output.setEnabled(user.isEnabled());
163
164 output.setPassword(user.getPassword());
165 output.setUsername(toUsername(user));
166 output.setProperties(user.getProperties());
167
168 output.setTimestamp(Defaults.now());
169 output.setUuid(uuid);
170 }
171 return output;
172 }
173
174 public static boolean isGenericUser(final Object object) {
175 return object != null && GenericUser.class.isAssignableFrom(object.getClass());
176 }
177
178 public static boolean isGenericUserLogin(final GenericUser genericUser) {
179 return genericUser != null && isNotBlank(genericUser.getId()) && !USER_TYPE_UNKNOWN.equals(genericUser.getType());
180 }
181
182 private static String toUsername(final User user) {
183 final String type = checkNotNull(user, "null user").getType();
184 final String id = user.getId() != null ? user.getId() : "";
185 final String name = user.getName();
186 return toUsername(type, id, name);
187 }
188
189 static String toUsername(final String type, final String id, final String name) {
190 return urlEncode(type) + DELIMITER
191 + urlEncode(id) + DELIMITER
192 + urlEncode(isNotBlank(name) ? name : "");
193 }
194
195 public static GenericUser unknownUser() {
196 return GenericUser.genericUserBuilder()
197 .authorities(ImmutableSet.of())
198 .enabled(FALSE)
199 .username(GenericUser.toUsername(GenericUser.USER_TYPE_UNKNOWN, "", GenericUser.USER_TYPE_UNKNOWN))
200 .password("")
201 .accountNonExpired(FALSE)
202 .accountNonLocked(FALSE)
203 .credentialsNonExpired(FALSE)
204 .timestamp(Defaults.now())
205 .uuid(GenericUser.uuid())
206 .build();
207 }
208
209 private static String uuid() {
210 return UUID.randomUUID().toString().replaceAll("-", "");
211 }
212
213 @Override
214 public void eraseCredentials() {
215 this.password = null;
216 }
217
218 @JsonIgnore
219 public String getType() {
220 return this.fromUsername(0, null);
221 }
222
223
224 public String getId() {
225 return this.fromUsername(1, "");
226 }
227
228 @JsonIgnore
229 public String getName() {
230 return this.fromUsername(2, "");
231 }
232
233 private String fromUsername(final int index, final String defaultValue) {
234 return isNotBlank(this.username) ? urlDecode(splitPreserveAllTokens(this.username, DELIMITER)[index]) :
235 defaultValue;
236 }
237
238 private void setUuid(final String uuid) {
239 checkArgument(isNotBlank(uuid));
240 this.uuid = uuid.replaceAll("-", "");
241 }
242
243 public UserDetails toUserInfo() {
244 return UserDetails.userDetailsBuilder()
245 .authorities(this.getAuthorities() != null ? this.getAuthorities() : ImmutableSet.of())
246 .enabled(this.isEnabled())
247 .id(this.getId())
248 .name(this.getName())
249 .password("[PROTECTED]")
250 .properties(this.getProperties())
251 .build();
252 }
253 }