SpringSecurity结合JwtToken验证(后端部分)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringSecurity结合JwtToken验证(后端部分)

SpringSecurity结合JwtToken验证

简介:本文在SpringSecurity基础公共之上,整合JwtToken功能,本文是后端部分。

对于SpringSecurity基本功能,可以看这篇文章:SpringSecurity入门案例——基本功能讲解

项目代码地址:https://gitee.com/geek-li-hua/code-in-blog.git

项目准备

添加依赖

在 pom.xml 中添加下列依赖:

  • jjwt-api
  • jjwt-impl
  • jjwt-jackson
<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>
  • 完整的pom.xml
<?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.7.15</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>springsecurity-back-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springsecurity-back-demo</name>
    <description>springsecurity-back-demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- Spring Boot Starter JDBC -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- Project Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- MySQL Connector/J -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
        </dependency>
        <!-- MyBatis Plus Boot Starter -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!-- MyBatis Plus Generator -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.3</version>
        </dependency>
        <!-- Spring Boot Starter Security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.7.0</version>
        </dependency>
        <!-- JJWT API -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.2</version>
        </dependency>
        <!-- JJWT Implementation -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
        <!-- JJWT Jackson -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.2</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

实现 JwtUtil 类

JwtUtil 类为jwt 工具类,用来创建、解析 jwt token

@Component
public class JwtUtil {
    // 设置JWT的有效期为14天
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;
    // 设置JWT的密钥
    public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";
    // 生成UUID
    public static String getUUID() {
        return UUID.randomUUID().toString().replaceAll("-", "");
    }
    // 创建JWT
    public static String createJWT(String subject) {
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }
    // 构建JWT的Builder
    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
        // 使用HS256算法进行签名
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        // 生成密钥
        SecretKey secretKey = generalKey();
        // 获取当前时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        // 如果未指定有效期,则使用默认的14天
        if (ttlMillis == null) {
            ttlMillis = JwtUtil.JWT_TTL;
        }
        // 计算过期时间
        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        // 构建JWT
        return Jwts.builder()
                .setId(uuid)  // 设置JWT的唯一标识
                .setSubject(subject)  // 设置JWT的主题
                .setIssuer("sg")  // 设置JWT的签发者
                .setIssuedAt(now)  // 设置JWT的签发时间
                .signWith(signatureAlgorithm, secretKey)  // 使用密钥进行签名
                .setExpiration(expDate);  // 设置JWT的过期时间
    }
    // 生成密钥
    public static SecretKey generalKey() {
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }
    // 解析JWT
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)  // 使用密钥进行验证
                .build()
                .parseClaimsJws(jwt)  // 解析JWT
                .getBody();  // 获取JWT的内容
    }
}

实现 JwtAuthenticationTokenFilter 类

实现 JwtAuthenticationTokenFilter 类,用来验证 jwt token ,如果验证成功,则将 User 信息注入上下文中。

