SpringBoot集成 Jwt 实现Token验证访问拦截

22 篇文章 1 订阅
11 篇文章 0 订阅

1、Jwt 简介

    JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作        为JSON对象传输。由于此信息是经过数字签名的,因此可以进行验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或        ECDSA的公钥/私钥对对JWT进行签名。 


2、应用场景

  • 授权:这是使用JWT最常见的方案。当用户登录后,每个后续请求将会在header带上JWT,允许用户访问允许使用该令牌的路由、服务和资源。单点登录是当今广泛使用JWT的一个特性,因为它具有较小的开销和易于跨不同域使用的能力。
  • 信息交换:JWT是保证各方之间安全地传输信息的一种好方法。因为JWT可以被签名,例如使用公钥/私钥对,可以确保发件人是他们所说的人。另外,由于使用header和playload计算签名,还可以验证内容是否被篡改。 

 3、Jwt结构

     JSON Web令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔,分别是:

  • 标头
  • 有效载荷
  • 签名

     因此,JWT通常如下所示。

   xxxxx.yyyyy.zzzzz


4、Jwt工作流程

     1. 用户使用账号和面发出post请求;
     2. 服务器使用私钥创建一个jwt;
     3. 服务器返回这个jwt给浏览器;
     4. 浏览器将该jwt串在请求头中像服务器发送请求;
     5. 服务器验证该jwt;
     6. 返回响应的资源给浏览器。


5、SpringBoot 集成 Jwt

  • 工作目录介绍

没有连接数据库

  • pom配置文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.example</groupId>
  <artifactId>loginintercept</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>loginintercept</name>
  <description>Demo project for Spring Boot</description>

  <properties>
    <java.version>1.8</java.version>
  </properties>

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

    <!--  jwt   -->
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt</artifactId>
      <version>0.7.0</version>
    </dependency>

    <!-- lombok   -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.12</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

</project>
  • JwtUtils 工具类
package com.example.loginintercept.config;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import org.springframework.stereotype.Component;

/**
 * token 工具类
 *
 * @author ch
 * @version 1.0.0
 * @since 1.0.0
 * <p>
 * Created at 2020/7/30 2:23 下午
 */
@Component
public class JwtUtils {

  // 过期时间
  private static long expire = 604800;
  // 秘钥
  private static String secret = "HSyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9";

  /**
   * 创建一个token
   *
   * @param userId
   * @return
   */
  public String generateToken(String userId) {
    Date now = new Date();
    Date expireDate = new Date(now.getTime() + expire);
    return Jwts.builder().setHeaderParam("type", "JWT").setSubject(userId).setIssuedAt(now)
        .setExpiration(expireDate).signWith(
            SignatureAlgorithm.HS512, secret).compact();
  }

  /**
   * 解析token
   */
  public Claims getClaimsByToken(String token) {
    try {
      return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    } catch (Exception e) {
      System.out.println("validate is token error");
      return null;
    }
  }

  /**
   * 判断 token 是否过期
   */
  public boolean isTokenExpired(Date expiration){
    return expiration.before(new Date());
  }

}
  • ResultT 统一返回格式
package com.example.loginintercept.config;

import java.util.HashMap;
import java.util.Map;

/**
 * TODO
 *
 * @author ch
 * @version 1.0.0
 * @since 1.0.0
 * <p>
 * Created at 2020/8/6 5:04 下午
 */
public class ResultT extends HashMap<String, Object> {

  public ResultT() {
    put("code", 0);
    put("msg", "success");
  }

  public static ResultT ok() {
    ResultT t = new ResultT();
    t.put("msg", "操作成功");
    return t;
  }

  public static ResultT ok(String msg) {
    ResultT t = new ResultT();
    t.put("msg", msg);
    return t;
  }

  public static ResultT ok(Map<String, Object> map) {
    ResultT t = new ResultT();
    t.putAll(map);
    return t;
  }

  public static ResultT error(int code, String msg) {

    ResultT t = new ResultT();
    t.put("code", code);
    t.put("msg", msg);
    return t;
  }

  public static ResultT error() {
    return error(500, "未知异常");
  }

  public ResultT put(String key, Object value){
    super.put(key, value);
    return this;
  }
}
  • TokenInterceptor 创建一个token拦截器
package com.example.loginintercept.config;

import com.example.loginintercept.exception.TokenRuntimeException;
import io.jsonwebtoken.Claims;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

