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.persistence.auth.PlatformLoginAttemptMapper;
|
||||||
import cn.craftlabs.platform.api.security.JwtService;
|
import cn.craftlabs.platform.api.security.JwtService;
|
||||||
import cn.craftlabs.platform.api.security.PlatformRoles;
|
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.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.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -25,13 +24,11 @@ import java.util.Map;
|
|||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private final JwtService jwtService;
|
private final JwtService jwtService;
|
||||||
private final PlatformLoginAttemptMapper loginAttemptMapper;
|
private final PasswordEncoder passwordEncoder;
|
||||||
private final HttpServletRequest request;
|
|
||||||
|
|
||||||
public AuthController(JwtService jwtService, PlatformLoginAttemptMapper loginAttemptMapper, HttpServletRequest request) {
|
public AuthController(JwtService jwtService, PasswordEncoder passwordEncoder) {
|
||||||
this.jwtService = jwtService;
|
this.jwtService = jwtService;
|
||||||
this.loginAttemptMapper = loginAttemptMapper;
|
this.passwordEncoder = passwordEncoder;
|
||||||
this.request = request;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/login")
|
@PostMapping("/login")
|
||||||
@@ -106,4 +103,23 @@ public class AuthController {
|
|||||||
|
|
||||||
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "invalid credentials");
|
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.config.http.SessionCreationPolicy;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
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.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
|
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy;
|
||||||
|
|
||||||
@@ -72,6 +74,11 @@ public class SecurityConfig {
|
|||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
/** I6:API 最小安全头;HSTS 由边缘 HTTPS 终止(Nginx/Caddy)配置。 */
|
/** I6:API 最小安全头;HSTS 由边缘 HTTPS 终止(Nginx/Caddy)配置。 */
|
||||||
private void apiHeaders(HeadersConfigurer<HttpSecurity> headers) {
|
private void apiHeaders(HeadersConfigurer<HttpSecurity> headers) {
|
||||||
headers
|
headers
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ const breadcrumb = computed(() => {
|
|||||||
|
|
||||||
function onGlobalSearch() { ElMessage.info("全局搜索(开发中)") }
|
function onGlobalSearch() { ElMessage.info("全局搜索(开发中)") }
|
||||||
function onNotifications() { ElMessage.info("通知中心(开发中)") }
|
function onNotifications() { ElMessage.info("通知中心(开发中)") }
|
||||||
function onUserMenu() { ElMessage.info("用户设置(开发中)") }
|
function onUserMenu() { router.push('/profile') }
|
||||||
function onLogout() { auth.logout(); router.push({ name: "login" }); }
|
function onLogout() { auth.logout(); router.push({ name: "login" }); }
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -158,6 +158,12 @@ const routes = [
|
|||||||
component: () => import("../views/CallbackStatsView.vue"),
|
component: () => import("../views/CallbackStatsView.vue"),
|
||||||
meta: { roles: ["SYS_ADMIN", "OPS"], title: "Callback 统计" },
|
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",
|
path: "reports/project-health",
|
||||||
name: "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