/**
 * JwtAuthenticationTokenFilter 是用于JWT身份验证的过滤器。
 * 它继承了 OncePerRequestFilter 类,确保过滤器每个请求只应用一次。
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    private IUserService userService;
    /**
     * 对每个传入的请求调用此方法,执行JWT身份验证过程。
     *
     * @param request       表示传入请求的 HttpServletRequest 对象。
     * @param response      表示要发送的 HttpServletResponse 对象。
     * @param filterChain   用于调用链中的下一个过滤器的 FilterChain 对象。
     * @throws ServletException     处理请求时发生异常。
     * @throws IOException          发生I/O异常。
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
        // 从请求头中获取JWT令牌
        String token = request.getHeader("Authorization");
        // 如果令牌为空或不以"Bearer "开头,则跳过身份验证过程
        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }
        // 移除令牌中的"Bearer "前缀
        token = token.substring(7);
        String userid;
        try {
            // 解析JWT令牌以获取声明,包括用户ID
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            // 如果在令牌解析过程中发生异常,则抛出运行时异常
            throw new RuntimeException(e);
        }
        // 使用用户ID从用户服务中检索用户
        User user = userService.getById(Integer.parseInt(userid));
        // 如果用户不存在,则抛出运行时异常
        if (user == null) {
            throw new RuntimeException("用户名未登录");
        }
        // 创建表示已认证用户的 UserDetailsImpl 对象
        UserDetailsImpl loginUser = new UserDetailsImpl(user);
        // 创建包含已认证用户的身份验证令牌,并将其设置在安全上下文中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        // 调用链中的下一个过滤器
        filterChain.doFilter(request, response);
    }
}

配置config.SecurityConfig类

放行登录、注册等接口。

/**
 * SecurityConfig 是用于配置 Spring Security 的类。
 * 它使用 @Configuration 注解将其标记为配置类,并使用 @EnableWebSecurity 注解启用 Web 安全功能。
 * 它继承了 WebSecurityConfigurerAdapter 类,用于自定义 Spring Security 的配置。
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    /**
     * 配置密码编码器的 Bean。
     *
     * @return 用于密码编码的 PasswordEncoder 对象。
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    /**
     * 配置 AuthenticationManager 的 Bean。
     *
     * @return AuthenticationManager 对象。
     * @throws Exception 如果配置 AuthenticationManager 失败。
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    /**
     * 配置 Spring Security 的 HTTP 安全性。
     *
     * @param http HttpSecurity 对象,用于配置 HTTP 安全性。
     * @throws Exception 如果配置 HttpSecurity 失败。
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/user/account/token/", "/user/account/register/").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();
        // 将 JwtAuthenticationTokenFilter 添加到 UsernamePasswordAuthenticationFilter 之前
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

实现接口API

实现API需要三个相关类或接口:

  • 在 service 下创建一个接口。
  • 在 service 下创建一个类实现这个接口。
  • 在 controller 下创建一个类进行操作。
1. 实现 LoginService

验证用户名密码,验证成功后返回 jwt token(令牌)

创建接口:在 service下 创建 user 创建 account 新建一个接口 LoginService

import java.util.Map;
public interface LoginService {
    public Map<String, String> getToken(String username, String password);
}

创建实现类:在 service下 impl 下实现一个LoginServiceImpl

@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    private AuthenticationManager authenticationManager;
    /**
     * 通过用户名和密码获取令牌。
     *
     * @param username 用户名。
     * @param password 密码。
     * @return 包含令牌信息的 Map 对象。
     */
    @Override
    public Map<String, String> getToken(String username, String password) {
        // 创建一个 UsernamePasswordAuthenticationToken 对象,用于进行身份验证
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        // 使用 AuthenticationManager 对象进行身份验证
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        // 从身份验证结果中获取认证的用户信息
        UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
        User user = loginUser.getUser();
        // 创建 JWT 令牌
        String jwt = JwtUtil.createJWT(user.getId().toString());
        // 创建一个 Map 对象,用于存储令牌信息
        Map<String, String> map = new HashMap<>();
        map.put("error_message", "success");
        map.put("token", jwt);
        return map;
    }
}

创建一个:LoginController

@RestController
public class LoginController {
    @Autowired
    private LoginService loginService;
    @PostMapping("/user/account/token/")
    public Map<String, String> getToken(@RequestParam Map<String, String> map) {
        // 从请求参数中获取用户名和密码
        String username = map.get("username");
        String password = map.get("password");
        // 打印用户名和密码
        System.out.println(username + ' ' + password);
        // 调用登录服务的方法获取令牌
        return loginService.getToken(username, password);
    }
}

2.配置InforService类

根据令牌返回用户信息

创建接口:在 service下 新建一个接口 InfoService

public interface InfoService {
    /*
    * 根据token返回用户信息
    * */
    public Map<String, String> getInfo();
}

