引言:现代Web应用认证的重要性
在当今数字化时代,用户认证是Web应用的基石。无论是电商平台、社交媒体还是企业系统,安全的登录注册功能都至关重要。本文将手把手教你使用Spring Boot作为后端、Angular作为前端,构建一个完整的登录注册系统。
系统整体架构设计
我们的系统采用经典的前后端分离架构:
graph TD
subgraph Frontend[Angular前端]
A[登录组件] -->|调用| B[认证服务]
C[注册组件] -->|调用| B
D[路由守卫] -->|保护| E[受保护路由]
F[HTTP拦截器] -->|添加Token| G[HTTP请求]
end
subgraph Backend[Spring Boot后端]
H[认证控制器] -->|处理| I[注册端点]
H -->|处理| J[登录端点]
K[安全配置] -->|保护| L[受保护API]
M[JWT工具] -->|生成/验证| N[认证]
O[用户仓库] -->|数据操作| P[数据库]
end
Frontend -->|HTTP API调用| Backend
架构核心组件:
- 前端:Angular应用,包含登录/注册组件、认证服务和路由守卫
- 后端:Spring Boot应用,提供REST API,处理认证和用户管理
- 通信:HTTPS协议,JSON数据格式
- 认证:基于JWT(JSON Web Token)的无状态认证机制
后端实现:Spring Boot安全认证
技术栈
- Spring Security
- Spring Data JPA
- JJWT库
- H2数据库(开发环境)
- Lombok
关键代码实现
1. 用户实体类
@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工具类
@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. 安全配置
@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. 认证控制器
@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. 认证服务
@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. 登录组件
@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. 登录组件模板
<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拦截器
@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);
})
);
}
}
系统数据流分析
登录流程
注册流程
安全架构设计
安全措施:
- 密码安全:BCrypt强哈希算法存储密码
- 传输安全:强制使用HTTPS
- 令牌安全:
- JWT设置合理有效期(建议2小时)
- 使用强密钥(256位以上)
- 跨域控制:严格的白名单策略
- 输入验证:前后端双重验证
部署架构
部署要点:
- 使用Nginx作为反向代理和静态文件服务器
- Spring Boot应用使用内嵌Tomcat
- 数据库主从复制提高可用性
- 使用环境变量管理敏感信息
- 配置监控和日志系统
总结与扩展
我们实现了一个完整的登录注册系统,具有以下特点:
✅ 前后端分离架构
✅ JWT无状态认证
✅ 响应式表单验证
✅ 路由级权限控制
✅ 多层安全防护
扩展方向:
- 添加社交登录(OAuth2)
- 实现双因素认证
- 集成短信/邮箱验证
- 添加RBAC权限管理系统
- 实现密码重置功能
通过本文,你应该已经掌握了使用Spring Boot和Angular构建登录注册系统的核心知识和技能。这个架构不仅适用于登录注册功能,还可以作为任何需要用户认证的Web应用的基础。