SpringBoot 整合 JWT 实现 Token 验证

前文

JWT —— 入门

JWT 请求流程

在这里插入图片描述

  • 用户使用账号和密码发出 post 请求
  • 服务器使用私钥创建一个 jwt
  • 服务器返回这个 jwt 给浏览器
  • 浏览器将该 jwt 串在请求头中向服务器发送请求
  • 服务器验证该 jwt
  • 返回响应的资源给浏览器

验证流程:

  • ① 在头部信息中声明加密算法和常量, 然后把header使用json转化为字符串
  • ② 在载荷中声明用户信息,同时还有一些其他的内容;再次使用json 把载荷部分进行转化,转化为字符串
  • ③ 使用在header中声明的加密算法和每个项目随机生成的secret来进行加密, 把第一步分字符串和第二部分的字符串进行加密, 生成新的字符串。词字符串是独一无二的。
  • ④ 解密的时候,只要客户端带着JWT来发起请求,服务端就直接使用secret进行解密

项目目录结构

com.example.jwtdemo
	config
		InterceptConfig.java
	controller
		UserController.java
	dao
		UserDao.java
	entity
		User.java
	intercepts
		JwtIntercept.java
	service
		UserService.java
		impl
			UserServiceImpl.java
	utils
		JwtUtil.java
	JwtDemoApplication.java

Maven 依赖

SpringBoot 使用的是 2.3.0 版本

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

    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.0</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.19</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</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>

application.properties

server.port=8080

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/数据库名?useSSL=false
spring.datasource.username=用户名
spring.datasource.password=密码

// 实体类包扫描
mybatis.type-aliases-package=com.example.jwtdemo.entity
mybatis.mapper-locations=classpath:mapper/*.xml

logging.level.com.example.jwtdemo.dao=debug

实体类

package com.example.jwtdemo.entity;

import lombok.Data;
import lombok.experimental.Accessors;

/**
 * @author Woo_home
 * @create 2020/9/13 23:09
 */

@Data
@Accessors(chain = true)
public class User {

    private String id;
    private String username;
    private String password;
}

Dao 层

用户登录接口

package com.example.jwtdemo.dao;

import com.example.jwtdemo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

/**
 * @author Woo_home
 * @create 2020/9/13 23:12
 */

@Mapper
@Repository
public interface UserDao {

    User login(User user);
}

UserDao.xml

<!DOCTYPE mapper PUBLIC
        "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.example.jwtdemo.dao.UserDao">
    <select id="login" parameterType="User" resultType="User">
        select * from user where username = #{username} and password = #{password}
    </select>
</mapper>

Service 层

跟 UserDao 一样

package com.example.jwtdemo.service;

import com.example.jwtdemo.entity.User;

/**
 * @author Woo_home
 * @create 2020/9/13 23:16
 */

public interface UserService {

    User login(User user);
}

实现类

package com.example.jwtdemo.service.impl;

import com.example.jwtdemo.dao.UserDao;
import com.example.jwtdemo.entity.User;
import com.example.jwtdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * @author Woo_home
 * @create 2020/9/13 23:16
 */

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public User login(User user) {
        // 根据接收用户名和密码查询数据库
        User userDB = userDao.login(user);
        if (userDB != null) {
            return userDB;
        }
        throw new RuntimeException("认证失败~~");
    }
}

JWT 封装类

主要是用于生成 token、验证 token、获取 token 信息

package com.example.jwtdemo.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;
import java.util.UUID;

/**
 * @author Woo_home
 * @create 2020/9/13 18:48
 */

public class JwtUtil {

    /**
     * 私钥
     */
    private static final String SALT = UUID.randomUUID().toString().replace("-", "");

    /**
     * 生成 token header.payload.sing
     * @param map
     * @return
     */
    public static String generToken(Map<String, String> map) {
        Calendar instance = Calendar.getInstance();
        // 默认 7 天过期
        instance.add(Calendar.DATE, 7);
        // 创建 JWT builder
        JWTCreator.Builder builder = JWT.create();
        map.forEach((k, v) -> {
            builder.withClaim(k, v);
        });
        // 指定令牌过期时间
        String token = builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SALT));

        return token;
    }

    /**
     * 验证 token 合法性
     * @param token
     */
    public static DecodedJWT verify(String token) {
        return JWT.require(Algorithm.HMAC256(SALT)).build().verify(token);
    }

    /**
     * 获取 token 信息
     * @param token
     * @return
     */
    public static DecodedJWT getTokenInfo(String token) {
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SALT)).build().verify(token);
        return verify;
    }
}

自定义 JWT 拦截器

package com.example.jwtdemo.intercepts;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.example.jwtdemo.utils.JwtUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Woo_home
 * @create 2020/9/14 14:50
 */