/**
 * 创建一个 token 拦截器.
 * 需要继承 HandlerInterceptorAdapter,并且声明为spring的组件
 * @author ch
 * @version 1.0.0
 * @since 1.0.0
 * <p>
 * Created at 2020/7/30 2:19 下午
 */
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {

  // 注入jwt工具类
  @Autowired
  private JwtUtils jwtUtils;

  // 重写 前置拦截方法
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
      throws Exception {
    // 1、从请求头中获取token
    String token = request.getHeader("token");

    // 2、判断 token 是否存在
    if (token == null ||"".equals(token)) {
      System.out.println("未登录");
      // 这里可以自定义 抛出 token 异常
      throw new TokenRuntimeException("未登录");
    }

    // 3、解析token
    Claims claim = jwtUtils.getClaimsByToken(token);

    if (null == claim) {
      System.out.println("token 解析错误");
      // 这里可以自定义 抛出 token 异常
      throw  new TokenRuntimeException("token 解析错误");
    }

    // 4、判断 token 是否过期
    Date expiration = claim.getExpiration();
    boolean tokenExpired = jwtUtils.isTokenExpired(expiration);
    if (tokenExpired) {
      System.out.println("token已过期,请重新登录");
      // 这里可以自定义 抛出 token 异常
      throw new TokenRuntimeException("token已过期,请重新登录");
    }

    // 5、 从 token 中获取员工信息
    String subject = claim.getSubject();

    // 6、去数据库中匹配 id 是否存在 (这里直接写死了)
    if (null == subject) {
      System.out.println("员工不存在");
      // 这里可以自定义 抛出 token 异常
      throw new TokenRuntimeException("员工不存在");
    }

    // 7、成功后 设置想设置的属性,比如员工姓名
    request.setAttribute("userId", subject);
    request.setAttribute("userName", "张三");

    return true;
  }
}
  • WebMvcConfig 设置拦截器
package com.example.loginintercept.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 设置拦截器.
 * 打上configuration 注解,标注为配置项
 * @author ch
 * @version 1.0.0
 * @since 1.0.0
 * <p>
 * Created at 2020/7/30 2:17 下午
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  // 注入 token 拦截器
  @Autowired
  private TokenInterceptor interceptor;

  /**
   * 重写添加拦截器
   */
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    // 添加自定义拦截器,并拦截对应 url
    registry.addInterceptor(interceptor).addPathPatterns("/gateway/**");
  }
}
  • GatewayController 模仿需要登录后才能访问的资源
package com.example.loginintercept.controller;

import com.example.loginintercept.config.ResultT;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * controller
 *
 * @author ch
 * @version 1.0.0
 * @since 1.0.0
 * <p>
 * Created at 2020/8/6 4:52 下午
 */
@RestController
@RequestMapping("/gateway")
public class GatewayController {

  @RequestMapping(value = "/find",method = RequestMethod.POST)
  public ResultT find(){
    return ResultT.ok("find one success");
  }

}
  • LoginController 登录
package com.example.loginintercept.controller;

import com.example.loginintercept.config.JwtUtils;
import com.example.loginintercept.config.ResultT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * 登录
 *
 * @author ch
 * @version 1.0.0
 * @since 1.0.0
 * <p>
 * Created at 2020/8/6 5:12 下午
 */
@RestController
public class LoginController {
  // 注入jwt工具类
  @Autowired
  private JwtUtils jwtUtils;
  @RequestMapping(value = "/login", method = RequestMethod.POST)
  public ResultT login(String name, String psw) {
    String userId = "132";
    String token = jwtUtils.generateToken(userId);
    return ResultT.ok().put("token", token);
  }
}
  • SysRuntimeExceptionHandler 全局异常处理
package com.example.loginintercept.exception;

import com.example.loginintercept.config.ResultT;
import com.example.loginintercept.exception.TokenRuntimeException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * 全局异常处理
 *
 * @author ch
 * @version 1.0.0
 * @since 1.0.0
 * <p>
 * Created at 2020/8/6 5:03 下午
 */
@RestControllerAdvice
public class SysRuntimeExceptionHandler {

  @ExceptionHandler(TokenRuntimeException.class)
  public ResultT tokenRuntimeException(TokenRuntimeException e) {
    e.printStackTrace();
    return ResultT.error(e.getCode(), e.getMsg());
  }

