在一个前后端分离的项目中,使用 Spring Security 的一种推荐方式是采用 JSON Web Token(JWT)进行身份验证。在这个例子中,我将介绍如何使用 Spring Security 和 JWT 实现基于您提供的用户模型的认证和授权。
首先,需要在项目中添加 Spring Security 和 JWT 相关依赖。在 pom.xml
文件中添加以下依赖:
1 2 3 4 5 6 7 8 9 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > <dependency > <groupId > io.jsonwebtoken</groupId > <artifactId > jjwt</artifactId > <version > 0.9.1</version > </dependency >
创建一个自定义的 UserDetailsService
,用于从数据库加载用户信息。这个服务将用于 Spring Security 认证过程中加载用户数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Service public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserRepository userRepository; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { User user = userRepository.findByName(username); if (user == null ) { throw new UsernameNotFoundException ("User not found with name: " + username); } return new org .springframework.security.core.userdetails.User(user.getName(), user.getPassword(), getAuthorities(user)); } private Collection<? extends GrantedAuthority > getAuthorities(User user) { List<SimpleGrantedAuthority> authorities = new ArrayList <>(); if (user.getManageInfo() == 1 ) { authorities.add(new SimpleGrantedAuthority ("ROLE_ADMIN" )); } else { authorities.add(new SimpleGrantedAuthority ("ROLE_USER" )); } return authorities; } }
创建一个 JWT 工具类,用于生成和解析 JWT 令牌:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 @Component public class JwtTokenProvider { private final String secretKey = "your-secret-key" ; private final long validityInMilliseconds = 3600000 ; public String createToken (String username, List<String> roles) { Claims claims = Jwts.claims().setSubject(username); claims.put("roles" , roles); Date now = new Date (); Date validity = new Date (now.getTime() + validityInMilliseconds); return Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(validity) .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } public Authentication getAuthentication (String token) { UserDetails userDetails = userDetailsService.loadUserByUsername(getUsername(token)); return new UsernamePasswordAuthenticationToken (userDetails, "" , userDetails.getAuthorities()); } public String getUsername (String token) { return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); } public boolean validateToken (String token) { try { Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); if (claims.getBody().getExpiration().before(new Date ())) { return false ; } return true ; } catch (JwtException | IllegalArgumentException e) { throw new InvalidJwtAuthenticationException ("Expired or invalid JWT token" ); } } }
继续创建一个自定义的 JwtAuthenticationFilter
,用于处理认证请求:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter { private final AuthenticationManager authenticationManager; private final JwtTokenProvider jwtTokenProvider; public JwtAuthenticationFilter (AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider) { this .authenticationManager = authenticationManager; this .jwtTokenProvider = jwtTokenProvider; setFilterProcessesUrl("/api/auth/login" ); } @Override public Authentication attemptAuthentication (HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { String requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator())); JSONObject jsonObject = new JSONObject (requestBody); String username = jsonObject.getString("username" ); String password = jsonObject.getString("password" ); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken (username, password); return authenticationManager.authenticate(authenticationToken); } catch (IOException e) { throw new AuthenticationServiceException ("Failed to parse authentication request body" , e); } } @Override protected void successfulAuthentication (HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { String token = jwtTokenProvider.createToken(authResult.getName(), ((User) authResult.getPrincipal()).getAuthorities() .stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); response.addHeader("Authorization" , "Bearer " + token); } @Override protected void unsuccessfulAuthentication (HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); } }
创建一个自定义的 JwtAuthorizationFilter
,用于验证 JWT 令牌:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class JwtAuthorizationFilter extends BasicAuthenticationFilter { private final JwtTokenProvider jwtTokenProvider; public JwtAuthorizationFilter (AuthenticationManager authenticationManager, JwtTokenProvider jwtTokenProvider) { super (authenticationManager); this .jwtTokenProvider = jwtTokenProvider; } @Override protected void doFilterInternal (HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String token = resolveToken(request); if (token != null && jwtTokenProvider.validateToken(token)) { Authentication auth = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(auth); } chain.doFilter(request, response); } private String resolveToken (HttpServletRequest request) { String bearerToken = request.getHeader("Authorization" ); if (bearerToken != null && bearerToken.startsWith("Bearer " )) { return bearerToken.substring(7 ); } return null ; } }
最后,创建一个安全配置类,整合前面创建的组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 @Configuration public class SecurityConfig { @Autowired private CustomUserDetailsService customUserDetailsService; @Autowired private JwtTokenProvider jwtTokenProvider; @Bean public PasswordEncoder passwordEncoder () { return new BCryptPasswordEncoder (); } @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter (authenticationManagerBean(), jwtTokenProvider); JwtAuthorizationFilter jwtAuthorizationFilter = new JwtAuthorizationFilter (authenticationManagerBean(), jwtTokenProvider); http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilter(jwtAuthenticationFilter) .addFilterAfter(jwtAuthorizationFilter, JwtAuthenticationFilter.class) .authorizeRequests() .antMatchers("/api/auth/login" ).permitAll() .antMatchers("/api/admin/**" ).hasRole("ADMIN" ) .anyRequest().authenticated(); return http.build(); } @Bean @Override public AuthenticationManager authenticationManagerBean () throws Exception { return super .authenticationManagerBean(); } }
在这个配置类中,我们定义了一个 SecurityFilterChain
Bean,并使用 HttpSecurity
参数来配置安全规则。我们创建了 JwtAuthenticationFilter
和 JwtAuthorizationFilter
的实例,并将它们添加到过滤器链中。
请注意,我们需要重写 authenticationManagerBean
方法以确保可以将 AuthenticationManager
注入到 JwtAuthenticationFilter
和 JwtAuthorizationFilter
中。