Compare commits

...

9 Commits

Author SHA1 Message Date
225e89df5d 重大修改 2025-05-08 15:54:59 +09:00
75103a3a8a 增加注册和登录的数据传输 2025-05-08 15:54:09 +09:00
37a8ea12c4 增加jwtfilter 2025-05-08 15:53:21 +09:00
779d94ba1b 增加jwtservice 2025-05-08 15:53:09 +09:00
f6ee15f4f9 增加securityconfig 2025-05-08 15:52:51 +09:00
3c4397f430 重写controller,增加了用户注册和登录接口 2025-05-08 15:51:59 +09:00
ab8f36d000 文件名更正 2025-05-08 15:50:04 +09:00
6e71978a77 文件名更正 2025-05-08 15:49:51 +09:00
44d934bc1c 增加jwt密钥和设置 2025-05-08 15:49:20 +09:00
22 changed files with 421 additions and 111 deletions

View File

@ -1,15 +0,0 @@
package co.jp.app.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,60 @@
package co.jp.app.config.security;
import co.jp.app.config.security.filter.JwtAuthenticationFilter;
import co.jp.app.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private UserService userService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/user/login", "/api/user/register").permitAll()
.anyRequest().authenticated()
)
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}

View File

@ -0,0 +1,58 @@
package co.jp.app.config.security.filter;
import co.jp.app.service.UserService;
import co.jp.app.service.JwtService;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtService jwtService;
@Autowired
private UserService userService;
@Override
protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain)
throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
final String jwt;
final String username;
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
jwt = authHeader.substring(7);
username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}

View File

@ -1,37 +0,0 @@
package co.jp.app.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import co.jp.app.entity.ErrorEntity;
import co.jp.app.entity.UserEntity;
import co.jp.app.service.ErraService;
import co.jp.app.service.UserService;
@CrossOrigin("http://192.168.1.50:5173")
@RestController("/api/login")
public class LoginController {
@Autowired
private UserService userService;
@Autowired
private ErraService erraService;
@GetMapping("/status")
public String getStatusByNameOrEmail() {
String input="aaa";
if (userByName == null && userByEmail == null) {
return "全項目に入力してください";
}
// 如果有找到,就固定使用 ID 1001 去查 erraEntity
ErrorEntity erra = erraService.getStatusById(1001);
return erra.getStatus();
}
}

View File

@ -0,0 +1,81 @@
package co.jp.app.controller;
import co.jp.app.common.ApiResponse;
import co.jp.app.dto.LoginDto;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.service.JwtService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import co.jp.app.entity.ErrorEntity;
import co.jp.app.entity.UserEntity;
import co.jp.app.service.ErraService;
import co.jp.app.service.UserService;
import java.util.HashMap;
import java.util.Map;
@CrossOrigin("http://192.168.1.50:5173")
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
private final AuthenticationManager authenticationManager;
private final JwtService jwtService;
@Autowired
public UserController(UserService userService, AuthenticationManager authenticationManager, JwtService jwtService) {
this.userService = userService;
this.authenticationManager = authenticationManager;
this.jwtService = jwtService;
}
@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody RegistrationDto registrationDto) {
try {
UserEntity registeredUser = userService.registerNewUser(registrationDto);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(registeredUser.getEmail()));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ApiResponse.fail("ユーザー登録失敗しました。"));
}
}
@PostMapping("/login")
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginDto loginDto) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginDto.getEmail(), loginDto.getPassword())
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String jwtToken = jwtService.generateToken(userDetails); // 生成单一的Token
Map<String, String> tokenResponse = new HashMap<>();
tokenResponse.put("token", jwtToken);
return ResponseEntity.ok(ApiResponse.success(tokenResponse));
} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ApiResponse.fail("メールアドレスまたはパスワードが間違っています。"));
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponse.fail("サーバーエラー。"));
}
}
}

View File

