提交 7c6708d5 编写于 作者: J johnniang

Complete token authentication

上级 e580f4fa
......@@ -91,6 +91,7 @@ public class InMemoryCacheStore extends StringCacheStore {
Assert.hasText(key, "Cache key must not be blank");
cacheContainer.remove(key);
log.debug("Removed key: [{}]", key);
}
/**
......
......@@ -4,15 +4,11 @@ import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import run.halo.app.cache.lock.CacheLock;
import run.halo.app.exception.BadRequestException;
import run.halo.app.model.dto.CountDTO;
import run.halo.app.model.dto.StatisticDTO;
import run.halo.app.model.params.LoginParam;
import run.halo.app.security.context.SecurityContextHolder;
import run.halo.app.security.filter.AdminAuthenticationFilter;
import run.halo.app.security.token.AuthToken;
import run.halo.app.service.AdminService;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
/**
......@@ -39,7 +35,7 @@ public class AdminController {
*/
@GetMapping("counts")
@ApiOperation("Gets count info")
public CountDTO getCount() {
public StatisticDTO getCount() {
return adminService.getCount();
}
......@@ -52,17 +48,7 @@ public class AdminController {
@PostMapping("logout")
@ApiOperation("Logs out (Clear session)")
@CacheLock
public void logout(HttpServletRequest request) {
adminService.clearAuthentication();
// Check if the current is logging in
boolean authenticated = SecurityContextHolder.getContext().isAuthenticated();
if (!authenticated) {
throw new BadRequestException("You haven't logged in yet, so you can't log out");
}
request.getSession().removeAttribute(AdminAuthenticationFilter.ADMIN_SESSION_KEY);
log.info("You have been logged out, Welcome to you next time!");
public void logout() {
adminService.clearToken();
}
}
......@@ -3,13 +3,13 @@ package run.halo.app.model.dto;
import lombok.Data;
/**
* Count output DTO.
* Statistic DTO.
*
* @author johnniang
* @date 3/19/19
*/
@Data
public class CountDTO {
public class StatisticDTO {
private long postCount;
......
......@@ -14,6 +14,7 @@ import run.halo.app.security.authentication.AuthenticationImpl;
import run.halo.app.security.context.SecurityContextHolder;
import run.halo.app.security.context.SecurityContextImpl;
import run.halo.app.security.support.UserDetail;
import run.halo.app.security.util.SecurityUtils;
import run.halo.app.service.UserService;
import javax.servlet.FilterChain;
......@@ -37,6 +38,16 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
*/
public final static String ADMIN_SESSION_KEY = "halo.admin.session";
/**
* Access token cache prefix.
*/
public final static String TOKEN_ACCESS_CACHE_PREFIX = "halo.admin.access.token.";
/**
* Refresh token cache prefix.
*/
public final static String TOKEN_REFRESH_CACHE_PREFIX = "halo.admin.refresh.token.";
/**
* Admin token header name.
*/
......@@ -82,20 +93,25 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
if (StringUtils.isNotBlank(token)) {
// Valid the token
Optional<UserDetail> optionalUserDetail = cacheStore.getAny(token, UserDetail.class);
// Get user id from cache
Optional<Integer> optionalUserId = cacheStore.getAny(SecurityUtils.buildTokenAccessKey(token), Integer.class);
if (!optionalUserDetail.isPresent()) {
if (!optionalUserId.isPresent()) {
getFailureHandler().onFailure(request, response, new AuthenticationException("The token has been expired or not exist").setErrorData(token));
return;
}
UserDetail userDetail = optionalUserDetail.get();
// Get the user
User user = userService.getById(optionalUserId.get());
// Build user detail
UserDetail userDetail = new UserDetail(user);
// Set security
SecurityContextHolder.setContext(new SecurityContextImpl(new AuthenticationImpl(userDetail)));
filterChain.doFilter(request, response);
return;
}
......
package run.halo.app.security.util;
import org.springframework.lang.NonNull;
import org.springframework.util.Assert;
import run.halo.app.model.entity.User;
import static run.halo.app.security.filter.AdminAuthenticationFilter.TOKEN_ACCESS_CACHE_PREFIX;
import static run.halo.app.security.filter.AdminAuthenticationFilter.TOKEN_REFRESH_CACHE_PREFIX;
import static run.halo.app.service.AdminService.ACCESS_TOKEN_CACHE_PREFIX;
import static run.halo.app.service.AdminService.REFRESH_TOKEN_CACHE_PREFIX;
/**
* Security utilities.
*
* @author johnniang
* @date 19-4-29
*/
public class SecurityUtils {
private SecurityUtils() {
}
@NonNull
public static String buildAccessTokenKey(@NonNull User user) {
Assert.notNull(user, "User must not be null");
return ACCESS_TOKEN_CACHE_PREFIX + user.getId();
}
@NonNull
public static String buildRefreshTokenKey(@NonNull User user) {
Assert.notNull(user, "User must not be null");
return REFRESH_TOKEN_CACHE_PREFIX + user.getId();
}
@NonNull
public static String buildTokenAccessKey(@NonNull String accessToken) {
Assert.hasText(accessToken, "Access token must not be blank");
return TOKEN_ACCESS_CACHE_PREFIX + accessToken;
}
@NonNull
public static String buildTokenRefreshKey(@NonNull String refreshToken) {
Assert.hasText(refreshToken, "Refresh token must not be blank");
return TOKEN_REFRESH_CACHE_PREFIX + refreshToken;
}
}
package run.halo.app.service;
import org.springframework.lang.NonNull;
import run.halo.app.model.dto.CountDTO;
import run.halo.app.model.dto.StatisticDTO;
import run.halo.app.model.params.LoginParam;
import run.halo.app.security.token.AuthToken;
......@@ -13,6 +13,10 @@ import run.halo.app.security.token.AuthToken;
*/
public interface AdminService {
String ACCESS_TOKEN_CACHE_PREFIX = "halo.admin.access_token.";
String REFRESH_TOKEN_CACHE_PREFIX = "halo.admin.refresh_token.";
/**
* Authenticates.
*
......@@ -25,7 +29,7 @@ public interface AdminService {
/**
* Clears authentication.
*/
void clearAuthentication();
void clearToken();
/**
* Get system counts.
......@@ -33,5 +37,5 @@ public interface AdminService {
* @return count dto
*/
@NonNull
CountDTO getCount();
StatisticDTO getCount();
}
package run.halo.app.service;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.entity.User;
import run.halo.app.model.params.UserParam;
import run.halo.app.service.base.CrudService;
import javax.servlet.http.HttpSession;
import java.util.Optional;
/**
......@@ -80,8 +81,8 @@ public interface UserService extends CrudService<User, Integer> {
/**
* Logins by username and password.
*
* @param key username or email must not be blank
* @param password password must not be blank
* @param key username or email must not be blank
* @param password password must not be blank
* @return user info
*/
@NonNull
......@@ -107,4 +108,21 @@ public interface UserService extends CrudService<User, Integer> {
*/
@NonNull
User createBy(@NonNull UserParam userParam);
/**
* The user must not expire.
*
* @param user user info must not be null
* @throws ForbiddenException throws if the given user has been expired
*/
void mustNotExpire(@NonNull User user);
/**
* Checks the password is match the user password.
*
* @param user user info must not be null
* @param plainPassword plain password
* @return true if the given password is match the user password; false otherwise
*/
boolean passwordMatch(@NonNull User user, @Nullable String plainPassword);
}
package run.halo.app.service.impl;
import cn.hutool.core.lang.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import run.halo.app.cache.StringCacheStore;
import run.halo.app.exception.BadRequestException;
import run.halo.app.model.dto.CountDTO;
import run.halo.app.model.dto.StatisticDTO;
import run.halo.app.model.entity.User;
import run.halo.app.model.enums.CommentStatus;
import run.halo.app.model.enums.PostStatus;
import run.halo.app.model.params.LoginParam;
import run.halo.app.security.authentication.Authentication;
import run.halo.app.security.context.SecurityContextHolder;
import run.halo.app.security.token.AuthToken;
import run.halo.app.security.util.SecurityUtils;
import run.halo.app.service.*;
import run.halo.app.utils.HaloUtils;
import java.util.concurrent.TimeUnit;
/**
* Admin service implementation.
......@@ -69,42 +76,92 @@ public class AdminServiceImpl implements AdminService {
public AuthToken authenticate(LoginParam loginParam) {
Assert.notNull(loginParam, "Login param must not be null");
return null;
if (SecurityContextHolder.getContext().isAuthenticated()) {
// If the user has been logged in
throw new BadRequestException("您已经登录,无需重复登录");
}
String username = loginParam.getUsername();
User user = Validator.isEmail(username) ?
userService.getByEmailOfNonNull(username) : userService.getByUsernameOfNonNull(username);
userService.mustNotExpire(user);
if (!userService.passwordMatch(user, loginParam.getPassword())) {
// If the password is mismatch
throw new BadRequestException("Username or password is incorrect");
}
// Generate new token
AuthToken token = new AuthToken();
int expiredIn = 24 * 3600;
token.setAccessToken(HaloUtils.randomUUIDWithoutDash());
token.setExpiredIn(expiredIn);
token.setRefreshToken(HaloUtils.randomUUIDWithoutDash());
// Cache those tokens, just for clearing
cacheStore.putAny(SecurityUtils.buildAccessTokenKey(user), token.getAccessToken(), 30, TimeUnit.DAYS);
cacheStore.putAny(SecurityUtils.buildRefreshTokenKey(user), token.getRefreshToken(), 30, TimeUnit.DAYS);
// Cache those tokens with user id
cacheStore.putAny(SecurityUtils.buildTokenAccessKey(token.getAccessToken()), user.getId(), expiredIn, TimeUnit.SECONDS);
cacheStore.putAny(SecurityUtils.buildTokenRefreshKey(token.getRefreshToken()), user.getId(), 30, TimeUnit.DAYS);
return token;
}
@Override
public void clearAuthentication() {
public void clearToken() {
// Check if the current is logging in
boolean authenticated = SecurityContextHolder.getContext().isAuthenticated();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!authenticated) {
if (authentication == null) {
throw new BadRequestException("You haven't logged in yet, so you can't log out");
}
// Get current user
User user = authentication.getDetail().getUser();
// Clear access token
cacheStore.getAny(SecurityUtils.buildAccessTokenKey(user), String.class).ifPresent(accessToken -> {
// Delete token
cacheStore.delete(SecurityUtils.buildTokenAccessKey(accessToken));
cacheStore.delete(SecurityUtils.buildAccessTokenKey(user));
});
// Clear refresh token
cacheStore.getAny(SecurityUtils.buildRefreshTokenKey(user), String.class).ifPresent(refreshToken -> {
cacheStore.delete(SecurityUtils.buildTokenRefreshKey(refreshToken));
cacheStore.delete(SecurityUtils.buildRefreshTokenKey(user));
});
log.info("You have been logged out, looking forward to your next visit!");
}
@Override
public CountDTO getCount() {
CountDTO countDTO = new CountDTO();
countDTO.setPostCount(postService.countByStatus(PostStatus.PUBLISHED));
countDTO.setAttachmentCount(attachmentService.count());
public StatisticDTO getCount() {
StatisticDTO statisticDTO = new StatisticDTO();
statisticDTO.setPostCount(postService.countByStatus(PostStatus.PUBLISHED));
statisticDTO.setAttachmentCount(attachmentService.count());
// Handle comment count
long postCommentCount = postCommentService.countByStatus(CommentStatus.PUBLISHED);
long sheetCommentCount = sheetCommentService.countByStatus(CommentStatus.PUBLISHED);
long journalCommentCount = journalCommentService.countByStatus(CommentStatus.PUBLISHED);
countDTO.setCommentCount(postCommentCount + sheetCommentCount + journalCommentCount);
statisticDTO.setCommentCount(postCommentCount + sheetCommentCount + journalCommentCount);
long birthday = optionService.getBirthday();
long days = (System.currentTimeMillis() - birthday) / (1000 * 24 * 3600);
countDTO.setEstablishDays(days);
statisticDTO.setEstablishDays(days);
countDTO.setLinkCount(linkService.count());
statisticDTO.setLinkCount(linkService.count());
countDTO.setVisitCount(postService.countVisit() + sheetService.countVisit());
countDTO.setLikeCount(postService.countLike() + sheetService.countLike());
return countDTO;
statisticDTO.setVisitCount(postService.countVisit() + sheetService.countVisit());
statisticDTO.setLikeCount(postService.countLike() + sheetService.countLike());
return statisticDTO;
}
}
......@@ -2,6 +2,7 @@ package run.halo.app.service.impl;
import cn.hutool.core.lang.Validator;
import cn.hutool.crypto.digest.BCrypt;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
......@@ -12,6 +13,7 @@ import run.halo.app.cache.lock.CacheLock;
import run.halo.app.event.logger.LogEvent;
import run.halo.app.event.user.UserUpdatedEvent;
import run.halo.app.exception.BadRequestException;
import run.halo.app.exception.ForbiddenException;
import run.halo.app.exception.NotFoundException;
import run.halo.app.model.entity.User;
import run.halo.app.model.enums.LogType;
......@@ -205,6 +207,25 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
return create(user);
}
@Override
public void mustNotExpire(User user) {
Assert.notNull(user, "User must not be null");
Date now = DateUtils.now();
if (user.getExpireTime() != null && user.getExpireTime().after(now)) {
long seconds = TimeUnit.MILLISECONDS.toSeconds(user.getExpireTime().getTime() - now.getTime());
// If expired
throw new ForbiddenException("You have been temporarily disabled,please try again " + HaloUtils.timeFormat(seconds) + " later").setErrorData(seconds);
}
}
@Override
public boolean passwordMatch(User user, String plainPassword) {
Assert.notNull(user, "User must not be null");
return !StringUtils.isBlank(plainPassword) && BCrypt.checkpw(plainPassword, user.getPassword());
}
@Override
@CacheLock
public User create(User user) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册