feat: 开发中...

This commit is contained in:
2023-03-27 00:17:27 +08:00
parent 9949fba3ed
commit d06e6d9f16
22 changed files with 201 additions and 66 deletions

View File

@@ -71,4 +71,4 @@ processResources {
clean {
delete(files('bin'))
}
}

View File

@@ -26,8 +26,17 @@ public class WebConfig {
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.empty();
}
BlogUser user = (BlogUser) authentication.getPrincipal();
return Optional.of(user.getUuid());
System.out.println("getUserIDAuditorAware");
System.out.println(authentication.getName());
System.out.println(authentication.getPrincipal());
System.out.println(authentication.getCredentials());
System.out.println(authentication.getDetails());
System.out.println(authentication.getAuthorities());
Object userDetails = authentication.getDetails();
if (userDetails instanceof BlogUser user) {
return Optional.of(user.getUuid());
}
return Optional.empty();
};
}

View File

@@ -0,0 +1,31 @@
package cn.hamster3.application.blog.config.security;
import jakarta.annotation.Resource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
@Component
public class BlogAuthenticationManager implements AuthenticationManager {
@Resource
private UserDetailsService userDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!(authentication instanceof UsernamePasswordAuthenticationToken)) {
throw new IllegalArgumentException("BlogAuthenticationManager only support UsernamePasswordAuthenticationToken!");
}
UserDetails user = userDetailsService.loadUserByUsername((String) authentication.getPrincipal());
UsernamePasswordAuthenticationToken authenticated = UsernamePasswordAuthenticationToken.authenticated(
authentication.getPrincipal(),
authentication.getCredentials(),
user.getAuthorities()
);
authenticated.setDetails(user);
return authenticated;
}
}

View File

@@ -1,6 +1,5 @@
package cn.hamster3.application.blog.config.security;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -10,13 +9,11 @@ import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Slf4j
@Configuration
@Profile("dev")
public class DevSecurityConfiguration {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
log.info("development environment security settings enabled.");
public SecurityFilterChain getSecurityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(request -> request
.anyRequest().permitAll())
.cors().and()
@@ -29,8 +26,7 @@ public class DevSecurityConfiguration {
}
@Bean
public WebMvcConfigurer corsConfigurer() {
log.info("add cors configuration...");
public WebMvcConfigurer getWebMvcConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(@NotNull CorsRegistry registry) {
@@ -43,4 +39,5 @@ public class DevSecurityConfiguration {
}
};
}
}

View File