public class JwtIntercept implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
        Map<String, Object> map = new HashMap<>();
        // 获取请求头中的令牌
        String token = request.getHeader("token");
        try {
            // 验证令牌
            JwtUtil.verify(token);
            // 放行请求
            return true;
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg", "无效签名!");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            map.put("msg", "token 过期!");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            map.put("msg", "token 算法不一致!");
        } catch (Exception e) {
            e.printStackTrace();
            map.put("msg", "无效的 token!");
        }
        // 设置状态
        map.put("state", false);
        // 将 map 转为 json
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

将拦截器注册到 SpringMVC

将我们自定义的拦截器注册到 SpringMVC

package com.example.jwtdemo.config;

import com.example.jwtdemo.intercepts.JwtIntercept;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Woo_home
 * @create 2020/9/14 14:57
 */

@Configuration
public class InterceptConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	// 将我们自定义的拦截器注册进来
        registry.addInterceptor(new JwtIntercept())
                // 其它接口均需 token 验证
                .addPathPatterns("/user/test")
                // 所有用户可访问
                .excludePathPatterns("/user/login");
    }
}

控制器

package com.example.jwtdemo.controller;

import com.auth0.jwt.interfaces.DecodedJWT;
import com.example.jwtdemo.entity.User;
import com.example.jwtdemo.service.UserService;
import com.example.jwtdemo.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Woo_home
 * @create 2020/9/13 23:19
 */

@RestController
@Slf4j
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/user/login")
    public Map<String, Object> login(User user) {
        log.info("用户名:[{}]", user.getUsername());
        log.info("密码:[{}]", user.getPassword());
        Map<String, Object> map = new HashMap<>();
        try {
            user = userService.login(user);
            Map<String, String> payload = new HashMap<>();
            payload.put("id", user.getId());
            payload.put("username", user.getUsername());
            // 生成 JWT 的令牌
            String token = JwtUtil.generToken(payload);
            map.put("state", true);
            map.put("msg", "认证成功");
            map.put("token", token);
        } catch (Exception e) {
            map.put("state", false);
            map.put("msg", e.getMessage());
        }
        return map;
    }


    @PostMapping("/user/test")
    public Map<String, Object> test(HttpServletRequest request) {
        Map<String, Object> map = new HashMap<>();
        // 处理自己业务逻辑
        String token = request.getHeader("token");
        DecodedJWT verify = JwtUtil.verify(token);
        log.info("用户 id:[{}]", verify.getClaim("id").asString());
        log.info("用户 username:[{}]", verify.getClaim("username").asString());
        map.put("state", true);
        map.put("msg", "请求成功!");
        return map;
    }
}

测试

数据库中的数据如下:
在这里插入图片描述
我们启动项目,使用 postman 来测试一下,首先先输入一个错的用户名和密码
在这里插入图片描述
可以发现,认证失败了
我们再输入正确的用户名和密码
在这里插入图片描述
认证成功,并且给我们返回了一个 token
然后我们不带 token 去访问 localhost:8080/user/test
在这里插入图片描述
当我们在请求头 Header 加上正确的 token 时,再次验证
在这里插入图片描述
此时就能请求成功了
在这里插入图片描述
完整代码已上传到 Gitee, 下载地址

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot 集成 JWT 验证 Token 的步骤如下: 1. 引入相关依赖包 2. 实现自定义 JWT 验证过滤器 3. 在 Security 配置中配置 JWT 验证过滤器 4. 在需要验证的接口上添加 @PreAuthorize 注解 5. 处理 Token 相关操作(生成、刷新、验证等) 请注意,以上步骤仅是大致流程,具体实现可能会有所不同。如需细节请参考Spring Boot 官方文档或搜索相关教程。 ### 回答2: Spring Boot是一款非常方便的框架,可以快速搭建应用程序。同样的,JWT(JSON Web Token)也是一种非常流行的身份验证机制,使用标准的JSON格式,并且使用了数字签名方式验证身份。本文将介绍Spring Boot整合JWT验证Token。 首先,通过pom.xml文件引入相关依赖包: ``` <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 接下来创建JWTUtils类来处理Token的创建与验证: ``` @Component public class JWTUtils { // 密钥 private static final String SECRET = "mySecret"; // 过期时间毫秒 private static final long EXPIRATION_TIME = 1800000; // 创建token public static String createToken(String username, String role) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) .claim("roles", role) .signWith(SignatureAlgorithm.HS512, SECRET) .compact(); } // 验证token public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token); return true; } catch (SignatureException | ExpiredJwtException e) { return false; } } // 获取用户名 public static String getUsernameFromToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().getSubject(); } // 获取角色 public static String getRoleFromToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().get("roles").toString(); } } ``` 其中,createToken方法用于创建Token,validateToken方法用于验证Token,getUsernameFromToken和getRoleFromToken用于获取Token中的信息。 接下来,在Spring Boot中配置WebSecurityConfig: ``` @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 跨域请求会先进行一次options请求 .antMatchers("/auth/**").permitAll() // 登录注册接口放行 .anyRequest().authenticated() .and() .addFilter(new JWTAuthenticationFilter(authenticationManager())) .addFilter(new JWTAuthorizationFilter(authenticationManager())) .headers().cacheControl(); } @Override public void configure(WebSecurity web) { web.ignoring().antMatchers( "/error", "/v2/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**"); } @Autowired public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` 其中,configure方法配置了安全规则,addFilter方法添加了JWT的过滤器,configureAuthentication方法配置用户认证方式。 最后,在Controller中使用接口进行调用: ``` @RestController public class UserController { @Autowired private UserService userService; @PostMapping("/auth/register") public String register(@RequestBody UserDTO userDTO) { if (userService.register(userDTO)) { return "注册成功"; } else { return "注册失败"; } } @PostMapping("/auth/login") public ResponseEntity<TokenDTO> login(@RequestParam String username, @RequestParam String password) { String role = "ROLE_USER"; // 这里演示只有ROLE_USER角色 String token = JWTUtils.createToken(username, role); return ResponseEntity.ok(new TokenDTO(token)); } @GetMapping("/user/info") public String userInfo(Authentication authentication) { String username = authentication.getName(); String role = JWTUtils.getRoleFromToken(((JwtUserDetails) authentication.getPrincipal()).getToken()); return username + "-" + role; } } ``` 其中,login方法用于登录获取Token,userInfo方法用于获取当前用户信息。这里使用了Spring Security提供的authentication对象来获取用户信息。 综上,Spring Boot整合JWT验证Token并不复杂,通过相关依赖包、JWTUtils、WebSecurityConfig和Controller的配置,即可完成Token身份验证,保证接口的安全性。 ### 回答3: Spring Boot是一个Java开发框架,它可以减少Web应用程序的开发时间和复杂性。在开发Web应用程序时,安全性是一个关键问题,可以使用JWT(Json Web Token)来提高应用程序的安全性。 JWT是一种基于JSON的安全令牌,可以在用户与应用程序之间传递信息。JWT包含了许多有用的信息,例如用户的ID、角色、权限等等。在Spring Boot中,我们可以使用jwt.io上的库来生成和解码JWT令牌。 首先,在pom.xml文件中添加jwt库的依赖项,如下所示: ``` <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> ``` 接下来,我们需要创建一个JWTUtils工具类。该类将用于编码、解码和验证JWT令牌。实现代码如下: ``` import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; @Component public class JWTUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration * 1000); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { Claims claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } public boolean validateToken(String token, UserDetails userDetails) { String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } private boolean isTokenExpired(String token) { Date expiryDate = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody() .getExpiration(); return expiryDate.before(new Date()); } } ``` 在上面的代码中,我们使用@Value注释从application.properties文件中读取秘密和过期时间。generateToken方法是用于生成JWT令牌的方法。setSubject()定义了JWT令牌的主题,setIssuedAt()是JWT生成时间,setExpiration()是JWT令牌的超时时间。最后,我们使用signWith()进行签名和压缩。 getUsernameFromToken()方法获取JWT令牌中的用户名。validateToken()用于验证JWT令牌是否有效。isTokenExpired()方法检查JWT令牌是否已过期。 为了让Spring Boot知道我们在哪里可以找到JWTUtils类,我们需要在Application.java类中添加@ComponentScan注解,如下所示: ``` @SpringBootApplication @ComponentScan({"com.example.jwt"}) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } ``` 最后,我们在Spring Boot中创建一个Controller类。该类有一个login()方法,该方法接收用户名和密码并验证该用户是否存在。如果用户存在,则生成JWT令牌并将其返回给客户端。如下所示: ``` import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.web.bind.annotation.*; import com.example.jwt.JWTUtils; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthenticationManager authenticationManager; @Autowired private JWTUtils jwtUtils; @Autowired private UserDetailsService userDetailsService; @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) throws AuthenticationException { try { authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword())); } catch (UsernameNotFoundException e) { throw new RuntimeException("User not found"); } final UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername()); final String token = jwtUtils.generateToken(userDetails.getUsername()); return ResponseEntity.ok(new AuthResponse(token)); } } ``` 在上面的代码中,我们使用@Autowired注释从其他类中获取所需的依赖项。login()方法中,我们使用authenticationManager.authenticate()方法验证用户是否存在。如果该用户不存在,则会抛出一个异常。然后,我们使用userDetailsService.loadUserByUsername()方法加载用户详细信息。最后,我们调用JWTUtils.generateToken()方法生成JWT令牌。 在上面的代码中,我们还创建了一个名为AuthResponse的简单类,该类是返回的响应对象,包含JWT令牌。 ``` public class AuthResponse { private String token; public AuthResponse(String token) { this.token = token; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } } ``` 以上就是Spring Boot整合JWT验证token的简单代码示例。该代码示例可以让开发者更加容易地理解Spring Boot如何使用JWT来增强应用程序的安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值