创建实现类:在 service下 impl 下新建一个实现类InfoServiceImpl

/**
 * 这个类是InfoService接口的实现类。
 * 它提供了根据token获取用户信息的方法。
 */
@Service
public class InfoServiceImpl implements InfoService {
    /**
     * 根据token返回用户信息。
     * @return 包含用户信息的map
     */
    @Override
    public Map<String, String> getInfo() {
        // 从安全上下文中获取认证token
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        // 从认证token中获取已登录的用户详情
        UserDetailsImpl loginUser = (UserDetailsImpl) authentication.getPrincipal();
        User user = loginUser.getUser();
        // 创建一个map来存储用户信息
        Map<String, String> map = new HashMap<>();
        map.put("error_message", "success");
        map.put("id", user.getId().toString());
        map.put("username", user.getUsername());
        return map;
    }
}

创建controller:在 controller 创建一个InfoController

/**
 * 这个类是一个REST控制器,用于处理与用户信息相关的请求。
 */
@RestController
public class InfoController {
    @Autowired
    private InfoService infoService;
    /**
     * 获取用户信息。
     * @return 包含用户信息的map
     */
    @GetMapping("/user/account/info/")
    public Map<String, String> getInfo() {
        // 调用infoService的getInfo()方法来获取用户信息
        return infoService.getInfo();
    }
}

3. 配置RegisterService类

注册账号

创建接口:在 service下 新建一个接口 RegisterService

public interface RegisterService {
    /*
    * 注册账号
    *
    * @param username 用户名
    * @param password 密码
    * */
    public Map<String, String> register(String username, String password, String confirmedPassword);
}

创建实现类:在 service下 新建一个实现类RegisterServiceImpl

/**
 * 这个类是RegisterService接口的实现类。
 * 它提供了注册用户的方法。
 */
@Service
public class RegisterServiceImpl implements RegisterService {
    @Autowired
    private UserMapper userMapper; // 用户数据访问对象
    @Autowired
    private PasswordEncoder passwordEncoder; // 密码加密器
    /**
     * 注册新用户。
     * @param username 用户名
     * @param password 密码
     * @param confirmedPassword 确认密码
     * @return 包含注册结果的map
     */
    @Override
    public Map<String, String> register(String username, String password, String confirmedPassword) {
        Map<String, String> map = new HashMap<>(); // 创建一个map对象用于存储注册结果
        // 检查用户名是否为空
        if (username == null) {
            map.put("error_message", "用户名不能为空");
            return map;
        }
        // 检查密码是否为空
        if (password == null || confirmedPassword == null) {
            map.put("error_message", "密码不能为空");
            return map;
        }
        // 删除用户名首尾的空白字符
        username = username.trim();
        // 检查用户名是否为空
        if (username.length() == 0) {
            map.put("error_message", "用户名不能为空");
            return map;
        }
        // 检查密码是否为空
        if (password.length() == 0 || confirmedPassword.length() == 0) {
            map.put("error_message", "密码不能为空");
            return map;
        }
        // 检查用户名长度是否超过限制
        if (username.length() > 100) {
            map.put("error_message", "用户名长度不能大于100");
            return map;
        }
        // 检查密码长度是否超过限制
        if (password.length() > 100 || confirmedPassword.length() > 100) {
            map.put("error_message", "密码不能大于100");
            return map;
        }
        // 检查两次输入的密码是否一致
        if (!password.equals(confirmedPassword)) {
            map.put("error_message", "两次输入的密码不一致");
            return map;
        }
        // 查询用户名是否已存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
        queryWrapper.eq("username", username);
        List<User> users = userMapper.selectList(queryWrapper);
        if (!users.isEmpty()) {
            map.put("error_message", "用户名已存在");
            return map;
        }
        // 添加一个新用户
        String encodedPassword = passwordEncoder.encode(password); // 对密码进行加密处理
        User user = new User(null, username, encodedPassword); // 创建一个新的用户对象
        userMapper.insert(user); // 将用户对象插入数据库
        map.put("error_message", "success"); // 注册成功
        return map;
    }
}