@@ -1,6 +1,5 @@
package cn.hamster3.application.blog.config.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@@ -8,13 +7,11 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Slf4j
@Configuration
@Profile("prod")
public class SecurityConfiguration {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
log.info("production environment security settings enabled.");
public SecurityFilterChain getSecurityFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(request -> request
.requestMatchers(HttpMethod.GET, "/", "/index", "/index.html").permitAll()
.requestMatchers(HttpMethod.GET, "/favicon.ico", "/assets/**").permitAll()
@@ -30,4 +27,5 @@ public class SecurityConfiguration {
.and()
.build();
}
}

View File

@@ -3,11 +3,13 @@ package cn.hamster3.application.blog.config.security;
import cn.hamster3.application.blog.constant.UserPermissions;
import cn.hamster3.application.blog.dao.UserRepository;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class UserDetailServiceImpl implements UserDetailsService {
@Resource
@@ -15,7 +17,8 @@ public class UserDetailServiceImpl implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return userRepo.findByEmailIgnoreCaseWithPermission(username)
log.info("find user by email: {}", username);
return userRepo.findByEmailIgnoreCase(username)
.map(user -> new BlogUser(
user.getEmail(),
user.getPassword(),

View File

@@ -8,6 +8,7 @@ import cn.hamster3.application.blog.vo.blog.BlogUpdateRequireVO;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@@ -26,13 +27,13 @@ public class BlogController {
}
@GetMapping("/{blogID}/")
public ResponseVO<BlogInfoResponseVO> getBlogInfo() {
return null;
public ResponseVO<BlogInfoResponseVO> getBlogInfo(@PathVariable Long blogID) {
return blogService.getBlogInfo(blogID);
}
@GetMapping("/")
public ResponseVO<List<BlogInfoResponseVO>> getBlogInfoList() {
return null;
public ResponseVO<List<BlogInfoResponseVO>> getBlogInfoList(int page, int size) {
return blogService.getBlogInfoList(PageRequest.of(page, size));
}
@PutMapping("/{blogID}/")

View File

@@ -1,13 +1,30 @@
package cn.hamster3.application.blog.controller;
import cn.hamster3.application.blog.vo.ResponseVO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.io.PrintWriter;
import java.io.StringWriter;
@Slf4j
@RestControllerAdvice
public class ExceptionController {
@Resource
private Environment environment;
@ExceptionHandler(Exception.class)
public ResponseVO<String> onException(Exception e) {
return ResponseVO.failed(e);
log.error("", e);
if ("dev".equals(environment.getProperty("spring.profiles.active"))) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
// StringWriter 不需要 close()
return new ResponseVO<>(403, e.getMessage(), writer.toString());
}
return ResponseVO.failed(e.getMessage());
}
}

View File

@@ -1,15 +1,16 @@
package cn.hamster3.application.blog.controller;
import cn.hamster3.application.blog.service.IUserService;
import cn.hamster3.application.blog.vo.PageableResponseVO;
import cn.hamster3.application.blog.vo.ResponseVO;
import cn.hamster3.application.blog.vo.attach.AttachInfoResponseVO;
import cn.hamster3.application.blog.vo.user.*;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@@ -27,6 +28,11 @@ public class UserController {
return userService.loginUser(requireVO);
}
@GetMapping("/current")
public ResponseVO<UserInfoResponseVO> currentUser() {
return userService.currentUser();
}
@PostMapping("/")
public ResponseVO<UserRegisterResponseVO> createUser(@RequestBody @Valid UserCreateRequireVO requireVO) {
return userService.createUser(requireVO);
@@ -38,8 +44,8 @@ public class UserController {
}
@GetMapping("/")
public ResponseVO<List<UserInfoResponseVO>> getAllUserInfo() {
return ResponseVO.success(new ArrayList<>());
public ResponseVO<PageableResponseVO<UserInfoResponseVO>> getAllUserInfo(int page, int size) {
return userService.getAllUserInfo(PageRequest.of(page, size));
}
@GetMapping("/{userID}/")

View File

@@ -2,6 +2,7 @@ package cn.hamster3.application.blog.dao;
import cn.hamster3.application.blog.entity.BlogEntity;
import cn.hamster3.application.blog.entity.UserEntity;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
@@ -12,12 +13,13 @@ import java.util.UUID;
public interface UserRepository extends JpaRepository<UserEntity, UUID>, JpaSpecificationExecutor<BlogEntity> {
@EntityGraph(attributePaths = {"permissions"})
Optional<UserEntity> findByEmailIgnoreCaseWithPermission(String email);
Optional<UserEntity> findByEmailIgnoreCase(String email);
boolean existsByNicknameIgnoreCase(String nickname);
boolean existsByEmailIgnoreCase(String email);
@EntityGraph(attributePaths = {"attachEntities"})
UserEntity findByIdWithAttach(UUID uuid);
@NotNull
Optional<UserEntity> findById(@NotNull UUID uuid);
}

View File

@@ -7,6 +7,7 @@ import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.util.ArrayList;
import java.util.Date;
@@ -18,6 +19,7 @@ import java.util.List;
@Getter
@Entity
@Table(name = "blog_entity")
@EntityListeners(AuditingEntityListener.class)
public class BlogEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@@ -44,10 +46,12 @@ public class BlogEntity {
private UserEntity uploader;
@CreatedDate
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "create_time", nullable = false)
private Date createTime;
@LastModifiedDate
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "update_time", nullable = false)
private Date updateTime;

View File

@@ -32,7 +32,7 @@ public class UserEntity {
private String nickname;
@Setter
@Column(name = "password", nullable = false, length = 32)
@Column(name = "password", nullable = false, length = 60)
private String password;
@Setter

View File

@@ -1,13 +1,19 @@
package cn.hamster3.application.blog.entity.mapper;
import cn.hamster3.application.blog.entity.AttachEntity;
import cn.hamster3.application.blog.entity.UserEntity;
import cn.hamster3.application.blog.vo.attach.AttachInfoResponseVO;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;
import java.util.UUID;
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE, componentModel = MappingConstants.ComponentModel.SPRING)
public interface AttachMapper {
AttachInfoResponseVO entityToInfoVO(AttachEntity entity);
default UUID map(UserEntity value) {
return value.getId();
}
}

View File

@@ -2,8 +2,16 @@ package cn.hamster3.application.blog.service;
import cn.hamster3.application.blog.vo.ResponseVO;
import cn.hamster3.application.blog.vo.blog.BlogCreateRequireVO;
import cn.hamster3.application.blog.vo.blog.BlogInfoResponseVO;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.PageRequest;
import java.util.List;
public interface IBlogService {
@NotNull ResponseVO<Long> createBlog(@NotNull BlogCreateRequireVO requireVO);
@NotNull ResponseVO<BlogInfoResponseVO> getBlogInfo(@NotNull Long blogID);
@NotNull ResponseVO<List<BlogInfoResponseVO>> getBlogInfoList(@NotNull PageRequest page);
}

View File

@@ -1,9 +1,11 @@
package cn.hamster3.application.blog.service;
import cn.hamster3.application.blog.vo.PageableResponseVO;
import cn.hamster3.application.blog.vo.ResponseVO;
import cn.hamster3.application.blog.vo.attach.AttachInfoResponseVO;
import cn.hamster3.application.blog.vo.user.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
@@ -12,10 +14,14 @@ import java.util.UUID;
public interface IUserService {
@NotNull ResponseVO<Void> loginUser(@NotNull UserLoginRequireVO requireVO);
@NotNull ResponseVO<UserInfoResponseVO> currentUser();
@NotNull ResponseVO<UserRegisterResponseVO> createUser(@NotNull UserCreateRequireVO requireVO);
@NotNull ResponseVO<Void> updateUser(@NotNull UserUpdateRequireVO requireVO);
@NotNull ResponseVO<PageableResponseVO<UserInfoResponseVO>> getAllUserInfo(@NotNull Pageable pageable);
@NotNull ResponseVO<UserInfoResponseVO> getUserInfo(UUID id);
@NotNull ResponseVO<List<AttachInfoResponseVO>> getUserAttaches(@PathVariable UUID userID);

View File

@@ -6,10 +6,14 @@ import cn.hamster3.application.blog.entity.mapper.BlogMapper;
import cn.hamster3.application.blog.service.IBlogService;
import cn.hamster3.application.blog.vo.ResponseVO;
import cn.hamster3.application.blog.vo.blog.BlogCreateRequireVO;
import cn.hamster3.application.blog.vo.blog.BlogInfoResponseVO;
import jakarta.annotation.Resource;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class BlogService implements IBlogService {
@Resource
@@ -23,4 +27,20 @@ public class BlogService implements IBlogService {
BlogEntity save = blogRepo.save(entity);
return ResponseVO.success(save.getId());
}
@Override
public @NotNull ResponseVO<BlogInfoResponseVO> getBlogInfo(@NotNull Long blogID) {
return blogRepo.findById(blogID)
.map(o -> ResponseVO.success(blogMapper.entityToInfoVO(o)))
.orElseThrow(() -> new IllegalArgumentException("未找到该文章!"));
}
@Override
public @NotNull ResponseVO<List<BlogInfoResponseVO>> getBlogInfoList(@NotNull PageRequest page) {
return ResponseVO.success(
blogRepo.findAll(page).stream()
.map(o -> blogMapper.entityToInfoVO(o))
.toList()
);
}
}

View File

@@ -7,15 +7,17 @@ import cn.hamster3.application.blog.entity.UserEntity;
import cn.hamster3.application.blog.entity.mapper.AttachMapper;
import cn.hamster3.application.blog.entity.mapper.UserMapper;
import cn.hamster3.application.blog.service.IUserService;
import cn.hamster3.application.blog.vo.PageableResponseVO;
import cn.hamster3.application.blog.vo.ResponseVO;
import cn.hamster3.application.blog.vo.attach.AttachInfoResponseVO;
import cn.hamster3.application.blog.vo.user.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.Pageable;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@@ -24,6 +26,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import java.util.List;
import java.util.UUID;
@Slf4j
@Service
public class UserService implements IUserService {
@Resource
@@ -43,34 +46,28 @@ public class UserService implements IUserService {
@Override
public @NotNull ResponseVO<Void> loginUser(@NotNull UserLoginRequireVO requireVO) {
UserEntity userEntity = userRepo.findByEmailIgnoreCaseWithPermission(requireVO.getEmail()).orElse(null);
if (userEntity == null) {
return ResponseVO.failed("未找到该邮箱!");
}
if (!passwordEncoder.matches(requireVO.getPassword(), userEntity.getPassword())) {
return ResponseVO.failed("密码错误!");
}
List<SimpleGrantedAuthority> userPermissions = userEntity.getPermissions().stream()
.map(UserPermissions::getAuthority)
.toList();
BlogUser blogUser = new BlogUser(
userEntity.getEmail(),
userEntity.getPassword(),
userPermissions,
userEntity.getId()
);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
blogUser,
requireVO.getPassword(),
blogUser.getAuthorities()
);
authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(token);
Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
requireVO.getEmail(),
requireVO.getPassword()
));
SecurityContextHolder.getContext().setAuthentication(authenticate);
return ResponseVO.success();
}
@Override
public @NotNull ResponseVO<UserInfoResponseVO> currentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object userDetails = authentication.getDetails();
if (userDetails instanceof BlogUser user) {
return ResponseVO.success(
userRepo.findById(user.getUuid())
.map(o -> userMapper.entityToInfoVO(o))
.orElse(null)
);
}
return ResponseVO.failed("not login.");
}
@Override
public @NotNull ResponseVO<UserRegisterResponseVO> createUser(@NotNull UserCreateRequireVO requireVO) {
UserEntity entity = userMapper.voToEntity(requireVO);
@@ -85,6 +82,7 @@ public class UserService implements IUserService {
}
entity.setPassword(passwordEncoder.encode(entity.getPassword()));
log.info("prepare to save userinfo: {}", entity);
UserEntity save = userRepo.save(entity);
return ResponseVO.success("注册成功!", userMapper.entityToRegisterVO(save));
@@ -136,6 +134,13 @@ public class UserService implements IUserService {
return ResponseVO.success();
}
@Override
public @NotNull ResponseVO<PageableResponseVO<UserInfoResponseVO>> getAllUserInfo(@NotNull Pageable pageable) {
return PageableResponseVO.success(
userRepo.findAll(pageable).map(o -> userMapper.entityToInfoVO(o))
);
}
@Override
public @NotNull ResponseVO<UserInfoResponseVO> getUserInfo(UUID id) {
return userRepo.findById(id)
@@ -145,7 +150,7 @@ public class UserService implements IUserService {
@Override
public @NotNull ResponseVO<List<AttachInfoResponseVO>> getUserAttaches(@PathVariable UUID userID) {
UserEntity userEntity = userRepo.findByIdWithAttach(userID);
UserEntity userEntity = userRepo.findById(userID).orElse(null);
if (userEntity == null) {
return ResponseVO.failed("未找到该用户!");
}

View File

@@ -0,0 +1,31 @@
package cn.hamster3.application.blog.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.data.domain.Page;
import java.util.List;
@Data
@AllArgsConstructor
public class PageableResponseVO<T> {
private int page;
private int size;
private long totalElements;
private int totalPage;
private List<T> data;
public static <T> ResponseVO<PageableResponseVO<T>> success(Page<T> page) {
return ResponseVO.success(of(page));
}
public static <T> PageableResponseVO<T> of(Page<T> page) {
return new PageableResponseVO<>(
page.getNumber(),
page.getSize(),
page.getTotalElements(),
page.getTotalPages(),
page.getContent()
);
}
}

View File

@@ -5,9 +5,6 @@ import lombok.Data;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.PrintWriter;
import java.io.StringWriter;
@Data
@AllArgsConstructor
public class ResponseVO<T> {
@@ -34,11 +31,4 @@ public class ResponseVO<T> {
return new ResponseVO<>(403, msg, null);
}
public static ResponseVO<String> failed(Exception e) {
StringWriter writer = new StringWriter();
e.printStackTrace(new PrintWriter(writer));
// StringWriter 不需要 close()
return new ResponseVO<>(403, e.getMessage(), writer.toString());
}
}

View File

@@ -1,14 +1,17 @@
package cn.hamster3.application.blog.vo.user;
import cn.hamster3.application.blog.constant.UserPermissions;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.Set;
import java.util.UUID;
@Data
@AllArgsConstructor
public class UserInfoResponseVO {
private UUID id;
private String email;
private String nickname;
private String password;
private Set<UserPermissions> permissions;
}

View File

@@ -2,11 +2,9 @@ package cn.hamster3.application.blog.vo.user;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class UserLoginRequireVO {
@Pattern(regexp = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$", message = "邮箱不合法!")
private String email;

View File

@@ -7,7 +7,7 @@ spring:
ddl-auto: update
# open-in-view: true
show-ddl: true
# show-sql: true
show-sql: true
autoconfigure:
exclude:
# - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration