下面是关于Spring Boot如何使用JWT和Spring Security保护REST API的攻略:
什么是JWT?
JSON Web Token(JWT)是一种开放标准(RFC 7519),用于通过网络在各方之间安全地传输声明。JSON Web Token是由三部分组成:标题,声明和签名。例如:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik15IEFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT由以下三部分组成:
- Header:JWT头部承载两部分信息,声明
JWT
以及所使用的算法。如:
{
"alg": "HS256",
"typ": "JWT"
}
- Payload:载荷,即所携带的信息,包含标准中注册的声明(建议但不强制),私有声明和公共声明。
{
"sub": "1234567890",
"name": "My Admin",
"iat": 1516239022
}
- Signature:签名,为确保
JWT
未被篡改,JWT
的签名部分采用header
和payload
字符流按照规定的算法进行加密后的结果。
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技术站