【Spring Security系列】Spring Security整合JWT:构建安全的Web应用
作者:mmseoamin日期:2024-04-27

前言

在企业级开发或者我们自己的课程设计中,确保用户数据的安全性和访问控制非常重要。而Spring Security和JWT是都两个强大的工具,它俩结合可以帮助我们实现这一目标。

Spring Security提供了全面的安全功能,而JWT则是一种用于身份验证的令牌机制。

【Spring Security系列】Spring Security整合JWT:构建安全的Web应用,在这里插入图片描述,第1张

JWT简单介绍

前面两个章节介绍过了Spring Security,这里就不再赘述了!!!

JWT是一种轻量级的身份验证和授权机制,通过发送包含用户信息的加密令牌来实现身份验证。这个工具我们在前面的文章中也提起过。

整合步骤与代码实现

目前大部分项目,大多数是使用前后端分离的模式。前后端分离的情况下,我们使用SpringSecurity解决权限问题的最常见的方案就是SpringSecurity+JWT 。

【Spring Security系列】Spring Security整合JWT:构建安全的Web应用,在这里插入图片描述,第2张

添加依赖

首先,我们需要在项目的pom.xml文件中添加Spring Security和JWT的依赖:



    com.auth0
    java-jwt
    3.8.1


    io.jsonwebtoken
    jjwt
    0.9.1



    cn.hutool
    hutool-all
    5.8.0.M3


    javax.xml.bind
    jaxb-api


    com.alibaba
    fastjson
    1.2.75

接下来配置Spring Security,在Spring Security配置类中,我们自定义用户详情服务和认证管理器,并配置HTTP安全策略:

@Configuration  
@EnableWebSecurity  
public class SecurityConfig extends WebSecurityConfigurerAdapter {  
  
    @Autowired  
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;  
  
    @Autowired  
    private JwtRequestFilter jwtRequestFilter;  
  
    @Autowired  
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {  
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());  
    }  
  
    @Bean  
    @Override  
    public AuthenticationManager authenticationManagerBean() throws Exception {  
        return super.authenticationManagerBean();  
    }  
  
    @Bean  
    public PasswordEncoder passwordEncoder() {  
        return new BCryptPasswordEncoder();  
    }  
  
    @Override  
    protected void configure(HttpSecurity http) throws Exception {  
        http  
            .csrf().disable()  
            .authorizeRequests()  
            .antMatchers("/authenticate").permitAll()  
            .anyRequest().authenticated()  
            .and()  
            .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)  
            .and()  
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);  
  
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);  
    }   
}

实现JWT生成和验证,我们创建一个JWT工具类,用于生成和解析JWT:

java
@Component  
public class JwtTokenUtil {  
  
    private String secret = "your_secret_key"; // 私钥,用于签名JWT  
  
    public String generateToken(UserDetails userDetails) {  
        Map claims = new HashMap<>();  
        return Jwts.builder()  
                .setClaims(claims)  
                .setSubject(((User) userDetails).getUsername())  
                .setIssuedAt(new Date(System.currentTimeMillis()))  
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10小时过期  
                .signWith(SignatureAlgorithm.HS512, secret)  
                .compact();  
    }  
  
    public String getUsernameFromToken(String token) {  
        return getClaimFromToken(token, Claims::getSubject);  
    }  
  
    public Date getExpirationDateFromToken(String token) {  
        return getClaimFromToken(token, Claims::getExpiration);  
    }  
  
    private  T getClaimFromToken(String token, Function claimsResolver) {  
        final Claims claims = getAllClaimsFromToken(token);  
        return claimsResolver.apply(claims);  
    }  
  
    private Claims getAllClaimsFromToken(String token) {  
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();  
    }  
  
    public boolean validateToken(String token, UserDetails userDetails) {  
        final String username = getUsernameFromToken(token);  
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));  
    }  
  
    private boolean isTokenExpired(String token) {  
        final Date expiration = getExpirationDateFromToken(token);  
        return expiration.before(new Date());

【Spring Security系列】Spring Security整合JWT:构建安全的Web应用,在这里插入图片描述,第3张

创建JWT过滤器与认证管理器

为了在用户每次请求时验证JWT,我们需要创建一个自定义的过滤器。同时,我们还需要一个认证管理器来处理用户的登录请求。

我们实现JWT过滤器

@Component  
public class JwtRequestFilter extends OncePerRequestFilter {  
  
    @Autowired  
    private JwtTokenUtil jwtTokenUtil;  
  
    @Autowired  
    private UserDetailsService userDetailsService;  
  
    @Override  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)  
            throws ServletException, IOException {  
        final String requestTokenHeader = request.getHeader("Authorization");  
  
        String username = null;  
        String jwtToken = null;  
        if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {  
            jwtToken = requestTokenHeader.substring(7);  
            try {  
                username = jwtTokenUtil.getUsernameFromToken(jwtToken);  
            } catch (Exception e) {  
                logger.error("Unable to get JWT Token");  
            }  
        }  
  
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {  
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);  
  
            if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {  
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =  
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());  
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));  
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);  
            }  
        }  
  
        filterChain.doFilter(request, response);  
    }  
}

认证管理器,我们创建一个AuthenticationManager的实现来处理用户的登录请求:

@Service  
public class CustomAuthenticationManager implements AuthenticationManager {  
  
    @Autowired  
    private UserDetailsService userDetailsService;  
  
    @Autowired  
    private PasswordEncoder passwordEncoder;  
  
    @Override  
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {  
        String username = authentication.getName();  
        String password = authentication.getCredentials().toString();  
  
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);  
  
        if (userDetails == null) {  
            throw new BadCredentialsException("User not found");  
        }  
  
        if (!passwordEncoder.matches(password, userDetails.getPassword())) {  
            throw new BadCredentialsException("Wrong password");  
        }  
  
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());  
    }  
}

创建控制层LoginController

RestController  
@RequestMapping("/security")  
public class AuthenticationController {  
  
    @Autowired  
    private CustomAuthenticationManager authenticationManager;  
  
    @Autowired  
    private JwtTokenUtil jwtTokenUtil;  
  
    @Autowired  
    private UserDetailsService userDetailsService;  
  
    @PostMapping("/login")
    public ResponseEntity createAuthenticationToken(@Valid @RequestBody LoginRequest loginRequest) throws Exception {  
        authenticate(loginRequest.getUsername(), loginRequest.getPassword());  
        final UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());  
        final String token = jwtTokenUtil.generateToken(userDetails);  
        return ResponseEntity.ok(new JwtAuthenticationResponse(token));  
    }  
  
    private void authenticate(String username, String password) throws Exception {  
        try {  
            authenticationManager.authenticate(  
                    new UsernamePasswordAuthenticationToken(username, password)  
            );  
        } catch (DisabledException e) {  
            throw new Exception("USER_DISABLED", e);  
        } catch (BadCredentialsException e) {  
            throw new Exception("INVALID_CREDENTIALS", e);  
        }  
    }  
}

使用ApiFox测试

【Spring Security系列】Spring Security整合JWT:构建安全的Web应用,在这里插入图片描述,第4张

这样,我们就可以构建一个安全且高效的Web应用了。

小结

Spring Security提供了强大的身份验证和授权功能,而JWT则提供了一种轻量级的令牌机制来验证用户身份。通过结合使用,我们可以实现无缝的用户身份验证和访问控制,然后保护我们应用的数据安全。

文章到这里就先结束了,后续会继续分享相关的知识点。

【Spring Security系列】Spring Security整合JWT:构建安全的Web应用,在这里插入图片描述,第5张