View Javadoc
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   * A generic token of user. Never extends GenericUser, use {@link GenericUser#fromPrincipal} {@link
63   * GenericUser#fromUser}. username => type:id:name:tel:wx
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       // DaoAuthenticationProvider#additionalAuthenticationChecks need this value
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   // @JsonIgnore//api needs expose id
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 }