-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
503 additions
and
2 deletions.
There are no files selected for viewing
98 changes: 98 additions & 0 deletions
98
src/main/java/me/gt/snaptickets/controller/AdminUserController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package me.gt.snaptickets.controller; | ||
|
||
import io.swagger.v3.oas.annotations.Hidden; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import me.gt.snaptickets.dto.AdminUserDto; | ||
import me.gt.snaptickets.model.AdminUser; | ||
import me.gt.snaptickets.service.AdminUserService; | ||
import me.gt.snaptickets.util.PasswordUtil; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
@CrossOrigin | ||
@RestController | ||
@Hidden | ||
@RequestMapping("/admin") | ||
public class AdminUserController { | ||
|
||
@Autowired | ||
private AdminUserService userService; | ||
|
||
@Value("${jwt.admin.secret}") | ||
private String jwtSecret; | ||
|
||
@Operation(summary = "註冊管理員帳號") | ||
@PostMapping("/auth/register") | ||
public ResponseEntity<String> registerUser(@RequestBody AdminUserDto adminUserDto) { | ||
String validationMessage = PasswordUtil.validatePassword(adminUserDto.getPassword()); // 驗證密碼是否符合安全性要求 | ||
if (validationMessage != null) { | ||
return ResponseEntity.badRequest().body(validationMessage); | ||
} | ||
AdminUserService.RegistrationStatus status = userService.registerAdminUser(adminUserDto.convertToUser()); | ||
return switch (status) { | ||
case USERNAME_EXISTS -> ResponseEntity.badRequest().body("此帳號已存在"); | ||
case EMAIL_EXISTS -> ResponseEntity.badRequest().body("此信箱已被註冊"); | ||
default -> ResponseEntity.ok("註冊成功"); | ||
}; | ||
} | ||
|
||
@Operation(summary = "管理員帳號登入") | ||
@PostMapping("/auth/login") | ||
public ResponseEntity<Object> loginUser(@RequestParam String identifier, @RequestParam String password) { | ||
AdminUser user = userService.loginUser(identifier, password); | ||
if (user == null) { | ||
return ResponseEntity.badRequest().body("帳號或密碼錯誤"); | ||
} | ||
String token = PasswordUtil.generateJwtToken(user,jwtSecret); | ||
return ResponseEntity.ok().body(Map.of("username", user.getUsername(), "token", token)); | ||
} | ||
|
||
@Operation(summary = "透過帳號查詢資料") | ||
@GetMapping("/user/info/{username}") | ||
public ResponseEntity<Object> getUserByUsername(@PathVariable String username) { | ||
AdminUser user = userService.getByUsername(username); | ||
if (user == null) { | ||
return ResponseEntity.badRequest().body("查無此帳號"); | ||
} | ||
return ResponseEntity.ok(AdminUserDto.fromUser(user)); | ||
} | ||
|
||
@Operation(summary = "更新帳號資訊") | ||
@PutMapping("/user") | ||
public ResponseEntity<Object> updateUser(@RequestBody AdminUserDto user) { | ||
boolean success = userService.updateUser(user.convertToUser()); | ||
if (success) { | ||
return ResponseEntity.ok("更改資料成功"); | ||
} | ||
return ResponseEntity.badRequest().body("更改資料失敗"); | ||
} | ||
|
||
@Operation(summary = "更改帳號密碼") | ||
@PutMapping("/change-password/{username}") | ||
public ResponseEntity<String> updatePassword(@PathVariable String username, @RequestParam String oldPassword, @RequestParam String newPassword) { | ||
String validationMessage = PasswordUtil.validatePassword(newPassword); // 驗證新密碼是否符合安全性要求 | ||
if (validationMessage != null) { | ||
return ResponseEntity.badRequest().body(validationMessage); | ||
} | ||
AdminUserService.AuthStatus status = userService.updatePassword(username, oldPassword, newPassword); | ||
return switch (status) { | ||
case USER_NOT_FOUND -> ResponseEntity.badRequest().body("帳號不存在"); | ||
case PASSWORD_INCORRECT -> ResponseEntity.badRequest().body("舊密碼不正確"); | ||
case PASSWORD_SAME -> ResponseEntity.badRequest().body("新密碼與舊密碼相同"); | ||
default -> ResponseEntity.ok("更改密碼成功"); | ||
}; | ||
} | ||
|
||
@Operation(summary = "刪除帳號") | ||
@DeleteMapping("/user/{username}") | ||
public ResponseEntity<String> deleteUser(@PathVariable String username) { | ||
userService.deleteUser(username); | ||
return ResponseEntity.ok("帳號已刪除"); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
package me.gt.snaptickets.dto; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import lombok.*; | ||
import me.gt.snaptickets.model.AdminUser; | ||
|
||
@Data | ||
@Builder | ||
@ToString | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class AdminUserDto { | ||
|
||
@Schema(description = "帳號") | ||
private String username; | ||
|
||
@Schema(description = "密碼 (需包含大小寫字母、數字、特殊字元,且長度至少8)", accessMode = Schema.AccessMode.WRITE_ONLY) | ||
private String password; | ||
|
||
@Schema(description = "姓名") | ||
private String name; | ||
|
||
@Schema(description = "電子郵件") | ||
private String email; | ||
|
||
/** | ||
* 轉換成 AdminUser 物件 | ||
* @return AdminUser | ||
*/ | ||
public AdminUser convertToUser() { | ||
return AdminUser.builder() | ||
.username(username) | ||
.password(password) | ||
.name(name) | ||
.email(email) | ||
.build(); | ||
} | ||
|
||
/** | ||
* 從 User 物件轉換成 AdminUserDto 物件 | ||
* | ||
* @param user | ||
* | ||
* @return AdminUserDto | ||
*/ | ||
public static AdminUserDto fromUser(AdminUser user) { | ||
return AdminUserDto.builder() | ||
.username(user.getUsername()) | ||
.name(user.getName()) | ||
.email(user.getEmail()) | ||
.build(); | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
src/main/java/me/gt/snaptickets/filter/AdminJwtAuthenticationFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package me.gt.snaptickets.filter; | ||
|
||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.Jwts; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import me.gt.snaptickets.service.AdminUserService; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
import java.io.IOException; | ||
import java.util.ArrayList; | ||
|
||
public class AdminJwtAuthenticationFilter extends OncePerRequestFilter { | ||
|
||
@Value("${jwt.admin.secret}") | ||
private String jwtSecret; | ||
|
||
@Autowired | ||
private AdminUserService adminUserService; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
String header = request.getHeader("Admin-Authorization"); | ||
if (header != null && header.startsWith("Bearer ")) { | ||
String token = header.substring(7); | ||
try { | ||
Claims claims = Jwts.parser() | ||
.setSigningKey(jwtSecret) | ||
.build() | ||
.parseClaimsJws(token) | ||
.getBody(); | ||
|
||
String username = claims.getSubject(); | ||
String password = (String) claims.get("password"); | ||
if (username != null && password != null) { | ||
if (!adminUserService.verifyTokenLogin(username,password)) { // Token 異常 登入密碼不對 | ||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | ||
return; | ||
} | ||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>()); | ||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||
} | ||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | ||
return; | ||
} | ||
} | ||
filterChain.doFilter(request, response); | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
src/main/java/me/gt/snaptickets/mapper/AdminUserMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package me.gt.snaptickets.mapper; | ||
|
||
import me.gt.snaptickets.model.AdminUser; | ||
import org.apache.ibatis.annotations.*; | ||
|
||
@Mapper | ||
public interface AdminUserMapper { | ||
|
||
@Insert("INSERT INTO admins (username, password, name, email, permission) " + | ||
"VALUES (#{username}, #{password}, #{name}, #{email}, #{permission})") | ||
void register(AdminUser user); | ||
|
||
@Select("SELECT * FROM admins WHERE username = #{username}") | ||
AdminUser getByUsername(String username); | ||
|
||
@Select("SELECT * FROM admins WHERE email = #{email}") | ||
AdminUser getByEmail(String email); | ||
|
||
@Update("UPDATE admins SET password = #{password} WHERE username = #{username}") | ||
void updatePassword(String username, String password); | ||
|
||
@Update("UPDATE admins SET name = #{name}, email = #{email}, permission = #{permission} WHERE username = #{username}") | ||
int updateUser(AdminUser user); | ||
|
||
@Delete("DELETE FROM admins WHERE username = #{username}") | ||
void deleteUser(String username); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package me.gt.snaptickets.model; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnore; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import lombok.*; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
@Data | ||
@Builder | ||
@ToString | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
public class AdminUser { | ||
|
||
@Schema(description = "帳號") | ||
private String username; | ||
|
||
@Schema(description = "密碼") | ||
@JsonIgnore | ||
private String password; | ||
|
||
@Schema(description = "姓名") | ||
private String name; | ||
|
||
@Schema(description = "電子郵件") | ||
private String email; | ||
|
||
@Schema(description = "權限") | ||
@Builder.Default | ||
private Permission permission = Permission.MOD; | ||
|
||
@Schema(description = "建立日期") | ||
@Builder.Default | ||
private LocalDateTime createdAt = LocalDateTime.now(); | ||
|
||
enum Permission { | ||
ADMIN, MOD | ||
} | ||
} |
86 changes: 86 additions & 0 deletions
86
src/main/java/me/gt/snaptickets/service/AdminUserService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package me.gt.snaptickets.service; | ||
|
||
import me.gt.snaptickets.model.AdminUser; | ||
|
||
public interface AdminUserService { | ||
|
||
/** | ||
* 註冊會員 | ||
* | ||
* @param adminUser 管理員帳號 | ||
* @return 註冊狀態 | ||
*/ | ||
RegistrationStatus registerAdminUser(AdminUser adminUser); | ||
|
||
/** | ||
* 透過管理員名稱查詢管理員 | ||
* | ||
* @param identifier 管理員帳號(帳號或信箱) | ||
* @return 管理員資訊 | ||
*/ | ||
AdminUser loginUser(String identifier, String password); | ||
|
||
|
||
/** | ||
* 管理員Token登入 | ||
* | ||
* @param username 會員名稱 | ||
* @param password 會員密碼 | ||
* @return 驗證狀態 true: 通過 false: 失敗 | ||
*/ | ||
boolean verifyTokenLogin(String username, String password); | ||
|
||
/** | ||
* 透過管理員名稱查詢管理員 | ||
* | ||
* @param username 管理員名稱 | ||
* @return 管理員資訊 | ||
*/ | ||
AdminUser getByUsername(String username); | ||
|
||
/** | ||
* 更新管理員資訊 | ||
* | ||
* @param adminUser 管理員資訊 | ||
* | ||
* @return 更新狀態 | ||
*/ | ||
boolean updateUser(AdminUser adminUser); | ||
|
||
/** | ||
* 更新管理員密碼 | ||
* | ||
* @param username 管理員名稱 | ||
* @param oldPassword 管理員舊密碼 | ||
* @param newPassword 管理員新密碼 | ||
* | ||
* @return 帳號狀態 | ||
*/ | ||
AuthStatus updatePassword(String username, String oldPassword, String newPassword); | ||
|
||
/** | ||
* 刪除管理員 | ||
* | ||
* @param username 管理員名稱 | ||
*/ | ||
void deleteUser(String username); | ||
|
||
/** | ||
* 註冊狀態 | ||
*/ | ||
enum RegistrationStatus { | ||
USERNAME_EXISTS, | ||
EMAIL_EXISTS, | ||
SUCCESS | ||
} | ||
|
||
/** | ||
* 登入狀態 | ||
*/ | ||
enum AuthStatus { | ||
USER_NOT_FOUND, | ||
PASSWORD_INCORRECT, | ||
PASSWORD_SAME, | ||
SUCCESS | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.