现代全栈开发的完美实践
图:系统架构示意图
在当今的Web应用开发中,用户认证是核心功能之一。本文将手把手教你使用Spring Boot作为后端、Angular作为前端,构建一个完整的登录注册系统。
技术栈概览
后端 (Spring Boot) | 前端 (Angular) |
---|---|
Spring Security | Angular Router |
Spring Data JPA | Reactive Forms |
JWT 认证 | HTTP Client |
H2 数据库 (开发环境) | Auth Guard |
Lombok | Interceptor |
第一部分:Spring Boot后端实现
1. 项目初始化
使用 Spring Initializr 创建项目,选择:
- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database
- Lombok
2. 用户实体类
@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 role = "USER";
}
3. JWT工具类
@Component
public class JwtUtil {
private final String SECRET_KEY = "your-secret-key";
public String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10小时
.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));
}
}
4. 安全配置
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtUtil))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
5. 认证控制器
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserRepository userRepository;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest request) {
if (userRepository.existsByEmail(request.getEmail())) {
return ResponseEntity.badRequest().body("Email already exists");
}
User user = new User();
user.setEmail(request.getEmail());
user.setPassword(new BCryptPasswordEncoder().encode(request.getPassword()));
userRepository.save(user);
return ResponseEntity.ok("User registered successfully");
}
@PostMapping("/login")
public ResponseEntity<?> login(@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(401).body("Invalid credentials");
}
}
}
第二部分:Angular前端实现
1. 项目初始化
ng new auth-frontend
ng add @angular/material
ng g s services/auth
ng g guard guards/auth
2. 认证服务
@Injectable({ providedIn: 'root' })
export class AuthService {
private apiUrl = 'http://localhost:8080/api/auth';
private currentUserSubject = new BehaviorSubject<any>(null);
constructor(private http: HttpClient) {
const token = localStorage.getItem('token');
if (token) {
this.currentUserSubject.next(this.decodeToken(token));
}
}
register(credentials: { email: string; password: string }) {
return this.http.post(`${this.apiUrl}/register`, credentials);
}
login(credentials: { email: string; password: string }) {
return this.http.post<{ token: string }>(`${this.apiUrl}/login`, credentials)
.pipe(tap(res => {
localStorage.setItem('token', res.token);
this.currentUserSubject.next(this.decodeToken(res.token));
}));
}
logout() {
localStorage.removeItem('token');
this.currentUserSubject.next(null);
}
private decodeToken(token: string): any {
try {
return JSON.parse(atob(token.split('.')[1]));
} catch (e) {
return null;
}
}
}
3. HTTP拦截器
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler) {
const token = localStorage.getItem('token');
if (token) {
const cloned = req.clone({
headers: req.headers.set('Authorization', `Bearer ${token}`)
});
return next.handle(cloned);
}
return next.handle(req);
}
}
4. 路由守卫
@Injectable({ providedIn: 'root' })
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(): boolean {
if (this.authService.isAuthenticated()) {
return true;
}
this.router.navigate(['/login']);
return false;
}
}
5. 登录组件模板
<mat-card>
<mat-card-header>
<mat-card-title>Login</mat-card-title>
</mat-card-header>
<mat-card-content>
<form [formGroup]="loginForm" (ngSubmit)="onSubmit()">
<mat-form-field>
<input matInput placeholder="Email" formControlName="email">
<mat-error>Valid email required</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput type="password" placeholder="Password" formControlName="password">
</mat-form-field>
<button mat-raised-button color="primary" type="submit">Login</button>
</form>
</mat-card-content>
</mat-card>
关键功能亮点
JWT无状态认证
使用JSON Web Token实现无状态会话管理密码安全存储
BCrypt算法加密存储密码路由守卫
防止未授权用户访问受保护路由响应式表单
实时表单验证和错误处理HTTP拦截器
自动附加JWT到请求头
部署注意事项
- 生产环境切换MySQL数据库:
spring.datasource.url=jdbc:mysql://localhost:3306/auth_db
spring.datasource.username=root
spring.datasource.password=yourpassword
- 配置CORS策略:
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://your-angular-app.com")
.allowedMethods("*");
}
};
}
- 环境变量管理敏感信息
完整项目结构
backend/
├─ src/
│ ├─ main/
│ │ ├─ java/
│ │ │ ├─ controller/
│ │ │ ├─ security/
│ │ │ ├─ model/
│ │ │ ├─ repository/
│ │ ├─ resources/
│ │ │ ├─ application.properties
frontend/
├─ src/
│ ├─ app/
│ │ ├─ components/
│ │ │ ├─ login/
│ │ │ ├─ register/
│ │ ├─ services/
│ │ ├─ guards/
│ ├─ environments/
总结
通过这个教程,我们实现了:
✅ 基于Spring Security的安全认证
✅ JWT令牌的生成与验证
✅ Angular的响应式表单处理
✅ 路由权限控制
✅ 前后端分离架构
扩展建议:
- 添加密码重置功能
- 实现第三方登录(OAuth2)
- 增加双因素认证
- 集成验证码机制
通过这个全栈解决方案,你可以快速构建安全可靠的用户认证系统,为你的应用奠定坚实的安全基础。欢迎在评论区交流遇到的问题或优化建议!