@ -0,0 +1,24 @@
package co.jp.app.dto;
public class LoginDto {
private String email;
private String password;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -0,0 +1,34 @@
package co.jp.app.dto;
public class RegistrationDto {
private String name;
private String email;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -7,9 +7,10 @@ import org.springframework.data.repository.query.Param;
import co.jp.app.entity.ErrorEntity;
public interface ErraRepository extends JpaRepository<ErrorEntity, Integer>{
public interface ErrRepository extends JpaRepository<ErrorEntity, Integer>{
public default ErrorEntity getById(@Param("id") int id) {
return getById(id);
}
}

View File

@ -4,14 +4,14 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import co.jp.app.entity.ErrorEntity;
import co.jp.app.repository.ErraRepository;
import co.jp.app.repository.ErrRepository;
@Service
public class ErraService {
@Autowired
ErraRepository erraRepository;
ErrRepository erraRepository;
public ErrorEntity getStatusById(int id) {
return erraRepository.getById(id);

View File

@ -0,0 +1,102 @@
package co.jp.app.service;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import java.util.function.Function;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Service
public class JwtService {
private static final Logger logger = LoggerFactory.getLogger(JwtService.class);
//log
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.token-expiration-ms}")
private long tokenExpirationMs;
private Key getSignKey() {
byte[] keyBytes = Base64.getDecoder().decode(secretKey);
return Keys.hmacShaKeyFor(keyBytes);
}
public String extractUsername(String token) {
try {
return extractClaim(token, Claims::getSubject);
} catch (ExpiredJwtException e) {
logger.warn("JWT token is expired when extracting username: {}", e.getMessage());
return e.getClaims().getSubject();
} catch (MalformedJwtException | SignatureException | UnsupportedJwtException | IllegalArgumentException e) {
logger.error("Invalid JWT token when extracting username: {}", e.getMessage());
return null;
}
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + tokenExpirationMs)) // 使用配置的过期时间
.signWith(getSignKey(), SignatureAlgorithm.HS256)
.compact();
}
public boolean isTokenValid(String token, UserDetails userDetails) {
try {
final String username = extractUsername(token);
if (username == null) {
return false;
}
return (username.equals(userDetails.getUsername()) && !isTokenActuallyExpired(token));
} catch (ExpiredJwtException e) {
logger.warn("Token validation failed: Expired JWT - {}", e.getMessage());
return false;
} catch (MalformedJwtException | SignatureException | UnsupportedJwtException | IllegalArgumentException e) {
// 这些是更严重的token结构或签名问题
logger.error("Token validation failed: Invalid JWT (format, signature, etc.) - {}", e.getMessage());
return false;
}
}
private boolean isTokenActuallyExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
}

View File

@ -1,23 +0,0 @@
package co.jp.app.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import co.jp.app.entity.PetEntity;
import co.jp.app.repository.UploadRepository;
@Service
public class UploadService {
@Autowired
private UploadRepository uploadDao;
public List<PetEntity> saveAll(Iterable<PetEntity> entities) {
return uploadDao.saveAll(entities);
}
}

View File

@ -1,52 +1,74 @@
package co.jp.app.service;
import jakarta.transaction.Transactional;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import co.jp.app.dto.RegistrationDto;
import co.jp.app.entity.UserEntity;
import co.jp.app.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import co.jp.app.entity.UserEntity;
import co.jp.app.repository.UserRepository;
import co.jp.app.entity.PetEntity;
import co.jp.app.repository.UploadRepository;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
public class UserService implements UserDetailsService {
private final UserRepository userEntityRepository;
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Autowired
public UserService(UserRepository userEntityRepository, PasswordEncoder passwordEncoder ) {
this.userEntityRepository = userEntityRepository;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Transactional // 整个注册过程应该是一个事务
public UserEntity registerNewUser(String name, String email, String rawPassword) throws Exception {
// 1. 检查邮箱是否已被注册
if (userEntityRepository.existsByEmail(email)) {
throw new Exception("错误:邮箱地址已被注册!"); // 或者自定义异常
@Transactional
public UserEntity registerNewUser(RegistrationDto registrationDto) throws Exception {
if (userRepository.existsByEmail(registrationDto.getEmail())) {
throw new Exception("错误:邮箱 " + registrationDto.getEmail() + " 已被注册!");
}
// (可选) 检查用户名是否已被注册 (如果您有用户名字段)
// if (userEntityRepository.existsByUsername(username)) {
// throw new Exception("错误:该用户名已被注册!");
// }
// 2. 创建新的 UserEntity 对象
UserEntity newUser = new UserEntity();
newUser.setName(name);
newUser.setEmail(email);
newUser.setName(registrationDto.getName());
newUser.setEmail(registrationDto.getEmail());
newUser.setPassword(passwordEncoder.encode(registrationDto.getPassword()));
// 3. 对密码进行哈希加密 (非常重要!)
// String hashedPassword = passwordEncoder.encode(rawPassword);
// newUser.setPassword(hashedPassword);
newUser.setPassword(rawPassword); // 实际项目中必须加密!这里为了简化先直接赋值
// 4. 设置其他默认属性,例如账户状态、角色等 (如果需要)
// newUser.setActive(true);
// newUser.setRoles(...);
// 5. 保存新用户到数据库
return userEntityRepository.save(newUser);
return userRepository.save(newUser);
}
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("未找到邮箱为: " + email + " 的用户"));
Collection<? extends GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); // 示例给所有用户一个ROLE_USER权限
return new User(
userEntity.getEmail(),
userEntity.getPassword(),
true, // enabled
true, // accountNonExpired
true, // credentialsNonExpired
true, // accountNonLocked
authorities // 用户的权限集合
);
}
public boolean checkPassword(UserEntity user, String rawPassword) {
return passwordEncoder.matches(rawPassword, user.getPassword());
}
}

View File

@ -7,4 +7,7 @@ spring.jpa.database-platform=org.hibernate.dialect.MySQLDialect
spring.datasource.url=jdbc:mysql://192.168.1.192:3306/dog
spring.datasource.username=coder
spring.datasource.password=coder
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
jwt.secret=wM7Pz4BxvZ5NcLaBpgJm0eRQ5ztc3W5+OPH0E7g3gcQ=
jwt.token-expiration-ms==900000