  @ExceptionHandler(Exception.class)
  public ResultT handlerException(Exception e){
    e.printStackTrace();
    return ResultT.error();
  }
}
  • TokenRuntimeException 自定义 token 异常
package com.example.loginintercept.exception;

import lombok.Data;

/**
 * 自定义 token 异常
 *
 * @author ch
 * @version 1.0.0
 * @since 1.0.0
 * <p>
 * Created at 2020/8/6 4:58 下午
 */
@Data
public class TokenRuntimeException extends RuntimeException{

  private Integer code = 401;
  private String msg;

  public TokenRuntimeException(String msg) {
    this.msg = msg;
  }

}

6、运行测试

  • 首先访问需要 token 的资源

  • 登录 获取 token

  • 请求头中存放 token 再次访问需要 token 的资源

代码地址: git@github.com:ChenHaoXFN/loginintercept.git

                  https://github.com/ChenHaoXFN/loginintercept

参考文献:https://jwt.io/introduction/

                  https://www.jianshu.com/p/e88d3f8151db

 

  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
在Spring Boot中,可以使用以下步骤将JWT集成到应用程序中以实现token认证: 1. 添加依赖项 在pom.xml文件中添加以下依赖项: ``` <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 创建Token工具类 创建一个JwtTokenUtil工具类,该类将用于生成和验证JWT令牌。以下是一个基本的JwtTokenUtil类: ```java public class JwtTokenUtil { private static final String SECRET_KEY = "secret"; public static String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + 3600000); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (SignatureException ex) { System.out.println("Invalid JWT signature"); } catch (MalformedJwtException ex) { System.out.println("Invalid JWT token"); } catch (ExpiredJwtException ex) { System.out.println("Expired JWT token"); } catch (UnsupportedJwtException ex) { System.out.println("Unsupported JWT token"); } catch (IllegalArgumentException ex) { System.out.println("JWT claims string is empty."); } return false; } } ``` 3. 创建安全配置类 创建一个SecurityConfig类,该类将用于配置Spring Security以使用JWT进行认证。以下是一个基本的SecurityConfig类: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired private JwtUserDetailsService jwtUserDetailsService; @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder()); } @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @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(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } } ``` 4. 创建用户详细信息服务类 创建一个JwtUserDetailsService类,该类将用于从数据库或其他存储中获取用户信息以进行身份验证。以下是一个基本的JwtUserDetailsService类: ```java @Service public class JwtUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 在这里获取用户信息并返回UserDetails对象 return null; } } ``` 5. 创建身份验证过滤器 创建一个JwtAuthenticationFilter类,该类将用于拦截所有请求,并进行JWT身份验证。以下是一个基本的JwtAuthenticationFilter类: ```java public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUserDetailsService jwtUserDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String requestTokenHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; // JWT Token is in the form "Bearer token". Remove Bearer word and get only the Token if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { jwtToken = requestTokenHeader.substring(7); try { username = jwtTokenUtil.getUsernameFromToken(jwtToken); } catch (IllegalArgumentException e) { System.out.println("Unable to get JWT Token"); } catch (ExpiredJwtException e) { System.out.println("JWT Token has expired"); } } else { logger.warn("JWT Token does not begin with Bearer String"); } // Once we get the token validate it. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username); // if token is valid configure Spring Security to manually set authentication if (jwtTokenUtil.validateToken(jwtToken)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // After setting the Authentication in the context, we specify // that the current user is authenticated. So it passes the // Spring Security Configurations successfully. SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } chain.doFilter(request, response); } } ``` 6. 创建身份验证控制器 创建一个JwtAuthenticationController类,该类将用于处理身份验证请求,生成JWT令牌并返回给客户端。以下是一个基本的JwtAuthenticationController类: ```java @RestController public class JwtAuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JwtTokenUtil jwtTokenUtil; @Autowired private JwtUserDetailsService jwtUserDetailsService; @RequestMapping(value = "/authenticate", method = RequestMethod.POST) public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception { authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword()); final UserDetails userDetails = jwtUserDetailsService .loadUserByUsername(authenticationRequest.getUsername()); final String token = jwtTokenUtil.generateToken(userDetails.getUsername()); return ResponseEntity.ok(new JwtResponse(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); } } } ``` 现在,您的Spring Boot应用程序已经集成JWT以进行身份验证

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值