在一个前后端分离的项目中,使用 Spring Security 的一种推荐方式是采用 JSON Web Token(JWT)进行身份验证。在这个例子中,我将介绍如何使用 Spring Security 和 JWT 实现基于您提供的用户模型的认证和授权。

  1. 首先,需要在项目中添加 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>
  1. 创建一个自定义的 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;
}
}
  1. 创建一个 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; // 1 hour

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");
}
}
}
  1. 继续创建一个自定义的 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);
}
}
  1. 创建一个自定义的 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. 最后,创建一个安全配置类,整合前面创建的组件:

    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 参数来配置安全规则。我们创建了 JwtAuthenticationFilterJwtAuthorizationFilter 的实例,并将它们添加到过滤器链中。

    请注意,我们需要重写 authenticationManagerBean 方法以确保可以将 AuthenticationManager 注入到 JwtAuthenticationFilterJwtAuthorizationFilter 中。