使用Spring Boot + Angular构建安全的登录注册系统

现代全栈开发的完美实践


图:系统架构示意图

在当今的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. 用户实体类
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 role = "USER";
}
3. JWT工具类
java 复制代码
@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. 安全配置
java 复制代码
@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. 认证控制器
java 复制代码
@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. 项目初始化
bash 复制代码
ng new auth-frontend
ng add @angular/material
ng g s services/auth
ng g guard guards/auth
2. 认证服务
typescript 复制代码
@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拦截器
typescript 复制代码
@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. 路由守卫
typescript 复制代码
@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. 登录组件模板
html 复制代码
<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>

关键功能亮点

  1. JWT无状态认证

    使用JSON Web Token实现无状态会话管理

  2. 密码安全存储

    BCrypt算法加密存储密码

  3. 路由守卫

    防止未授权用户访问受保护路由

  4. 响应式表单

    实时表单验证和错误处理

  5. HTTP拦截器

    自动附加JWT到请求头


部署注意事项

  1. 生产环境切换MySQL数据库:
properties 复制代码
spring.datasource.url=jdbc:mysql://localhost:3306/auth_db
spring.datasource.username=root
spring.datasource.password=yourpassword
  1. 配置CORS策略:
java 复制代码
@Bean
public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                .allowedOrigins("http://your-angular-app.com")
                .allowedMethods("*");
        }
    };
}
  1. 环境变量管理敏感信息

完整项目结构

复制代码
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)
  • 增加双因素认证
  • 集成验证码机制

GitHub完整代码
后端代码仓库
前端代码仓库


通过这个全栈解决方案,你可以快速构建安全可靠的用户认证系统,为你的应用奠定坚实的安全基础。欢迎在评论区交流遇到的问题或优化建议!