商道如狼道 发表于 2024-6-14 21:33:22

【Spring Security系列】Spring Security整合JWT:构建安全的Web应用

前言

在企业级开辟或者我们本身的课程设计中,确保用户数据的安全性和访问控制非常紧张。而Spring Security和JWT是都两个强大的工具,它俩结合可以资助我们实现这一目标。
Spring Security提供了全面的安全功能,而JWT则是一种用于身份验证的令牌机制。
https://img-blog.csdnimg.cn/direct/b221a348abc94fdf85ea915218d399d9.jpeg#pic_center
JWT简朴介绍

前面两个章节介绍过了Spring Security,这里就不再赘述了!!!
JWT是一种轻量级的身份验证和授权机制,通过发送包罗用户信息的加密令牌来实现身份验证。这个工具我们在前面的文章中也提起过。
整合步调与代码实现

目前大部门项目,大多数是使用前后端分离的模式。前后端分离的环境下,我们使用SpringSecurity解决权限题目的最常见的方案就是SpringSecurity+JWT 。
https://img-blog.csdnimg.cn/direct/0634d9b877e247a2ab4d80e778ef463c.png
添加依赖
首先,我们需要在项目标pom.xml文件中添加Spring Security和JWT的依赖:
<!--JWT-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.1</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<!--工具包-->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.0.M3</version>
</dependency>
<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>
接下来配置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<String, Object> 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> T getClaimFromToken(String token, Function<Claims, T> 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());
https://img-blog.csdnimg.cn/direct/9fb5a338e154422cbabc7658dedaa294.png
创建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测试

https://img-blog.csdnimg.cn/direct/5630b6b4050d4260a4fd2f7e9c54a50a.png
这样,我们就可以构建一个安全且高效的Web应用了。
小结

Spring Security提供了强大的身份验证和授权功能,而JWT则提供了一种轻量级的令牌机制来验证用户身份。通过结合使用,我们可以实现无缝的用户身份验证和访问控制,然后掩护我们应用的数据安全。
文章到这里就先结束了,后续会继续分享相关的知识点。
https://img-blog.csdnimg.cn/1f5dbcc45069486f99a40c2744a518e0.gif#pic_center

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【Spring Security系列】Spring Security整合JWT:构建安全的Web应用