/*
* Copyright 2014-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.rockagen.gnext.service.spring.security.extension;
import com.rockagen.commons.util.CommUtil;
import com.rockagen.gnext.po.AuthGroup;
import com.rockagen.gnext.po.AuthUser;
import com.rockagen.gnext.service.AuthUserServ;
import com.rockagen.gnext.tool.Conf;
import com.rockagen.gnext.tool.Crypto;
import com.rockagen.gnext.tool.Token;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* Extension token authentication class
* <p>
* <pre>
* token = base64(expirationTime + ":" + uid) + "." + base64(hmac(expirationTime + ":" + uid + ":" + password))
*
* password: That matches the one in the retrieved UserDetails
* uid: As identifiable to the UserDetailsService
* expirationTime: The date and time when the token expires,expressed in milliseconds
* </pre>
* </p>
*
* @author ra
* @since JDK1.8
*/
public class ExTokenAuthentication {
private static final Logger log = LoggerFactory.getLogger(ExTokenAuthentication.class);
// one week
private long expiredTime = 7 * 24 * 60 * 60 * 1000;
private String keyName = "spring.security.token.key";
private AuthUserServ authUserServ;
/**
* Validate token
*
* @param token token
* @see #newToken(String)
* @return true if authenticated
*/
public boolean authenticated(String token) {
if (token == null) {
return false;
}
String[] parts = Token.parsePartAB(token);
if(parts!=null){
String partA = CommUtil.decodeBase64(parts[0]);
String partB = CommUtil.decodeBase64(parts[1]);
String[] userDetails = Token.getUidAndExpirationTime(partA);
if (userDetails!=null) {
String uid = userDetails[0];
String expireTime = userDetails[1];
long now = System.currentTimeMillis();
try {
// Not expired
if (Long.parseLong(expireTime) > now) {
Optional<AuthUser> u = authUserServ.load(uid);
if (u.filter(AuthUser::enabled).isPresent()) {
String passwd = u.get().getPassword();
String vhmac = hmac(uid,expireTime,passwd);
return partB.equals(vhmac);
}
}
} catch (Exception e) {
log.error("Authenticate failed: because {}", e.getMessage());
}
}
}
return false;
}
/**
* <p>
* <pre>
* token = base64(expirationTime + ":" + uid) + "." + base64(hmac(expirationTime + ":" + uid + ":" + password))
*
* expirationTime: The date and time when the token expires,expressed in milliseconds
* uid: As identifiable to the UserDetailsService
* password: That matches the one in the retrieved UserDetails
* </pre>
* </p>
*
* @param uid uid
* @return new token,null if uid not exist or disabled
*/
public String newToken(String uid) {
Optional<AuthUser> u = authUserServ.load(uid);
if(u.filter(AuthUser::enabled).isPresent()) {
String passwd = u.get().getPassword();
long expirationTime = System.currentTimeMillis() + expiredTime;
String partA = expirationTime + ":" + uid;
String partB = hmac(uid,String.valueOf(expirationTime),passwd);
String token=CommUtil.encodeBase64(partA) + "." + CommUtil.encodeBase64(partB);
return token;
}
return null;
}
/**
* hmac
* @param expirationTime expiration time
* @param uid username
* @param salt salt
* @return hmac String
*/
private String hmac(String uid,String expirationTime,String salt){
String content=expirationTime + ":" + uid + ":" + salt;
return Crypto.hmac(Conf.get(keyName), content);
}
/**
* Get {@link org.springframework.security.core.userdetails.UserDetails} from token
*
* @param token token
* @return {@link org.springframework.security.core.userdetails.UserDetails} if token authenticated,otherwise return null
*/
public UserDetails getUserDetailsFromToken(String token) {
if (authenticated(token)) {
// Load user
Optional<AuthUser> user = authUserServ.load(Token.getUidFromToken(token));
if (user.filter(AuthUser::enabled).isPresent()) {
List<GrantedAuthority> authorities = new LinkedList<>();
Set<AuthGroup> groups = user.get().getGroups();
if (groups != null && groups.size() > 0) {
groups.forEach(x -> x.getRoles().forEach(y -> authorities.add(new SimpleGrantedAuthority(y.getName().trim()))));
}
return new User(user.get().getUid(), "***", authorities);
}
}
return null;
}
public void setExpiredTime(long expiredTime) {
this.expiredTime = expiredTime;
}
public void setKeyName(String keyName) {
this.keyName = keyName;
}
public void setAuthUserServ(AuthUserServ authUserServ) {
this.authUserServ = authUserServ;
}
}