目录
登录服务实现UserService与UserServiceImpl
需求:登录功能的实现完善
使用技术:
springboot(lombok、Spring Web、MyBatis Framework、MySQL Driver)、Maven、JDK17
后端完整登录功能实现包括:
1、用户实体类
2、登录请求/响应 DTO
3、用户服务接口和实现
4、登录控制器
5、简单的密码加密
6、JWT token 生成和验证
一、后端登录功能的实现
1、首先,添加以下依赖
- Spring Security
- JWT 支持
- Validation 支持(参数校验工具)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.4</version>
</dependency>
2、创建基本的结构
创建实体类(entity/User.java)
package com.userShowing.entity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;//ID
private String username;//用户名
private String password;//密码
private String email;//邮箱
private String phone;//手机号
private LocalDateTime createTime;//注册时间
private LocalDateTime updateTime;//用户信息更新时间
private Boolean enabled;//是否启用
}
创建基本的DTO
登录请求DTO - LoginRequest.java
package com.userShowing.dto;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
登录响应DTO - LoginResponse.java
package com.userShowing.dto;
import lombok.Data;
import lombok.Builder;
@Data
@Builder
public class LoginResponse {
private String token;
private String username;
private String email;
}
创建登录接口 AuthController.java
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final UserService userService;
// 使用构造函数进行依赖注入
public AuthController(UserService userService) {
this.userService = userService;
}
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest loginRequest) {
try {
LoginResponse response = userService.login(loginRequest);
return ResponseEntity.ok(response);
} catch (RuntimeException e) {
Map<String, String> error = new HashMap<>();
error.put("message", e.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(error);
} catch (Exception e) {
Map<String, String> error = new HashMap<>();
error.put("message", "登录失败,请稍后重试");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
登录服务实现UserService与UserServiceImpl
UserService
package com.useranalysis.service;
import com.useranalysis.dto.LoginRequest;
import com.useranalysis.dto.LoginResponse;
import com.useranalysis.dto.RegisterRequest;
import com.useranalysis.entity.User;
public interface UserService {
LoginResponse login(LoginRequest loginRequest);//登录
User findByUsername(String username);//用户查询
User register(RegisterRequest registerRequest);//注册
}
UserServiceImpl
public LoginResponse login(LoginRequest loginRequest) {
try {
// 1. 使用 Spring Security 进行认证
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
// 2. 获取认证后的用户详情
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
// 3. 生成 JWT token
String token = jwtUtil.generateToken(userDetails);
// 4. 查询用户详细信息
User user = userMapper.findByUsername(userDetails.getUsername());
// 5. 构建登录响应
return LoginResponse.builder()
.token(token)
.username(user.getUsername())
.email(user.getEmail())
.build();
} catch (Exception e) {
// 6. 错误处理
// 检查用户是否存在
User user = userMapper.findByUsername(loginRequest.getUsername());
if (user == null) {
throw new RuntimeException("用户不存在");
}
// 检查密码格式
if (!user.getPassword().startsWith("$2a$")) {
throw new RuntimeException("密码格式不正确,请联系管理员重置密码");
}
throw new RuntimeException("用户名或密码错误");
}
}
安全配置(SecurityConfig与JwtUtil)
SecurityConfig
package com.useranalysis.config;
import com.useranalysis.service.impl.CustomUserDetailsService;
import lombok.RequiredArgsConstructor;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final CustomUserDetailsService userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.authenticationProvider(authenticationProvider());
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
JwtUtil.java
package com.useranalysis.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret:your-default-secret-key-must-be-at-least-32-chars}")
private String secret;//存储jwt的签名秘钥
@Value("${jwt.expiration:86400000}")
private Long expiration;//存储jwt的过期时间
//获取签名秘钥
private Key getSigningKey() {
byte[] keyBytes = secret.getBytes();
return Keys.hmacShaKeyFor(keyBytes);
}
//从token中提取用户名
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
//从token中提取过期时间
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
//从token中提取声明
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
//从token中提取所有声明
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
//判断token是否过期
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
//生成token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
//生成token
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()//创建一个JWT构建器
.setClaims(claims)//设置JWT的声明
.setSubject(subject)//设置JWT的主题
.setIssuedAt(new Date(System.currentTimeMillis()))//设置JWT的颁发时间
.setExpiration(new Date(System.currentTimeMillis() + expiration))//设置JWT的过期时间
.signWith(getSigningKey(), SignatureAlgorithm.HS256)//设置JWT的签名算法
.compact();//生成JWT字符串
}
//验证token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
3、添加配置文件 (user_analysis更换你自己数据库名称,还有username与password)
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/user_analysis?useSSL=false&serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
jwt:
secret: your-secret-key-here-must-be-at-least-32-characters-long
expiration: 86400000 # 24 hours in milliseconds
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.userShowing.entity
再此之后,还需要:
- 确保 MySQL 数据库已经启动,并创建了名为 user_analysis(你自己的也行) 的数据库
- 修改 application.yml 中的数据库连接信息(用户名和密码)
- 实现 UserServiceImpl 中的 findByUsername 方法,连接到实际的数据库
- 创建UserMapper接口
@Mapper
public interface UserMapper {
User findByUsername(@Param("username") String username);
}
- 创建映射文件UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.useranalysis.mapper.UserMapper">
<select id="findByUsername" resultType="com.useranalysis.entity.User">
SELECT id, username, password, email, phone, create_time, update_time, enabled
FROM user
WHERE username = #{username}
</select>
</mapper>
- 修改 UserServiceImpl 实现类
import com.usershowing.mapper.UserMapper;
private final UserMapper userMapper;
//查询用户
@Override
public User findByUsername(String username) {
return userMapper.findByUsername(username);
}
- 数据库表设计
CREATE TABLE IF NOT EXISTS `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password` VARCHAR(100) NOT NULL,
`email` VARCHAR(100) NOT NULL,
`phone` VARCHAR(20),
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`enabled` BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
至此,后端登录功能完成!!!
二、后端注册功能的实现
1、注册请求 DTO(RegisterRequest)
package com.useranalysis.dto;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class RegisterRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
private String username;
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
private String password;
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
}
2、注册接口(AuthController)
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest registerRequest) {
try {
User user = userService.register(registerRequest);
return ResponseEntity.ok(user);
} catch (RuntimeException e) {
Map<String, String> error = new HashMap<>();
error.put("message", e.getMessage());
return ResponseEntity.badRequest().body(error);
} catch (Exception e) {
Map<String, String> error = new HashMap<>();
error.put("message", "注册失败,请稍后重试");
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
}
3、注册服务实现(UserServiceImpl)
@Transactional
public User register(RegisterRequest registerRequest) {
// 1. 检查用户名是否已存在
if (findByUsername(registerRequest.getUsername()) != null) {
throw new RuntimeException("用户名已存在");
}
// 2. 创建新用户
User user = new User();
user.setUsername(registerRequest.getUsername());
// 3. 使用 BCrypt 加密密码
user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));
user.setEmail(registerRequest.getEmail());
user.setEnabled(true);
user.setCreateTime(LocalDateTime.now());
user.setUpdateTime(LocalDateTime.now());
// 4. 保存用户到数据库
userMapper.insert(user);
return user;
}
UserMapper
void insert(User user);
UserMapper.xml映射语句
<insert id="insert" parameterType="com.useranalysis.entity.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (username, password, email, phone, create_time, update_time, enabled)
VALUES (#{username}, #{password}, #{email}, #{phone}, #{createTime}, #{updateTime}, #{enabled})
</insert>
三、测试
启动项目,使用postman/apifox 测试接口:
1、注册新用户
POST /api/auth/register
Content-Type: application/json
{
"username": "testuser",
"password": "password123",
"email": "test@example.com"
}
测试返回数据:
2、用户登录
POST /api/auth/login
Content-Type: application/json
{
"username": "testuser",
"password": "password123"
}
测试返回数据:
项目完整文件地址:
通过百度网盘分享的文件:user-analysis
链接: https://pan.baidu.com/s/1Oi_EMBIN9dEdtHCPaGmCbQ?pwd=jbwd 提取码: jbwd