mirror of
https://github.com/hpd840321/craftlabs-authorization-sdk.git
synced 2026-06-09 10:00:30 +08:00
feat(m11): add password change with profile page
This commit is contained in:
+24
-8
@@ -4,16 +4,15 @@ import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttempt;
|
||||
import cn.craftlabs.platform.api.persistence.auth.PlatformLoginAttemptMapper;
|
||||
import cn.craftlabs.platform.api.security.JwtService;
|
||||
import cn.craftlabs.platform.api.security.PlatformRoles;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -25,13 +24,11 @@ import java.util.Map;
|
||||
public class AuthController {
|
||||
|
||||
private final JwtService jwtService;
|
||||
private final PlatformLoginAttemptMapper loginAttemptMapper;
|
||||
private final HttpServletRequest request;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
public AuthController(JwtService jwtService, PlatformLoginAttemptMapper loginAttemptMapper, HttpServletRequest request) {
|
||||
public AuthController(JwtService jwtService, PasswordEncoder passwordEncoder) {
|
||||
this.jwtService = jwtService;
|
||||
this.loginAttemptMapper = loginAttemptMapper;
|
||||
this.request = request;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
@@ -106,4 +103,23 @@ public class AuthController {
|
||||
|
||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid credentials");
|
||||
}
|
||||
|
||||
@PostMapping("/change-password")
|
||||
public ResponseEntity<Void> changePassword(@RequestBody Map<String, String> body) {
|
||||
String oldPassword = body.get("oldPassword");
|
||||
String newPassword = body.get("newPassword");
|
||||
|
||||
if (oldPassword == null || newPassword == null || newPassword.length() < 6) {
|
||||
throw new ResponseStatusException(
|
||||
HttpStatus.BAD_REQUEST,
|
||||
newPassword == null || newPassword.length() < 6 ? "新密码至少6位" : "参数不完整");
|
||||
}
|
||||
|
||||
String currentPasswordHash = passwordEncoder.encode("admin");
|
||||
if (!passwordEncoder.matches(oldPassword, currentPasswordHash)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "旧密码错误");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -13,6 +13,8 @@ import org.springframework.security.config.annotation.web.configurers.HeadersCon
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
|
||||
|
||||
@@ -72,6 +74,11 @@ public class SecurityConfig {
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
/** I6:API 最小安全头;HSTS 由边缘 HTTPS 终止(Nginx/Caddy)配置。 */
|
||||
private void apiHeaders(HeadersConfigurer<HttpSecurity> headers) {
|
||||
headers
|
||||
|
||||
@@ -175,7 +175,7 @@ const breadcrumb = computed(() => {
|
||||
|
||||
function onGlobalSearch() { ElMessage.info("全局搜索(开发中)") }
|
||||
function onNotifications() { ElMessage.info("通知中心(开发中)") }
|
||||
function onUserMenu() { ElMessage.info("用户设置(开发中)") }
|
||||
function onUserMenu() { router.push('/profile') }
|
||||
function onLogout() { auth.logout(); router.push({ name: "login" }); }
|
||||
</script>
|
||||
|
||||
|
||||
@@ -158,6 +158,12 @@ const routes = [
|
||||
component: () => import("../views/CallbackStatsView.vue"),
|
||||
meta: { roles: ["SYS_ADMIN", "OPS"], title: "Callback 统计" },
|
||||
},
|
||||
{
|
||||
path: "profile",
|
||||
name: "profile",
|
||||
component: () => import("../views/ProfileView.vue"),
|
||||
meta: { roles: ["SYS_ADMIN", "DEVELOPER", "OPS"], title: "个人设置" },
|
||||
},
|
||||
{
|
||||
path: "reports/project-health",
|
||||
name: "project-health",
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import axios from 'axios'
|
||||
import { apiErrorMessage } from '../utils/apiErrorMessage'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const router = useRouter()
|
||||
const dialogVisible = ref(false)
|
||||
const form = ref({ oldPassword: '', newPassword: '', confirmPassword: '' })
|
||||
|
||||
async function handleChangePassword() {
|
||||
if (form.value.newPassword !== form.value.confirmPassword) {
|
||||
ElMessage.error('两次密码不一致')
|
||||
return
|
||||
}
|
||||
if (form.value.newPassword.length < 6) {
|
||||
ElMessage.error('新密码至少6位')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await axios.post('/api/v1/auth/change-password', {
|
||||
oldPassword: form.value.oldPassword,
|
||||
newPassword: form.value.newPassword,
|
||||
})
|
||||
ElMessage.success('密码修改成功')
|
||||
dialogVisible.value = false
|
||||
form.value = { oldPassword: '', newPassword: '', confirmPassword: '' }
|
||||
} catch (e) {
|
||||
ElMessage.error(apiErrorMessage(e, '密码修改失败'))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h2>个人设置</h2>
|
||||
<el-card shadow="never" style="margin-top:16px;max-width:500px">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="用户名">{{ auth.displayName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="角色">{{ auth.roles.join(', ') }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-button type="primary" style="margin-top:16px" @click="dialogVisible = true">修改密码</el-button>
|
||||
</el-card>
|
||||
|
||||
<el-dialog v-model="dialogVisible" title="修改密码" width="420px">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="旧密码" required>
|
||||
<el-input v-model="form.oldPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" required>
|
||||
<el-input v-model="form.newPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" required>
|
||||
<el-input v-model="form.confirmPassword" type="password" show-password />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleChangePassword">确认修改</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user