mirror of https://github.com/halo-dev/halo
Complete user login
parent
90b7007b6e
commit
b547e0720b
|
@ -2,6 +2,9 @@ package cc.ryanc.halo.repository;
|
|||
|
||||
import cc.ryanc.halo.model.entity.User;
|
||||
import cc.ryanc.halo.repository.base.BaseRepository;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* User repository.
|
||||
|
@ -10,4 +13,12 @@ import cc.ryanc.halo.repository.base.BaseRepository;
|
|||
*/
|
||||
public interface UserRepository extends BaseRepository<User, Integer> {
|
||||
|
||||
/**
|
||||
* Gets user by username.
|
||||
*
|
||||
* @param username username must not be blank
|
||||
* @return an optional user
|
||||
*/
|
||||
@NonNull
|
||||
Optional<User> findByUsername(@NonNull String username);
|
||||
}
|
||||
|
|
|
@ -28,6 +28,11 @@ public class UserDetail {
|
|||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets user info.
|
||||
*
|
||||
* @param user user info
|
||||
*/
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package cc.ryanc.halo.service;
|
||||
|
||||
import cc.ryanc.halo.exception.NotFoundException;
|
||||
import cc.ryanc.halo.model.entity.User;
|
||||
import cc.ryanc.halo.service.base.CrudService;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* User service.
|
||||
|
@ -10,4 +14,43 @@ import cc.ryanc.halo.service.base.CrudService;
|
|||
*/
|
||||
public interface UserService extends CrudService<User, Integer> {
|
||||
|
||||
/**
|
||||
* Login failure count key.
|
||||
*/
|
||||
String LOGIN_FAILURE_COUNT_KEY = "login.failure.count";
|
||||
|
||||
/**
|
||||
* Max login try count.
|
||||
*/
|
||||
int MAX_LOGIN_TRY = 5;
|
||||
|
||||
int LOCK_MINUTES = 10;
|
||||
|
||||
/**
|
||||
* Gets user by username.
|
||||
*
|
||||
* @param username username must not be blank
|
||||
* @return an optional user
|
||||
*/
|
||||
@NonNull
|
||||
Optional<User> getByUsername(@NonNull String username);
|
||||
|
||||
/**
|
||||
* Gets non null user by username.
|
||||
*
|
||||
* @return user info
|
||||
* @throws NotFoundException throws when the username does not exist
|
||||
*/
|
||||
@NonNull
|
||||
User getByUsernameOfNonNull(@NonNull String username);
|
||||
|
||||
/**
|
||||
* Logins by username and password.
|
||||
*
|
||||
* @param username username must not be blank
|
||||
* @param password password must not be blank
|
||||
* @return user info
|
||||
*/
|
||||
@NonNull
|
||||
User login(@NonNull String username, @NonNull String password);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
package cc.ryanc.halo.service.impl;
|
||||
|
||||
import cc.ryanc.halo.cache.StringCacheStore;
|
||||
import cc.ryanc.halo.exception.BadRequestException;
|
||||
import cc.ryanc.halo.exception.NotFoundException;
|
||||
import cc.ryanc.halo.model.entity.User;
|
||||
import cc.ryanc.halo.repository.UserRepository;
|
||||
import cc.ryanc.halo.service.UserService;
|
||||
import cc.ryanc.halo.service.base.AbstractCrudService;
|
||||
import cc.ryanc.halo.utils.DateUtils;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* UserService implementation class
|
||||
|
@ -17,8 +26,64 @@ public class UserServiceImpl extends AbstractCrudService<User, Integer> implemen
|
|||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
public UserServiceImpl(UserRepository userRepository) {
|
||||
private final StringCacheStore stringCacheStore;
|
||||
|
||||
public UserServiceImpl(UserRepository userRepository,
|
||||
StringCacheStore stringCacheStore) {
|
||||
super(userRepository);
|
||||
this.userRepository = userRepository;
|
||||
this.stringCacheStore = stringCacheStore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<User> getByUsername(String username) {
|
||||
return userRepository.findByUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public User getByUsernameOfNonNull(String username) {
|
||||
return getByUsername(username).orElseThrow(() -> new NotFoundException("The username dose not exist").setErrorData(username));
|
||||
}
|
||||
|
||||
@Override
|
||||
public User login(String username, String password) {
|
||||
Assert.hasText(username, "Username must not be blank");
|
||||
Assert.hasText(password, "Password must not be blank");
|
||||
|
||||
// Ger user by username
|
||||
User user = getByUsernameOfNonNull(username);
|
||||
|
||||
// Check expiration
|
||||
if (user.getExpireTime() != null && DateUtils.now().before(user.getExpireTime())) {
|
||||
// If expired
|
||||
// TODO replace by i18n
|
||||
throw new BadRequestException("You have been locked temporarily");
|
||||
}
|
||||
|
||||
|
||||
if (!BCrypt.checkpw(password, user.getPassword())) {
|
||||
// If the password is mismatched
|
||||
// Add login failure count
|
||||
Integer loginFailureCount = stringCacheStore.get(LOGIN_FAILURE_COUNT_KEY).map(countString -> Integer.valueOf(countString)).orElse(0);
|
||||
|
||||
if (loginFailureCount >= MAX_LOGIN_TRY) {
|
||||
// Set expiration
|
||||
user.setExpireTime(org.apache.commons.lang3.time.DateUtils.addMilliseconds(DateUtils.now(), LOCK_MINUTES));
|
||||
// Update user
|
||||
update(user);
|
||||
}
|
||||
|
||||
loginFailureCount++;
|
||||
|
||||
stringCacheStore.put(LOGIN_FAILURE_COUNT_KEY, loginFailureCount.toString(), LOCK_MINUTES, TimeUnit.MINUTES);
|
||||
|
||||
// TODO replace by i18n
|
||||
throw new BadRequestException("Username or password is mismatched, last " + (MAX_LOGIN_TRY - loginFailureCount) + " retry(s)");
|
||||
}
|
||||
|
||||
// TODO Set session
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package cc.ryanc.halo.utils;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Date utilities.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/18/19
|
||||
*/
|
||||
public class DateUtils {
|
||||
|
||||
private DateUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current date.
|
||||
*
|
||||
* @return current date
|
||||
*/
|
||||
@NonNull
|
||||
public static Date now() {
|
||||
return new Date();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,218 @@
|
|||
package cc.ryanc.halo.utils;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Json utilities.
|
||||
*
|
||||
* @author johnniang
|
||||
* @date 3/18/19
|
||||
*/
|
||||
public class JsonUtils {
|
||||
|
||||
/**
|
||||
* Default json mapper.
|
||||
*/
|
||||
public final static ObjectMapper DEFAULT_JSON_MAPPER = createDefaultJsonMapper();
|
||||
|
||||
private JsonUtils() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a default json mapper.
|
||||
*
|
||||
* @return object mapper
|
||||
*/
|
||||
public static ObjectMapper createDefaultJsonMapper() {
|
||||
return createDefaultJsonMapper(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a default json mapper.
|
||||
*
|
||||
* @param strategy property naming strategy
|
||||
* @return object mapper
|
||||
*/
|
||||
@NonNull
|
||||
public static ObjectMapper createDefaultJsonMapper(@Nullable PropertyNamingStrategy strategy) {
|
||||
// Create object mapper
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
// Configure
|
||||
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
// Set property naming strategy
|
||||
if (strategy != null) {
|
||||
mapper.setPropertyNamingStrategy(strategy);
|
||||
}
|
||||
return mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts json to the object specified type.
|
||||
*
|
||||
* @param json json content must not be blank
|
||||
* @param type object type must not be null
|
||||
* @param <T> target object type
|
||||
* @return object specified type
|
||||
* @throws IOException throws when fail to convert
|
||||
*/
|
||||
@NonNull
|
||||
public static <T> T jsonToObject(@NonNull String json, @NonNull Class<T> type) throws IOException {
|
||||
return jsonToObject(json, type, DEFAULT_JSON_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts json to the object specified type.
|
||||
*
|
||||
* @param json json content must not be blank
|
||||
* @param type object type must not be null
|
||||
* @param objectMapper object mapper must not be null
|
||||
* @param <T> target object type
|
||||
* @return object specified type
|
||||
* @throws IOException throws when fail to convert
|
||||
*/
|
||||
@NonNull
|
||||
public static <T> T jsonToObject(@NonNull String json, @NonNull Class<T> type, @NonNull ObjectMapper objectMapper) throws IOException {
|
||||
Assert.hasText(json, "Json content must not be blank");
|
||||
Assert.notNull(type, "Target type must not be null");
|
||||
Assert.notNull(objectMapper, "Object mapper must not null");
|
||||
|
||||
return objectMapper.readValue(json, type);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Converts input stream to object specified type.
|
||||
// *
|
||||
// * @param inputStream input stream must not be null
|
||||
// * @param type object type must not be null
|
||||
// * @param <T> target object type
|
||||
// * @return object specified type
|
||||
// * @throws IOException throws when fail to convert
|
||||
// */
|
||||
// @NonNull
|
||||
// public static <T> T inputStreamToObject(@NonNull InputStream inputStream, @NonNull Class<T> type) throws IOException {
|
||||
// return inputStreamToObject(inputStream, type, null);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Converts input stream to object specified type.
|
||||
// *
|
||||
// * @param inputStream input stream must not be null
|
||||
// * @param type object type must not be null
|
||||
// * @param objectMapper object mapper must not be null
|
||||
// * @param <T> target object type
|
||||
// * @return object specified type
|
||||
// * @throws IOException throws when fail to convert
|
||||
// */
|
||||
// @NonNull
|
||||
// public static <T> T inputStreamToObject(@NonNull InputStream inputStream, @NonNull Class<T> type, @NonNull ObjectMapper objectMapper) throws IOException {
|
||||
// Assert.notNull(inputStream, "Input stream must not be null");
|
||||
//
|
||||
// String json = IOUtils.toString(inputStream);
|
||||
// return jsonToObject(json, type, objectMapper);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Converts object to json format.
|
||||
*
|
||||
* @param source source object must not be null
|
||||
* @return json format of the source object
|
||||
* @throws JsonProcessingException throws when fail to convert
|
||||
*/
|
||||
@NonNull
|
||||
public static String objectToJson(@NonNull Object source) throws JsonProcessingException {
|
||||
return objectToJson(source, DEFAULT_JSON_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts object to json format.
|
||||
*
|
||||
* @param source source object must not be null
|
||||
* @param objectMapper object mapper must not be null
|
||||
* @return json format of the source object
|
||||
* @throws JsonProcessingException throws when fail to convert
|
||||
*/
|
||||
@NonNull
|
||||
public static String objectToJson(@NonNull Object source, @NonNull ObjectMapper objectMapper) throws JsonProcessingException {
|
||||
Assert.notNull(source, "Source object must not be null");
|
||||
Assert.notNull(objectMapper, "Object mapper must not null");
|
||||
|
||||
return objectMapper.writeValueAsString(source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a map to the object specified type.
|
||||
*
|
||||
* @param sourceMap source map must not be empty
|
||||
* @param type object type must not be null
|
||||
* @param <T> target object type
|
||||
* @return the object specified type
|
||||
* @throws IOException throws when fail to convert
|
||||
*/
|
||||
@NonNull
|
||||
public static <T> T mapToObject(@NonNull Map<String, ?> sourceMap, @NonNull Class<T> type) throws IOException {
|
||||
return mapToObject(sourceMap, type, DEFAULT_JSON_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a map to the object specified type.
|
||||
*
|
||||
* @param sourceMap source map must not be empty
|
||||
* @param type object type must not be null
|
||||
* @param objectMapper object mapper must not be null
|
||||
* @param <T> target object type
|
||||
* @return the object specified type
|
||||
* @throws IOException throws when fail to convert
|
||||
*/
|
||||
@NonNull
|
||||
public static <T> T mapToObject(@NonNull Map<String, ?> sourceMap, @NonNull Class<T> type, @NonNull ObjectMapper objectMapper) throws IOException {
|
||||
Assert.notEmpty(sourceMap, "Source map must not be empty");
|
||||
|
||||
// Serialize the map
|
||||
String json = objectToJson(sourceMap, objectMapper);
|
||||
|
||||
// Deserialize the json format of the map
|
||||
return jsonToObject(json, type, objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a source object to a map
|
||||
*
|
||||
* @param source source object must not be null
|
||||
* @return a map
|
||||
* @throws IOException throws when fail to convert
|
||||
*/
|
||||
@NonNull
|
||||
public static Map<?, ?> objectToMap(@NonNull Object source) throws IOException {
|
||||
return objectToMap(source, DEFAULT_JSON_MAPPER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a source object to a map
|
||||
*
|
||||
* @param source source object must not be null
|
||||
* @param objectMapper object mapper must not be null
|
||||
* @return a map
|
||||
* @throws IOException throws when fail to convert
|
||||
*/
|
||||
@NonNull
|
||||
public static Map<?, ?> objectToMap(@NonNull Object source, @NonNull ObjectMapper objectMapper) throws IOException {
|
||||
|
||||
// Serialize the source object
|
||||
String json = objectToJson(source, objectMapper);
|
||||
|
||||
// Deserialize the json
|
||||
return jsonToObject(json, Map.class, objectMapper);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +1,15 @@
|
|||
package cc.ryanc.halo.web.controller.admin;
|
||||
|
||||
import cc.ryanc.halo.model.dto.LogOutputDTO;
|
||||
import cc.ryanc.halo.model.dto.UserOutputDTO;
|
||||
import cc.ryanc.halo.model.dto.post.PostSimpleOutputDTO;
|
||||
import cc.ryanc.halo.model.entity.User;
|
||||
import cc.ryanc.halo.model.vo.CommentVO;
|
||||
import cc.ryanc.halo.service.AttachmentService;
|
||||
import cc.ryanc.halo.service.CommentService;
|
||||
import cc.ryanc.halo.service.LogService;
|
||||
import cc.ryanc.halo.service.PostService;
|
||||
import cc.ryanc.halo.service.*;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* Admin controller
|
||||
|
@ -31,14 +29,18 @@ public class AdminController {
|
|||
|
||||
private final LogService logService;
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
public AdminController(PostService postService,
|
||||
CommentService commentService,
|
||||
AttachmentService attachmentService,
|
||||
LogService logService) {
|
||||
LogService logService,
|
||||
UserService userService) {
|
||||
this.postService = postService;
|
||||
this.commentService = commentService;
|
||||
this.attachmentService = attachmentService;
|
||||
this.logService = logService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,7 +49,7 @@ public class AdminController {
|
|||
* @param model model
|
||||
* @return template path: admin/admin_index.ftl
|
||||
*/
|
||||
@GetMapping(value = {"", "/index"})
|
||||
@GetMapping(value = {"", "index"})
|
||||
public String admin(Model model) {
|
||||
|
||||
Page<PostSimpleOutputDTO> postPage = postService.listLatest(5);
|
||||
|
@ -67,9 +69,14 @@ public class AdminController {
|
|||
return "admin/admin_index";
|
||||
}
|
||||
|
||||
@GetMapping(value = "/login")
|
||||
public String login(){
|
||||
@PostMapping("login")
|
||||
@ResponseBody
|
||||
public UserOutputDTO login(@RequestParam("username") String username,
|
||||
@RequestParam("password") String password) {
|
||||
// Login
|
||||
User user = userService.login(username, password);
|
||||
|
||||
return "";
|
||||
// Convert and return
|
||||
return new UserOutputDTO().convertFrom(user);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue