Spring Boot(四)之使用JWT和Spring Security保护REST API

下面是关于Spring Boot如何使用JWT和Spring Security保护REST API的攻略:

什么是JWT?

JSON Web Token(JWT)是一种开放标准(RFC 7519),用于通过网络在各方之间安全地传输声明。JSON Web Token是由三部分组成:标题,声明和签名。例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik15IEFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT由以下三部分组成:

  1. Header:JWT头部承载两部分信息,声明JWT以及所使用的算法。如:

{
"alg": "HS256",
"typ": "JWT"
}

  1. Payload:载荷,即所携带的信息,包含标准中注册的声明(建议但不强制),私有声明和公共声明。

{
"sub": "1234567890",
"name": "My Admin",
"iat": 1516239022
}

  1. Signature:签名,为确保JWT未被篡改,JWT的签名部分采用headerpayload字符流按照规定的算法进行加密后的结果。

HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)

JWT和Spring Security

Spring Security提供了支持JWT的模块,spring-security-jwt。通过使用该模块,我们可以轻松使用JWT来保护我们的REST API。下面将介绍使用Spring Security和JWT保护REST API的步骤。

步骤1:添加依赖

我们首先要在我们的Spring Boot应用程序中添加包括spring-security-jwt在内的相关依赖。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.11.2</version>
</dependency>

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-impl</artifactId>
  <version>0.11.2</version>
  <scope>runtime</scope>
</dependency>

<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-jackson</artifactId>
  <version>0.11.2</version>
  <scope>runtime</scope>
</dependency>

步骤2:创建JWT工具类

我们需要创建一个JWT工具类,用于生成和解析JWT。下面是一个简单的JWT工具类的示例:

@Service
public class JwtUtils {

    private final String jwtSecret = "yourSecretKey";

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    private String createToken(Map<String, Object> claims, String subject) {
        long now = System.currentTimeMillis();
        Date issuedAt = new Date(now);
        Date expiresAt = new Date(now + 1000 * 60 * 60 * 10); // 10 hours

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(issuedAt)
                .setExpiration(expiresAt)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public boolean validateToken(String token, UserDetails userDetails) {
        String username = getUsernameFromToken(token);
        return username.equals(userDetails.getUsername());
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
    }

}

步骤3:实现Spring Security的UserDetailsService

我们还需要实现一个Spring Security的UserDetailsService,用于通过用户名获取用户详细信息。下面是一个简单的UserDetailsService实现示例:

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if ("user".equals(username)) {
            return new User(username, "$2a$10$xNvAsL1I2WYs4oBbKgNWwOqnq4gqEmsyGuG0QpPcasgDzCWr9L0W", Collections.emptyList());
        } else {
            throw new UsernameNotFoundException("User not found with username: " + username);
        }
    }

}

步骤4:配置SecurityConfigurer(SecurityConfiguration之前的中间配置)

现在我们需要配置SecurityConfigurer,以允许一些URL请求不需要认证。下面是一个简单的SecurityConfigurer示例:

@Configuration
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    private JwtUtils jwtUtils;

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter(this.userDetailsService, this.jwtUtils);
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .cors()
                    .and()
                .csrf()
                    .disable()
                .exceptionHandling()
                    .authenticationEntryPoint(jwtAuthenticationEntryPoint)
                    .and()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                .authorizeRequests()
                    .antMatchers("/auth/**")
                    .permitAll()
                    .anyRequest()
                    .authenticated();

        httpSecurity.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

步骤5:实现JWT认证逻辑

我们还需要实现一个JWT认证过滤器JwtAuthenticationFilter。该过滤器会读取请求头中的JWT并进行验证。下面是一个简单的JwtAuthenticationFilter实现示例:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final UserDetailsService userDetailsService;
    private final JwtUtils jwtUtils;

    public JwtAuthenticationFilter(UserDetailsService userDetailsService, JwtUtils jwtUtils) {
        this.userDetailsService = userDetailsService;
        this.jwtUtils = jwtUtils;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtUtils.getUsernameFromToken(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            if (jwtUtils.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }

        chain.doFilter(request, response);
    }

}

步骤6:实现REST API

最后,我们需要实现一些REST API,以便使用我们的JWT和Spring Security集成进行安全保护。下面是一个简单的REST API示例:

@RestController
@RequestMapping("/api")
public class ApiController {

@GetMapping("/greeting")
public String greeting() {
    return "Hello World!";
}

@GetMapping("/restricted")
public String restricted() {
    return "This is a restricted endpoint!";
}

}

步骤7:运行应用程序

现在我们已经准备好了使用JWT和Spring Security集成来保护我们的REST API,我们可以运行我们的应用程序并访问API。要实现访问需要授权的API,需要通过先进行授权的方式,来获取JWT token值。
例如在postman中,在header中设置如下即可:

Authorization: Bearer < JWT token >

这样将会自动携带JWT,来进行权限验证。

这就是使用JWT和Spring Security保护REST API的完整攻略。

两个有关JWT应用的例子:

在本攻略中实现的API服务中,GET请求的/api/restricted请示,需要具备已获得JWT Token的凭证才能请求。可以在登录后,获取JWT Token的过程中,把Token信息和获取到的用户信息存储在redis中,这样也就保障了JWT Token信息有匹配的已存储的登录用户信息,再在每一次请求/api/restricted的处理方法体中,比较JWT Token抽取到的登录用户名是否和redis中存储的信息一致即可。

第二个应用例子就是通过JWT的妙用,实现跨域授权调用的API请求授权。我曾经参加一个个人博客的小项目开发,在该项目中使用了Spring Security + JWT的技术方案实现用户注册、登录等主要功能。原本用户数据存储在第三方提供的LeanCloud数据库服务中,但后来开发过程中发现,部署的项目地址与LeanCloud产品服务直接存在跨域问题,导致基本无法对用户进行注册、登录等操作,只有当时使用了JWT token的方式,从服务器端生成一个Token,将该Token再响应给前端,并且前端存储该Token信息。在前端调用登录后的可访问API时,必须在HTTP的请求头中加入认证参数。而且这种请求即使在另外一个域名网络环境下,只要能构造出合法的请求头,就可以正常调用API服务,从而完成相关的业务处理。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Boot(四)之使用JWT和Spring Security保护REST API - Python技术站

(0)
上一篇 2023年5月20日
下一篇 2023年5月20日

相关文章

  • Java实现简单汽车租赁系统

    Java实现简单汽车租赁系统的完整攻略 系统需求分析 该汽车租赁系统应该具备以下功能: 显示当前的租赁车辆列表 租客可以查询所需汽车类型的库存量 租客可以租车,并计算租车天数、费用等信息 车辆归还,更新库存 系统设计 类的设计-属性和方法 Car(汽车类) 属性: carType:汽车类型 carId:汽车编号 carPrice:汽车租金(元/天) isRe…

    Java 2023年5月19日
    00
  • Java字节码ByteBuddy使用及原理解析上

    Java字节码ByteBuddy使用及原理解析 ByteBuddy是一个Java字节码操作框架,可以动态生成或修改字节码,被广泛应用于类代理、字节码增强、AOP和模拟对象等场景。本攻略将详细介绍ByteBuddy的使用方法及原理解析。 介绍ByteBuddy ByteBuddy的设计理念是轻量、易用、灵活和快速。它通过提供一个DSL(领域特定语言),使得我们…

    Java 2023年5月27日
    00
  • SpringBoot项目jar和war打包部署方式详解

    下面是关于“SpringBoot项目jar和war打包部署方式详解”的完整攻略: 1. Jar包部署方式 1.1 打包Jar包 在pom.xml文件中添加以下配置,可以打包成可执行jar文件: <build> <plugins> <!–打包为可执行jar文件–> <plugin> <groupId&g…

    Java 2023年5月26日
    00
  • java实现sunday算法示例分享

    下面是“java实现sunday算法示例分享”的完整攻略: 算法背景 Sunday算法是一种字符串匹配算法,在字符串匹配过程中可以快速地跳过一些无需匹配的字符,提高字符串匹配的效率。它的基本思想是在匹配的过程中尽可能地跳过一些字符,最大化地减少匹配次数。 算法实现 下面是Sunday算法的Java实现,包括主函数和辅助函数。 public class Sun…

    Java 2023年5月19日
    00
  • Mybatis批量插入大量数据的最优方式总结

    首先我们来讲解一下Mybatis批量插入大量数据的最优方式总结。在Mybatis中,批量操作可以大大提升插入大量数据的效率。下面是最优的批量插入的方式: 1. 基于JDBC批量操作 在Mybatis中,我们可以通过执行多个SQL语句的方式来实现批量操作。但这种方式效率低下,不推荐使用。相比之下,使用JDBC的批量操作要高效得多。可以使用JDBC批量操作来插入…

    Java 2023年5月20日
    00
  • Java基础之SpringBoot整合knife4j

    Java基础之SpringBoot整合knife4j 本文将介绍如何在SpringBoot项目中整合knife4j,以便于更强大的API文档管理和展示。 前置条件 在开始整合之前,需要确保已经具备以下条件: 熟悉Java基础知识; 熟悉SpringBoot框架; 了解Swagger(Swagger是Knife4j的核心依赖)。 整合步骤 1. 引入依赖 在p…

    Java 2023年5月19日
    00
  • Java中Spring的单例模式使用

    Java中Spring的单例模式使用可以说是Spring框架中最常用的一种设计模式,它通过保持一个对象的唯一实例,来使得在系统中所有需要该对象的地方都共享同一个实例。 下面我将详细介绍Java中Spring的单例模式使用的完整攻略,并提供两个代码示例以帮助理解。 1. Spring的单例模式使用背景 首先,我们需要了解Spring框架的单例模式使用背景。 在…

    Java 2023年5月19日
    00
  • java 查询oracle数据库所有表DatabaseMetaData的用法(详解)

    Java查询Oracle数据库所有表DatabaseMetaData的用法 在Java中,我们可以使用DatabaseMetaData接口来查询Oracle数据库的元数据信息,包括所有表、列、索引等信息。下面我们来详细介绍如何使用DatabaseMetaData查询Oracle数据库中所有表的信息。 步骤一:加载Oracle驱动程序 在使用Oracle的JD…

    Java 2023年5月19日
    00
合作推广
合作推广
分享本页
返回顶部