Java tutorial
/* * Copyright 2016 TomeOkin * * 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 app.service; import app.config.ResultCode; import app.data.crypt.Crypto; import app.data.local.UserRepository; import app.data.model.*; import app.data.model.internal.AccountSession; import app.data.validator.UserInfoValidator; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.hash.Funnel; import com.google.common.hash.Hashing; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @Service public class AuthService { private static final int EXPIRE_TIME = 24; // hours private static final int REFRESH_TIME = 8; // days private static final Logger logger = LoggerFactory.getLogger(AuthService.class); private final ObjectMapper mObjectMapper; private final CaptchaService mCaptchaService; private final UserInfoValidator mUserInfoValidator; private final UserRepository mUserRepo; private final Funnel<AccountSession> mExpireSessionFunnel; private final Funnel<AccountSession> mRefreshSessionFunnel; @Autowired public AuthService(ObjectMapper objectMapper, CaptchaService captchaService, UserInfoValidator userInfoValidator, UserRepository userRepo) { mObjectMapper = objectMapper; mCaptchaService = captchaService; mUserInfoValidator = userInfoValidator; mUserRepo = userRepo; mExpireSessionFunnel = (Funnel<AccountSession>) (from, into) -> into .putString(from.getUserId(), StandardCharsets.UTF_8).putLong(from.getExpireTime()); mRefreshSessionFunnel = (Funnel<AccountSession>) (from, into) -> into .putString(from.getUserId(), StandardCharsets.UTF_8).putLong(from.getRefreshTime()); } /** * for take full advantage of cache, use findOne instead of exists */ public boolean isExistUser(String uid) { return mUserRepo.findOne(uid) != null; } /** * ??????? */ public int register(CryptoToken cryptoToken, AccessResponse accessResponse) { RegisterData registerData; try { byte[] json = Crypto.decrypt(cryptoToken); registerData = mObjectMapper.readValue(json, RegisterData.class); } catch (Exception e) { logger.warn("decrypt register crypt-token failure", e); return ResultCode.INVALID_TOKEN; } // ??? uid ?????? // ??????????? CaptchaRequest captchaRequest = registerData.getCaptchaRequest(); int result = mCaptchaService.checkCaptcha(captchaRequest, registerData.getAuthCode(), false); if (result != BaseResponse.COMMON_SUCCESS) { return result; } // check user info boolean check = mUserInfoValidator.checkUserId(registerData.getUserId()); check = check && mUserInfoValidator.checkPasswordStrengthBaseline(registerData.getPassword()); if (!check) { return ResultCode.ILLEGAL_ACCOUNT_INFO; } // add user to db if (isExistUser(registerData.getUserId())) { return ResultCode.UID_EXISTED; } User user = new User(); user.setUid(registerData.getUserId()); user.setNickname(registerData.getNickname()); if (captchaRequest.getSendObject().contains("@")) { user.setEmail(captchaRequest.getSendObject()); user.setValidate(User.EMAIL_VALID); } else { user.setPhone(captchaRequest.getSendObject()); user.setRegion(captchaRequest.getRegion()); user.setValidate(User.PHONE_VALID); } user.setPassword(registerData.getPassword()); user.setImage(registerData.getImage()); try { mUserRepo.save(user); } catch (Exception e) { logger.warn("update user info failure", e); return ResultCode.UPDATE_USER_INFO_FAILED; } // ???????? mCaptchaService.checkCaptcha(captchaRequest, registerData.getAuthCode()); accessResponse.setUser(user.cloneSelfPublic()); return refreshAccessResponse(registerData.getUserId(), accessResponse); } /** * ?? uid ?????? */ public int login(CryptoToken cryptoToken, AccessResponse accessResponse) { LoginData loginData; try { byte[] json = Crypto.decrypt(cryptoToken); loginData = mObjectMapper.readValue(json, LoginData.class); } catch (Exception e) { logger.warn("decrypt register crypt-token failure", e); return ResultCode.INVALID_TOKEN; } boolean check = mUserInfoValidator.checkUserId(loginData.getUid()) && mUserInfoValidator.checkPasswordStrengthBaseline(loginData.getPassword()); if (!check) { return ResultCode.ILLEGAL_ACCOUNT_INFO; } User user; try { user = mUserRepo.findOne(loginData.getUid()); check = user != null && loginData.getUid().equals(user.getUid()) && loginData.getPassword().equals(user.getPassword()); if (!check) { return ResultCode.ACCOUNT_NOT_MATCH; } accessResponse.setUser(user.cloneSelfPublic()); } catch (Exception e) { logger.warn("query user failure", e); return ResultCode.QUERY_USER_FAILED; } // ? return refreshAccessResponse(user.getUid(), accessResponse); } public int checkIfAuth(CryptoToken cryptoToken) { AccountSession expireSession = getSessionWithCheck(cryptoToken, true); if (expireSession == null) { return ResultCode.INVALID_TOKEN; } DateTime now = DateTime.now(); DateTime deadline = new DateTime(expireSession.getExpireTime()); if (now.isBefore(deadline) || now.isEqual(deadline)) { return BaseResponse.COMMON_SUCCESS; } return ResultCode.OVERDUE_TOKEN; } public String checkIfAuthBind(CryptoToken cryptoToken) { AccountSession expireSession = getSessionWithCheck(cryptoToken, true); if (expireSession == null) { return null; } DateTime now = DateTime.now(); DateTime deadline = new DateTime(expireSession.getExpireTime()); if (now.isBefore(deadline) || now.isEqual(deadline)) { return expireSession.getUserId(); } return null; } public String checkIfAuthBind(String token) { try { CryptoToken cryptoToken = mObjectMapper.readValue(token, CryptoToken.class); return checkIfAuthBind(cryptoToken); } catch (Exception e) { logger.warn("check auth bind failure", e); return null; } } public int refreshExpireToken(CryptoToken cryptoToken, AccessResponse accessResponse) { AccountSession refreshSession = getSessionWithCheck(cryptoToken, false); if (refreshSession == null) { return ResultCode.INVALID_TOKEN; } // ? refreshToken DateTime now = DateTime.now(); DateTime deadline = new DateTime(refreshSession.getRefreshTime()); if (now.isAfter(deadline)) { return ResultCode.OVERDUE_TOKEN; } // expireToken final long expireTime = now.plusHours(EXPIRE_TIME).getMillis(); refreshSession.setExpireTime(expireTime); CryptoToken expireToken = newSessionToken(refreshSession, true); if (expireToken == null) { return ResultCode.ENCRYPT_TOKEN_FAILED; } accessResponse.setExpireTime(expireTime); accessResponse.setExpireToken(expireToken); return BaseResponse.COMMON_SUCCESS; } public int refreshRefreshToken(CryptoToken cryptoToken, AccessResponse accessResponse) { RefreshData refreshData; try { byte[] json = Crypto.decrypt(cryptoToken); refreshData = mObjectMapper.readValue(json, RefreshData.class); } catch (Exception e) { logger.warn("decrypt refresh crypt-token failure", e); return ResultCode.INVALID_TOKEN; } AccountSession old = getSessionWithCheck(refreshData.getRefreshToken(), false); if (old == null || !old.getUserId().equals(refreshData.getUserId())) { return ResultCode.INVALID_TOKEN; } return refreshAccessResponse(refreshData.getUserId(), accessResponse); } protected int refreshAccessResponse(String userId, AccessResponse accessResponse) { DateTime now = DateTime.now(); final long expireTime = now.plusHours(EXPIRE_TIME).getMillis(); final long refreshTime = now.plusDays(REFRESH_TIME).getMillis(); AccountSession session = new AccountSession(); session.setUserId(userId); session.setExpireTime(expireTime); session.setRefreshTime(refreshTime); CryptoToken expireToken = newSessionToken(session, true); if (expireToken == null) { return ResultCode.ENCRYPT_TOKEN_FAILED; } CryptoToken refreshToken = newSessionToken(session, false); if (refreshToken == null) { return ResultCode.ENCRYPT_TOKEN_FAILED; } accessResponse.setExpireTime(expireTime); accessResponse.setExpireToken(expireToken); accessResponse.setRefreshTime(refreshTime); accessResponse.setRefreshToken(refreshToken); return BaseResponse.COMMON_SUCCESS; } protected CryptoToken newSessionToken(AccountSession session, boolean isExpire) { Funnel<AccountSession> sessionFunnel = isExpire ? mExpireSessionFunnel : mRefreshSessionFunnel; byte[] data = Hashing.sipHash24().hashObject(session, sessionFunnel).asBytes(); String sessionString = new String(data, Charset.forName("UTF-8")); session.setSession(sessionString); CryptoToken token; try { byte[] sessionData = mObjectMapper.writeValueAsBytes(session); token = Crypto.encrypt(sessionData); } catch (Exception e) { logger.warn("encrypt session failure", e); return null; } return token; } protected AccountSession getSessionWithCheck(CryptoToken cryptoToken, boolean isExpire) { Funnel<AccountSession> sessionFunnel = isExpire ? mExpireSessionFunnel : mRefreshSessionFunnel; AccountSession session; try { byte[] json = Crypto.decrypt(cryptoToken); session = mObjectMapper.readValue(json, AccountSession.class); } catch (Exception e) { logger.warn("decrypt token failure", e); return null; } byte[] sessionData = Hashing.sipHash24().hashObject(session, sessionFunnel).asBytes(); String sessionString = new String(sessionData, StandardCharsets.UTF_8); if (!sessionString.equals(session.getSession())) { logger.warn("session not equal"); return null; } return session; } }