引言:现代Web应用认证的重要性
在当今数字化时代,用户认证是Web应用的基石。无论是电商平台、社交媒体还是企业系统,安全的登录注册功能都至关重要。本文将手把手教你使用Spring Boot 作为后端、Angular作为前端,构建一个完整的登录注册系统。
系统整体架构设计
我们的系统采用经典的前后端分离架构:
架构核心组件:
- 前端:Angular应用,包含登录/注册组件、认证服务和路由守卫
- 后端:Spring Boot应用,提供REST API,处理认证和用户管理
- 通信:HTTPS协议,JSON数据格式
- 认证:基于JWT(JSON Web Token)的无状态认证机制
后端实现:Spring Boot安全认证
技术栈
- Spring Security
- Spring Data JPA
- JJWT库
- H2数据库(开发环境)
- Lombok
关键代码实现
1. 用户实体类
java
@Entity
@Data
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true)
private String email;
private String password;
private String name;
private String role = "USER";
private LocalDateTime createdAt = LocalDateTime.now();
}
2. JWT工具类
java
@Component
public class JwtUtil {
private final String SECRET_KEY = "your-strong-secret-key-here";
private final long EXPIRATION_MS = 10 * 60 * 60 * 1000; // 10小时
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_MS))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
3. 安全配置
java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**", "/h2-console/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtUtil))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 允许H2控制台的帧访问
http.headers().frameOptions().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4. 认证控制器
java
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@PostMapping("/register")
public ResponseEntity<?> register(@Valid @RequestBody RegisterRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
return ResponseEntity.badRequest().body(
Map.of("message", "Email already exists")
);
}
User user = new User();
user.setEmail(request.getEmail());
user.setName(request.getName());
user.setPassword(passwordEncoder.encode(request.getPassword()));
userRepository.save(user);
return ResponseEntity.ok(Map.of("message", "User registered successfully"));
}
@PostMapping("/login")
public ResponseEntity<?> login(@Valid @RequestBody LoginRequest request) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getEmail(),
request.getPassword()
)
);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthResponse(jwt));
} catch (BadCredentialsException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(
Map.of("message", "Invalid credentials")
);
}
}
}
前端实现:Angular认证系统
项目结构
src/
├── app/
│ ├── core/
│ │ ├── guards/ # 路由守卫
│ │ ├── interceptors/ # HTTP拦截器
│ │ └── services/ # 核心服务
│ ├── modules/
│ │ ├── auth/ # 认证模块
│ │ └── dashboard/ # 主应用模块
│ ├── shared/ # 共享模块
│ ├── app-routing.module.ts # 路由配置
│ └── app.module.ts # 主模块
关键组件实现
1. 认证服务
typescript
@Injectable({ providedIn: 'root' })
export class AuthService {
private readonly apiUrl = `${environment.apiUrl}/auth`;
private currentUserSubject = new BehaviorSubject<User | null>(null);
public currentUser$ = this.currentUserSubject.asObservable();
constructor(
private http: HttpClient,
private tokenService: TokenService,
private router: Router
) {
const user = localStorage.getItem('currentUser');
if (user) {
this.currentUserSubject.next(JSON.parse(user));
}
}
login(credentials: { email: string; password: string }): Observable<any> {
return this.http.post<{ token: string }>(`${this.apiUrl}/login`, credentials).pipe(
tap(response => {
this.tokenService.setToken(response.token);
this.fetchCurrentUser();
})
);
}
fetchCurrentUser(): void {
this.http.get<User>(`${environment.apiUrl}/users/me`).subscribe({
next: user => {
this.currentUserSubject.next(user);
localStorage.setItem('currentUser', JSON.stringify(user));
},
error: () => this.logout()
});
}
logout(): void {
this.tokenService.removeToken();
this.currentUserSubject.next(null);
localStorage.removeItem('currentUser');
this.router.navigate(['/login']);
}
}
2. 登录组件
typescript
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent implements OnInit {
loginForm: FormGroup;
isLoading = false;
errorMessage: string | null = null;
returnUrl: string | null = null;
showPassword = false;
constructor(
private fb: FormBuilder,
private authService: AuthService,
private router: Router,
private route: ActivatedRoute
) {
this.loginForm = this.fb.group({
email: ['', [Validators.required, Validators.email]],
password: ['', Validators.required],
rememberMe: [false]
});
}
onSubmit(): void {
if (this.loginForm.invalid) return;
this.isLoading = true;
this.errorMessage = null;
const { email, password } = this.loginForm.value;
this.authService.login({ email, password }).subscribe({
next: () => {
this.router.navigateByUrl(this.returnUrl || '/dashboard');
},
error: (err) => {
this.errorMessage = '登录失败,请检查您的凭据';
this.isLoading = false;
}
});
}
}
3. 登录组件模板
html
<div class="login-container">
<mat-card class="login-card">
<mat-card-header>
<mat-card-title>欢迎回来</mat-card-title>
<mat-card-subtitle>请登录您的账户</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<mat-form-field appearance="outline">
<mat-label>电子邮箱</mat-label>
<input matInput formControlName="email" type="email">
<mat-icon matSuffix>mail</mat-icon>
<mat-error *ngIf="loginForm.get('email')?.hasError('required')">
邮箱为必填项
</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>密码</mat-label>
<input
matInput
[type]="showPassword ? 'text' : 'password'"
formControlName="password"
>
<button
type="button"
mat-icon-button
matSuffix
(click)="togglePasswordVisibility()"
>
<mat-icon>{{ showPassword ? 'visibility_off' : 'visibility' }}</mat-icon>
</button>
</mat-form-field>
<button
mat-raised-button
color="primary"
type="submit"
[disabled]="loginForm.invalid || isLoading"
>
<span *ngIf="!isLoading">登录</span>
<mat-spinner *ngIf="isLoading" diameter="20"></mat-spinner>
</button>
</form>
</mat-card-content>
</mat-card>
</div>
4. HTTP拦截器
typescript
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
constructor(
private tokenService: TokenService,
private authService: AuthService,
private router: Router
) {}
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
const token = this.tokenService.getToken();
let authReq = request;
if (token) {
authReq = request.clone({
setHeaders: {
Authorization: `Bearer ${token}`
}
});
}
return next.handle(authReq).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
this.authService.logout();
this.router.navigate(['/login'], { queryParams: { expired: true } });
}
return throwError(() => error);
})
);
}
}
系统数据流分析
登录流程
User Angular SpringBoot Database 输入凭据并提交 POST /api/auth/login 查询用户 返回用户数据 生成JWT 200 OK (含JWT) 存储Token 跳转到主页 401 Unauthorized 显示错误 alt 认证成功 认证失败 User Angular SpringBoot Database
注册流程
User Angular SpringBoot Database 填写注册表单 POST /api/auth/register 检查邮箱唯一性 密码加密 保存用户 保存成功 200 OK 显示成功消息 400 Bad Request 显示错误 alt 邮箱可用 邮箱已存在 User Angular SpringBoot Database
安全架构设计
HTTPS Angular前端 Spring Boot后端 Spring Security JWT认证 Token生成 Token验证 密码加密 BCrypt CORS配置 CSRF防护
安全措施:
- 密码安全:BCrypt强哈希算法存储密码
- 传输安全:强制使用HTTPS
- 令牌安全 :
- JWT设置合理有效期(建议2小时)
- 使用强密钥(256位以上)
- 跨域控制:严格的白名单策略
- 输入验证:前后端双重验证
部署架构
HTTPS 静态文件 API代理 用户 Nginx Angular应用 Spring Boot应用 MySQL数据库集群
部署要点:
- 使用Nginx作为反向代理和静态文件服务器
- Spring Boot应用使用内嵌Tomcat
- 数据库主从复制提高可用性
- 使用环境变量管理敏感信息
- 配置监控和日志系统
总结与扩展
我们实现了一个完整的登录注册系统,具有以下特点:
✅ 前后端分离架构
✅ JWT无状态认证
✅ 响应式表单验证
✅ 路由级权限控制
✅ 多层安全防护
扩展方向:
- 添加社交登录(OAuth2)
- 实现双因素认证
- 集成短信/邮箱验证
- 添加RBAC权限管理系统
- 实现密码重置功能
通过本文,你应该已经掌握了使用Spring Boot和Angular构建登录注册系统的核心知识和技能。这个架构不仅适用于登录注册功能,还可以作为任何需要用户认证的Web应用的基础。