feat: 开发中...

This commit is contained in:
2023-03-31 15:22:02 +08:00
parent 0675cdc58a
commit eaf234d062
14 changed files with 123 additions and 39 deletions

View File

@@ -0,0 +1,49 @@
package cn.hamster3.application.blog.config;
import jakarta.annotation.Resource;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.cache.Cache;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Slf4j
@Component
public class AuthTokenFilter extends OncePerRequestFilter {
@Resource(name = "userCache")
private Cache userCache;
public AuthTokenFilter() {
}
@Override
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("token");
log.info("request token: {}", token);
if (token == null || token.isBlank()) {
filterChain.doFilter(request, response);
return;
}
UserDetails user = userCache.get(token, UserDetails.class);
if (user == null) {
filterChain.doFilter(request, response);
return;
}
SecurityContext context = SecurityContextHolder.getContext();
UsernamePasswordAuthenticationToken authentication = UsernamePasswordAuthenticationToken.authenticated(
user, "", user.getAuthorities()
);
context.setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}

View File

@@ -1,13 +1,10 @@
package cn.hamster3.application.blog.config; package cn.hamster3.application.blog.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.cache.Cache;
import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
@@ -21,6 +18,11 @@ public class WebConfig {
@Resource @Resource
private UserDetailsService userDetailsService; private UserDetailsService userDetailsService;
@Bean(name = "userCache")
public Cache getUserCache() {
return new ConcurrentMapCache("user-cache");
}
@Bean @Bean
public PasswordEncoder getPasswordEncoder() { public PasswordEncoder getPasswordEncoder() {
return new BCryptPasswordEncoder(5); return new BCryptPasswordEncoder(5);
@@ -35,12 +37,12 @@ public class WebConfig {
return new ProviderManager(provider); return new ProviderManager(provider);
} }
@Bean // @Bean
@ConditionalOnMissingBean(ObjectMapper.class) // @ConditionalOnMissingBean(ObjectMapper.class)
public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) { // public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
ObjectMapper objectMapper = builder.createXmlMapper(false).build(); // ObjectMapper objectMapper = builder.createXmlMapper(false).build();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return objectMapper; // return objectMapper;
} // }
} }

View File