相关文章
|
5月前
|
JavaScript NoSQL Redis
Vue中实现修改邮箱、手机号等流程的大致过程、验证码由后端的redis生成验证(版本1.0)
这篇文章记录了在Vue中实现修改手机号和邮箱的大致流程,包括使用过滤器部分隐藏展示的手机号和邮箱,以及通过点击触发路由跳转的便捷方式。文章还描述了旧号码和新号码验证的界面实现,其中验证码由后端生成并通过弹窗展示给用户,未来可以接入真正的手机验证码接口。此外,还提供了修改邮箱的页面效果截图,并强调了学习是一个永无止境的过程。
Vue中实现修改邮箱、手机号等流程的大致过程、验证码由后端的redis生成验证(版本1.0)
|
3月前
|
存储 前端开发 Java
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
本文介绍了使用Kaptcha插件在SpringBoot项目中实现验证码的生成和验证,包括后端生成验证码、前端展示以及通过session进行验证码校验的完整前后端代码和配置过程。
348 0
验证码案例 —— Kaptcha 插件介绍 后端生成验证码,前端展示并进行session验证(带完整前后端源码)
|
5月前
|
前端开发 JavaScript
这篇文章介绍了如何使用form表单结合Bootstrap格式将前端数据通过action属性提交到后端的servlet,包括前端表单的创建、数据的一级和二级验证,以及后端servlet的注解和参数获取。
这篇文章介绍了使用AJAX技术将前端页面中表单接收的多个参数快速便捷地传输到后端servlet的方法,并通过示例代码展示了前端JavaScript中的AJAX调用和后端servlet的接收处理。
这篇文章介绍了如何使用form表单结合Bootstrap格式将前端数据通过action属性提交到后端的servlet,包括前端表单的创建、数据的一级和二级验证,以及后端servlet的注解和参数获取。
|
5月前
|
前端开发 开发者 UED
数据校验的艺术:揭秘JSF如何将前端与后端验证合二为一,打造无缝用户体验
【8月更文挑战第31天】JavaServer Faces(JSF)是构建企业级Web应用的Java规范,提供了丰富的组件和API,便于快速搭建用户界面。JSF验证框架基于JavaBean验证API(JSR 303/JSR 380),利用注解如`@NotNull`、`@Size`等在模型类上定义验证规则,结合前端的`&lt;h:inputText&gt;`和`&lt;h:message&gt;`标签展示错误信息。
55 0
|
7月前
|
JSON 算法 Go
go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
go语言后端开发学习(一)——JWT的介绍以及基于JWT实现登录验证
|
前端开发 JavaScript API
php的短信验证的流程,如何实现前端js加后端php
php的短信验证的流程,如何实现前端js加后端php
127 0
|
SQL 缓存 前端开发
前、后端登录验证逻辑
前、后端登录验证逻辑
589 1
|
前端开发
flask+vue的前端发送与后端验证
前端发送与后端验证
187 0
|
中间件 数据库
【Node.js+koa--后端管理系统】用户登录接口设计 | 登录验证 | 登录返回凭证(令牌)
【Node.js+koa--后端管理系统】用户登录接口设计 | 登录验证 | 登录返回凭证(令牌)
306 0
【Node.js+koa--后端管理系统】用户登录接口设计 | 登录验证 | 登录返回凭证(令牌)
|
25天前
|
存储 缓存 负载均衡
后端开发中的性能优化策略
本文将探讨几种常见的后端性能优化策略,包括代码层面的优化、数据库查询优化、缓存机制的应用以及负载均衡的实现。通过这些方法,开发者可以显著提升系统的响应速度和处理能力,从而提供更好的用户体验。
51 4

热门文章

最新文章