@@ -2,23 +2,20 @@ package cn.hamster3.application.blog.config.security;
import cn.hamster3.application.blog.entity.repo.UserRepository; import cn.hamster3.application.blog.entity.repo.UserRepository;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Service;
import java.util.Collections; import java.util.Collections;
@Slf4j @Service
@Component
public class BlogUserDetailService implements UserDetailsService { public class BlogUserDetailService implements UserDetailsService {
@Resource @Resource
private UserRepository userRepo; private UserRepository userRepo;
@Override @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("find user by email: {}", username);
return userRepo.findByEmailIgnoreCase(username) return userRepo.findByEmailIgnoreCase(username)
.map(user -> new BlogUser( .map(user -> new BlogUser(
user.getEmail(), user.getEmail(),

View File

@@ -21,7 +21,6 @@ public class SecurityConfig {
.anyRequest().authenticated()) .anyRequest().authenticated())
.cors().and() .cors().and()
.csrf().disable() .csrf().disable()
.formLogin().and()
.httpBasic().and() .httpBasic().and()
.build(); .build();
} }

View File

@@ -13,6 +13,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@@ -29,8 +30,8 @@ public class UserController {
@PostMapping("/login") @PostMapping("/login")
@Operation(summary = "登录用户") @Operation(summary = "登录用户")
public ResponseVO<Void> loginUser(@RequestBody @Valid UserLoginRequireVO requireVO) { public ResponseVO<Void> loginUser(@RequestBody @Valid UserLoginRequireVO requireVO, HttpServletResponse response) {
return userService.loginUser(requireVO); return userService.loginUser(requireVO, response);
} }
@GetMapping("/current") @GetMapping("/current")

View File

@@ -8,13 +8,14 @@ import cn.hamster3.application.blog.vo.user.UserCreateRequireVO;
import cn.hamster3.application.blog.vo.user.UserInfoResponseVO; import cn.hamster3.application.blog.vo.user.UserInfoResponseVO;
import cn.hamster3.application.blog.vo.user.UserLoginRequireVO; import cn.hamster3.application.blog.vo.user.UserLoginRequireVO;
import cn.hamster3.application.blog.vo.user.UserUpdateRequireVO; import cn.hamster3.application.blog.vo.user.UserUpdateRequireVO;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import java.util.UUID; import java.util.UUID;
public interface IUserService { public interface IUserService {
@NotNull ResponseVO<Void> loginUser(@NotNull UserLoginRequireVO requireVO); @NotNull ResponseVO<Void> loginUser(@NotNull UserLoginRequireVO requireVO, @NotNull HttpServletResponse response);
@NotNull ResponseVO<UserInfoResponseVO> getCurrentUserInfo(); @NotNull ResponseVO<UserInfoResponseVO> getCurrentUserInfo();

View File

@@ -20,9 +20,14 @@ import cn.hamster3.application.blog.vo.user.UserInfoResponseVO;
import cn.hamster3.application.blog.vo.user.UserLoginRequireVO; import cn.hamster3.application.blog.vo.user.UserLoginRequireVO;
import cn.hamster3.application.blog.vo.user.UserUpdateRequireVO; import cn.hamster3.application.blog.vo.user.UserUpdateRequireVO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.cache.Cache;
import org.springframework.data.domain.Pageable; 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.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -48,8 +53,23 @@ public class UserService implements IUserService {
@Resource @Resource
private AttachRepository attachRepo; private AttachRepository attachRepo;
@Resource(name = "userCache")
private Cache userCache;
@Resource
private AuthenticationManager authenticationManager;
@Override @Override
public @NotNull ResponseVO<Void> loginUser(@NotNull UserLoginRequireVO requireVO) { public @NotNull ResponseVO<Void> loginUser(@NotNull UserLoginRequireVO requireVO, @NotNull HttpServletResponse response) {
Authentication authenticate = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(requireVO.getEmail(), requireVO.getPassword())
);
log.info("authenticate: {}", authenticate);
if (!authenticate.isAuthenticated()) {
return ResponseVO.failed("login failed.");
}
UUID uuid = UUID.randomUUID();
userCache.put(uuid.toString(), authenticate.getPrincipal());
response.addHeader("token", uuid.toString());
return ResponseVO.success(); return ResponseVO.success();
} }

View File

@@ -14,7 +14,7 @@ import java.util.UUID;
@Slf4j @Slf4j
public class BlogUtils { public class BlogUtils {
@NotNull @Nullable
public static Authentication getCurrentAuthentication() { public static Authentication getCurrentAuthentication() {
return SecurityContextHolder.getContext().getAuthentication(); return SecurityContextHolder.getContext().getAuthentication();
} }
@@ -23,6 +23,10 @@ public class BlogUtils {
public static Optional<BlogUser> getCurrentUser() { public static Optional<BlogUser> getCurrentUser() {
Authentication authentication = getCurrentAuthentication(); Authentication authentication = getCurrentAuthentication();
log.info("=============================="); log.info("==============================");
if (authentication == null) {
log.info("current user authentication: null");
return Optional.empty();
}
log.info("current user authentication: {}", authentication); log.info("current user authentication: {}", authentication);
log.info("current user authentication getPrincipal: {}", authentication.getPrincipal()); log.info("current user authentication getPrincipal: {}", authentication.getPrincipal());
log.info("current user authentication getCredentials: {}", authentication.getCredentials()); log.info("current user authentication getCredentials: {}", authentication.getCredentials());

View File

@@ -1,5 +1,6 @@
package cn.hamster3.application.blog.vo; package cn.hamster3.application.blog.vo;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@@ -7,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseVO<T> { public class ResponseVO<T> {
@NotNull @NotNull
private Integer code; private Integer code;

View File

@@ -6,7 +6,7 @@
<link rel="icon" href="/favicon.ico"> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="叁只仓鼠的个人博客"> <meta name="description" content="叁只仓鼠的个人博客">
<title>Vite App</title> <title>网站标题</title>
</head> </head>
<body> <body>

View File

@@ -3,7 +3,7 @@ import { ref, onMounted } from 'vue';
import { RouterLink, RouterView } from 'vue-router' import { RouterLink, RouterView } from 'vue-router'
import { SettingControllerApiFactory, UserControllerApiFactory, UserInfoResponseVORoleEnum, type UserInfoResponseVO } from '@/api-base'; import { SettingControllerApiFactory, UserControllerApiFactory, UserInfoResponseVORoleEnum, type UserInfoResponseVO } from '@/api-base';
let title = ref<string>() let title = ref<string>("网站标题")
let navigationMenu = ref([{ let navigationMenu = ref([{
url: "/about", url: "/about",
text: "关于我" text: "关于我"
@@ -14,10 +14,9 @@ onMounted(() => {
SettingControllerApiFactory().getSettingContent("site.title") SettingControllerApiFactory().getSettingContent("site.title")
.then(response => { .then(response => {
let vo = response.data; let vo = response.data;
title.value = vo.data ?? "网站标题" let siteTitle: string = vo.data ?? "网站标题"
}) title.value = siteTitle
.catch(err => { document.title = siteTitle
console.error(err);
}) })
SettingControllerApiFactory().getSettingContent("site.navigation.menus") SettingControllerApiFactory().getSettingContent("site.navigation.menus")
@@ -28,14 +27,6 @@ onMounted(() => {
navigationMenu.value = navigationMenuObject navigationMenu.value = navigationMenuObject
} }
}) })
UserControllerApiFactory().getCurrentUserInfo()
.then(response => {
let vo = response.data;
if (vo.code === 200) {
console.log(vo.data);
}
})
}); });
</script> </script>

View File

@@ -13,6 +13,11 @@ const router = createRouter({
name: 'register', name: 'register',
component: () => import('@/views/RegisterView.vue') component: () => import('@/views/RegisterView.vue')
}, },
{
path: '/tags',
name: 'tags',
component: () => import('@/views/TagsView.vue')
},
{ {
path: '/login', path: '/login',
name: 'login', name: 'login',

View File

@@ -2,6 +2,7 @@
import { UserControllerApiFactory } from '@/api-base'; import { UserControllerApiFactory } from '@/api-base';
import type UserLoginRequireVO from '@/api-base'; import type UserLoginRequireVO from '@/api-base';
import { reactive } from 'vue'; import { reactive } from 'vue';
const form = reactive({ const form = reactive({
email: '', email: '',
password: '' password: ''
@@ -14,14 +15,13 @@ const onSubmit = () => {
if (vo.data === 200) { if (vo.data === 200) {
console.log('login success!') console.log('login success!')
} else { } else {
console.log('login failed!')
} }
}) })
} }
</script> </script>
<template> <template>
<el-alert title="error alert" type="error" show-icon />
<div class="login-div"> <div class="login-div">
<el-card> <el-card>
<el-form :model="form" label-width="60px"> <el-form :model="form" label-width="60px">

View File

@@ -0,0 +1,13 @@
<template>
<div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>