spring boot集成mybatis和springsecurity实现登录认证功能

news2024/11/17 22:34:35

参考了很多网上优秀的教程,结合自己的理解,实现了登录认证功能,不打算把理论搬过来,直接上代码可能入门更快,文中说明都是基于我自己的理解写的,可能存在表述或者解释不对的情况,如果需要理论支撑,可以网上在找一下相关文章学习,或者直接访问框架官网Hello Spring Security :: Spring Security

本文主要通过IDEA创建spring boot工程,并加载mybatis和spring security相关配置,完成系统登录认证功能

创建spring boot工程

点击File->New->Project,分别按照下述步骤创建spring boot工程

依次在Developer Tools选择Spring Boot DevTools、Lombok

Security选择spring security

在SQL目录下选择MyVatis Framework 和MySQL Driver,右侧可以看到已经选择的框架,上方可以选择spring boot的版本

这几项选完以后,spring boot就会自动帮我们加载好这些框架\依赖

因为spring boot的新版本不太熟悉,可以改成比较成熟的2.4.2版本

因为后边的开发中,会继续添加几个相关的依赖,所以后边的内容均以已经完成的完整项目代码为例进行说明

完整的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.4.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.sgp</groupId>
    <artifactId>ss</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ss</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <mysql-connector-java.version>8.0.17</mysql-connector-java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <!--<dependency>-->
            <!--<groupId>org.thymeleaf.extras</groupId>-->
            <!--<artifactId>thymeleaf-extras-springsecurity6</artifactId>-->
        <!--</dependency>-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql-connector-java.version}</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--<dependency>-->
            <!--<groupId>org.mybatis.spring.boot</groupId>-->
            <!--<artifactId>mybatis-spring-boot-starter-test</artifactId>-->
            <!--<version>3.0.3</version>-->
            <!--<scope>test</scope>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.19.2</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.4.6</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.2.1.Final</version>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <!--<resource>-->
                <!--<directory>src/main/java/com/sgp/ss/mapper</directory>-->
                <!--<targetPath>mapper</targetPath>-->
            <!--</resource>-->
            <!--<resource>-->
                <!--<directory>src/main/resources</directory>-->
            <!--</resource>-->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <!--<include>**/*.properties</include>-->
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <!--<resource>-->
                <!--<directory>src/main/resources</directory>-->
                <!--<includes>-->
                    <!--<include>**/*.properties</include>-->
                    <!--<include>**/*.xml</include>-->
                <!--</includes>-->
                <!--<filtering>false</filtering>-->
            <!--</resource>-->
        </resources>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

完整配置文件application.properties如下:

spring.mvc.view.prefix=/WEB-INF/jsp/
spring.mvc.view.suffix=.jsp

server.port=8080
spring.jackson.serialization.FAIL_ON_EMPTY_BEANS=false
spring.main.allow-bean-definition-overriding=true

# Show or not log for each sql query
spring.jpa.database = MYSQL
spring.jpa.show-sql = true
spring.jpa.open-in-view=false

# database
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&useOldAliasMetadataBehavior=true&serverTimezone=UTC
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root

mybatis.mapper-locations = classpath:com/sgp/ss/mapper/*.xml

完整的代码目录如下:

mybatis配置与代码

mybatis主要的配置涉及以下几个地方:

1,pom文件配置

因为在代码目录中,*Mappee.xml文件放在了main/src/java目录下,所以需要在pom文件中配置不要过来/java目录下是xml文件,如果不添加这个配置,那么在target文件包下就没有xml文件,项目就会报Invalid bound statement (not found)的错误,而且一定要注意标签include内的内容,一定要写成**/*xml这个形式,其他形式都不要写

这部分配置可以参考BindingException:Invalid bound statement (not found)异常_bindingexception: invalid bound statement-CSDN博客

2,application.properties文件配置

配置xml文件的扫描位置,这个需要在配置文件application.properties中配置,建议写完整路径,这样不容易出错,一定要注意,路径中不能出现空格等特殊符号!

3,启动类注解

最后开启项目启动后对mapper文件进行扫描,需要在启动类上添加注解

@MapperScan(basePackages = "com.sgp.ss.dao")

4,相关代码

上述配置完成后,编写数据库表以及相应的dao,dto,entity,service,mapper文件,然后application.properties文件中配置数据库,把所有数据库的相关代码编写完成,比如测试登录验证用的用户信息表,具体如下:

#数据库建表语句 CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) DEFAULT NULL, `password` varchar(255) DEFAULT NULL, `permission` varchar(255) DEFAULT NULL, `role` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1

#entity对象

package com.sgp.ss.domain.entity;

/**
 * @author shanguangpu
 * @date 2023/2/22 17:33
 */
public class UserEntity {

    private Long id;

    private String username;

    private String password;

    private String permission;

    private String role;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

#dto对象

package com.sgp.ss.domain.dto.data;

/**
 * @author shanguangpu
 * @date 2023/2/22 17:32
 */
public class UserDto {

    private Long id;

    private String username;

    private String password;

    private String permission;

    private String role;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getPermission() {
        return permission;
    }

    public void setPermission(String permission) {
        this.permission = permission;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

#DAO代码

package com.sgp.ss.dao;

import com.sgp.ss.domain.dto.data.UserDto;
import com.sgp.ss.domain.entity.UserEntity;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author shanguangpu
 * @date 2023/2/22 17:34
 */
@Repository
public interface IUserMapper {

    public void truncate();

    /**
     * 新增对象
     *
     * @param
     * @return
     */
    public void insert(UserEntity userEntity);

    /**
     * 更新对象
     *
     * @param
     * @return
     */
    public void update(UserEntity userEntity);

    /**
     * 删除记录
     *
     * @param
     * @return
     */
    public void delete(Long id);

    /**
     * 根据主键获取对象
     *
     * @param id
     *            主键字段
     * @return
     */
    public UserEntity getUserEntityById(Long id);

    public UserEntity getUserEntityByName(String username);

    /**
     * 根据查询Bean获取对象集合,不带翻页
     *
     * @param queryBean
     * @return
     */
    public List<UserEntity> queryUserEntityList(UserDto queryBean);


}

#service接口文件

package com.sgp.ss.service;

import com.sgp.ss.domain.dto.data.UserDto;
import com.sgp.ss.domain.entity.UserEntity;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author shanguangpu
 * @date 2023/2/22 17:49
 */
@Component
public interface UserEntityService {

    public void truncate();

    /**
     * 新增对象
     *
     * @param
     * @return
     */
    public void insert(UserDto userDto);

    /**
     * 更新对象
     *
     * @param
     * @return
     */
    public void update(UserDto userDto);

    /**
     * 删除记录
     *
     * @param
     * @return
     */
    public void delete(Long id);

    /**
     * 根据主键获取对象
     *
     * @param id
     *            主键字段
     * @return
     */
    public UserEntity getUserEntityById(Long id);

    public UserEntity getUserEntityByName(String username);

    /**
     * 根据查询Bean获取对象集合,不带翻页
     *
     * @param queryBean
     * @return
     */
    public List<UserEntity> queryUserEntityList(UserDto queryBean);

}

#service实现类

package com.sgp.ss.service.impl;

import com.sgp.ss.dao.IUserMapper;
import com.sgp.ss.domain.dto.data.UserDto;
import com.sgp.ss.domain.entity.UserEntity;
import com.sgp.ss.service.UserEntityService;
//import com.sgp.ss.util.BeanUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * @author shanguangpu
 * @date 2023/2/22 17:50
 */
@Service
@Transactional
public class UserEntityServiceImpl implements UserEntityService {

    /** LOG */
    private static final Log LOG = LogFactory.getLog(UserEntityServiceImpl.class);

    @Autowired
    private IUserMapper userMapper;

    @Override
    public void truncate() {

    }

    @Override
    public void insert(UserDto userDto) {
        try {
            if (null != userDto) {
                UserEntity entity = new UserEntity();
//                BeanUtils.copyProperties(userDto, entity);
                entity.setId(userDto.getId());
                entity.setUsername(userDto.getUsername());
                entity.setPassword(userDto.getPassword());
                entity.setPermission(userDto.getPermission());
                entity.setRole(userDto.getRole());
                userMapper.insert(entity);
            } else {
                LOG.warn("UserEntityServiceImpl#insert failed, param is illegal.");
            }
        } catch (Exception e) {
            LOG.warn("UserEntityServiceImpl#insert failed, UserEntity has existed.");
            throw e;
        }
    }

    @Override
    public void update(UserDto userDto) {
        boolean resultFlag = false;
        try {
            if (null != userDto) {
                UserEntity entity = new UserEntity();
                entity.setId(userDto.getId());
                entity.setUsername(userDto.getUsername());
                entity.setPassword(userDto.getPassword());
                entity.setPermission(userDto.getPermission());
                entity.setRole(userDto.getRole());
                userMapper.update(entity);
            } else {
                LOG.warn("UserEntityServiceImpl#update failed, param is illegal.");
            }
        } catch (Exception e) {
            LOG.error("UserEntityServiceImpl#update has error.", e);
        }
    }

    @Override
    public void delete(Long id) {
        userMapper.delete(id);
    }

    @Override
    public UserEntity getUserEntityById(Long id) {
        UserEntity userEntity = null;
        try {
            if (null != id) {
                userEntity = userMapper.getUserEntityById(id);
            } else {
                LOG.warn("UserEntityServiceImpl#getUserEntityById failed, param is illegal.");
            }
        } catch (Exception e) {
            LOG.error("UserEntityServiceImpl#getUserEntityById has error.", e);
        }
        return userEntity;
    }

    public UserEntity getUserEntityByName(String username){
        UserEntity userEntity = null;
        try {
            if (null != username) {
                userEntity = userMapper.getUserEntityByName(username);
            } else {
                LOG.warn("UserEntityServiceImpl#getUserEntityByName failed, param is illegal.");
            }
        } catch (Exception e) {
            LOG.error("UserEntityServiceImpl#getUserEntityByName has error.", e);
        }
        return userEntity;
    }

    @Override
    public List<UserEntity> queryUserEntityList(UserDto queryBean) {
        List<UserEntity> userEntities = null;
        try {
            userEntities = userMapper.queryUserEntityList(queryBean);
        } catch (Exception e) {
            LOG.error("UserEntityServiceImpl#queryUserEntityList has error.", e);
        }
        return userEntities;
    }
}

#mapper.xml文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.sgp.ss.dao.IUserMapper">
    <resultMap id="UserEntityMap" type="com.sgp.ss.domain.entity.UserEntity">
        <result column="id" property="id" jdbcType="DECIMAL" />
		<result column="username" property="username" jdbcType="VARCHAR" />
        <result column="password" property="password" jdbcType="VARCHAR" />
        <result column="permission" property="permission" jdbcType="VARCHAR" />
        <result column="role" property="role" jdbcType="VARCHAR" />
    </resultMap>
    
    <sql id="UserEntityColumns">
    	id,username,password,permission,role
    </sql>
    
     <sql id="UserEntityUsedColumns">
    	username,password,permission,role
    </sql>
    <!-- 清空表 -->
    <delete id="truncate" parameterType="java.lang.Long">
        truncate table user
    </delete>

    <!-- 创建信息 -->
    <insert id="insert" parameterType="com.sgp.ss.domain.entity.UserEntity">
        INSERT INTO user(<include refid="UserEntityUsedColumns"/>)
        VALUES (#{username},#{password},#{permission},#{role})
	</insert>
	
    <!-- 修改信息 -->
    <update id="update" parameterType="com.sgp.ss.domain.entity.UserEntity">
        <if test="_parameter != null">
            <if test="id != null and id > 0">
                update user set
                <if test="id != null and id != ''">
                    id = #{id,jdbcType=DECIMAL},
                </if>
		        <if test="username != null and username != ''">
                    username = #{username,jdbcType=VARCHAR},
		        </if>
                <if test="password != null and password != ''">
                    password = #{password,jdbcType=VARCHAR},
                </if>
                <if test="permission != null and permission != ''">
                    permission = #{permission,jdbcType=VARCHAR},
                </if>
                <if test="role != null and role != ''">
                    role = #{role,jdbcType=VARCHAR},
                </if>
                	id = id
                where id = #{id}
            </if>
        </if>
    </update>

    <!-- 删除信息-逻辑删除 -->
    <delete id="delete" parameterType="java.lang.Long">
        delete from  user where id = #{id}
    </delete>

    <!-- 根据主键获取对象信息 -->
    <select id="getUserEntityById" resultMap="UserEntityMap" parameterType="java.lang.Long">
        <if test="_parameter != null">
            select <include refid="UserEntityColumns"/> from user
            where id = #{_parameter} and 1 = 1
        </if>
    </select>

    <!-- 根据名称获取对象信息 -->
    <select id="getUserEntityByName" resultMap="UserEntityMap" parameterType="java.lang.Long">
        <if test="_parameter != null">
            select <include refid="UserEntityColumns"/> from user
            where username = #{username} and 1 = 1
        </if>
    </select>

    <!-- 根据查询Bean获取数据集合,不带翻页 -->
    <select id="queryUserEntityList" resultMap="UserEntityMap"
            parameterType="com.sgp.ss.domain.dto.data.UserDto">
        select <include refid="UserEntityColumns"/> from user where <include refid="queryUserEntityListWhere"/>
    </select>

    <!-- 常用的查询Where条件 -->
    <sql id="queryUserEntityListWhere">
    	1 = 1
        <if test="id != null and id != ''">
            and id = #{id,jdbcType=DECIMAL}
        </if>
        <if test="username != null and username != ''">
            and username = #{username,jdbcType=VARCHAR}
        </if>
        <if test="password != null and password != ''">
            and password = #{password,jdbcType=VARCHAR}
        </if>
        <if test="permission != null and permission != ''">
            and permission = #{permission,jdbcType=VARCHAR}
        </if>
        <if test="role != null and role != ''">
            and role = #{role,jdbcType=VARCHAR}
        </if>
    </sql>
</mapper>

mybatis配置完成后,可以编写controller文件进行数据库测试

package com.sgp.ss.controller;

import com.sgp.ss.dao.IUserMapper;
import com.sgp.ss.domain.dto.data.UserDto;
import com.sgp.ss.domain.entity.UserEntity;
import com.sgp.ss.security.LoginUser;
import com.sgp.ss.util.JwtTokenUtil;
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 java.util.List;

/**
 * @author shanguangpu
 * @date 2023/12/12 14:59
 */
@RestController
public class index {

    @Autowired
    private IUserMapper userMapper;

    @GetMapping(value = "hello")
    public String request(){
        System.out.println("hello word");
        return "hello success";
    }

    @PostMapping(value = "query")
    public String selectInfo(){
        List<UserEntity> userEntities = userMapper.queryUserEntityList(new UserDto());
        return userEntities.toString();
    }

}

由于已经添加了spring security登录验证功能,所以代码中在没有配置spring security的前提下,数据库的测试会被登录验证拦截,所以可以在配置完成security后一并进行测试

spring security配置与代码

spring security的核心就是过滤器链,框架提供了十几种过滤器,其中比较主要的是认证过滤器和JWT过滤器,另外security对用户信息和用户服务功能进行了规范封装,即接口UserDetails和UserDetailsService,所以如果要自定义过滤器,除了要复写几个过滤器外,还要编写用户信息去实现规范用户接口UserDetails和UserDetailsService

1,UserDetails接口实现

首先编写用户登录信息对象实现UserDetails接口,这里为了区别用于数据库查询的用户类,新建Java类继承已有用户类,并实现UserDetails接口。代码如下

package com.sgp.ss.security;

import com.sgp.ss.domain.entity.UserEntity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

/**
 * @author shanguangpu
 * @date 2023/11/30 15:58
 */
public class LoginUser extends UserEntity implements UserDetails {

    Collection<? extends GrantedAuthority> authorities;

    private Set<String> permissions = new HashSet<>();

    public LoginUser(UserEntity userEntity){
        if (null != userEntity){
            this.setUsername(userEntity.getUsername());
            this.setPassword(userEntity.getPassword());
        }
    }

    public LoginUser(UserEntity userEntity, Collection<? extends GrantedAuthority> authorities){
        this.setId(userEntity.getId());
        this.setUsername(userEntity.getUsername());
        this.setPassword(userEntity.getPassword());
        permissions.add(userEntity.getPermission());
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    /**
     * 账户是否过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 账户是否被锁定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 证书是否过期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 账户是否有效
     * @return
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}
2,UserDetailsService接口实现

然后编写用户服务实现类,实现UserDetailsService接口,这里主要复写loadUserByUsername(String username)这个方法,这个方法就是去数据库查询用户信息,并返回UserDetails对象,代码如下:

package com.sgp.ss.security;

import com.sgp.ss.dao.IUserMapper;
import com.sgp.ss.domain.entity.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

/**
 * @author shanguangpu
 * @date 2023/11/30 15:50
 */
@Component
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    IUserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        try {
            UserEntity userEntityByName = userMapper.getUserEntityByName(username);
            if (userEntityByName == null){
                throw new UsernameNotFoundException("用户"+username+"不存在");
            }
            return new LoginUser(userEntityByName);

        } catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }
}
3,过滤器自定义-JWT过滤器

一般登录认证的流程中都会涉及到token,它可以理解为一个标识令牌,用户在第一次登录的时候系统会返回一个令牌token给用户,该用户在下次登录的时候,就不需要携带用户名密码进行请求了,只需要把token带上,系统可以从token中解析出用户信息,对用户进行验证;如果token在有效期内,则系统就会对请求放行,如果过期,则需要重新携带用户名密码进行登录然后再发送请求,并生成新的token返回给用户,所以我们的JWT过滤器主要就是对token进行验证,如果token有效且不为空,则通过token可以获取到用户信息,如果用户信息验证正确,就调用security框架的UsernamePasswordAuthenticationToken方法,生成security框架用户认证信息token,并存放在框架上下文SecurityContextHolder中,以便在下次登录中进行校验,最后过滤器放行,进行下一个过滤器,如果token为空,则直接放行过滤器,进入下一个过滤器生成token返回给用户,代码如下:

package com.sgp.ss.security;

import com.sgp.ss.domain.entity.UserEntity;
import com.sgp.ss.service.UserEntityService;
import com.sgp.ss.util.JwtTokenUtil;
import com.sgp.ss.vo.JwtProperties;
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author shanguangpu
 * @date 2023/12/8 15:29
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private UserEntityService userEntityService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

//    @Autowired
//    private JwtProperties jwtProperties;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        //获取token信息
        final String authorization = request.getHeader("token");

        String name = null;
        String authToken = null;

        if (!StringUtils.isEmpty(authorization)) {
            authToken = authorization.replace("Authorization", "");
            try {
                name = jwtTokenUtil.getUsernameFromToken(authToken);
            } catch (ExpiredJwtException e){
                e.printStackTrace();
            }
        }
        if (name != null && SecurityContextHolder.getContext().getAuthentication() == null){
//            if (jwtTokenUtil.isTokenValid(name, authToken)){
                UserEntity userEntityByName = userEntityService.getUserEntityByName(name);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userEntityByName, null, jwtTokenUtil.getAuthorityFromToken(authToken));
                SecurityContextHolder.getContext().setAuthentication(authentication);
//            }
        }
        filterChain.doFilter(request, response);

    }

    public JwtAuthenticationTokenFilter(){
        super();
    }
}
4,过滤器自定义-Authentication认证过滤器

第二个需要复写的过滤器是认证过滤器,这个过滤器的功能就是从输入流中获取用户信息,生成security的token令牌,交给认证管理器AuthenticationManager,

对于登录成功的用户,将生成框架定义的UsernamePasswordAuthenticationToken保存到上下文中,然后调用复写的成功验证方法successfulAuthentication生成token返回给用户,同时也可以复写验证失败的方法,自定义验证失败后的返回信息,代码如下

package com.sgp.ss.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sgp.ss.util.JwtTokenUtil;
import com.sgp.ss.vo.AuthRequestVO;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author shanguangpu
 * @date 2023/12/11 14:57
 */
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

//    @Autowired
    private AuthenticationManager authenticationManager;

    private JwtTokenUtil jwtTokenUtil;

    /**
     * 这个方法的目的仅仅是为了修改一下默认的登录接口,其他参数均是为了通过WebSecurityConfig配置类实现在本方法中自动注入前边private的类
     * @param tokenUtil
     * @param authenticationManager
     * @param url
     */
    public CustomAuthenticationFilter(JwtTokenUtil tokenUtil, AuthenticationManager authenticationManager, String url) {
        this.jwtTokenUtil = tokenUtil;//传入这个参数的目的,是为了在WebSecurityConfig配置类中将该类注入
        this.authenticationManager = authenticationManager;//传入这个参数的目的,是为了在WebSecurityConfig配置类中将该类注入
        super.setFilterProcessesUrl(url);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        String username = "";
        String password = "";

        //从输入流中获取登录信息
        try {

            AuthRequestVO requestVO = new ObjectMapper().readValue(request.getInputStream(), AuthRequestVO.class);
            username = requestVO.getUsername();
            password = requestVO.getPassword();
            if (StringUtils.isBlank(password)){
                throw new BadCredentialsException("Password cannot be null");
            }

        } catch (IOException e){
            e.printStackTrace();
        }

        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        this.setDetails(request, authRequest);
        return authenticationManager.authenticate(authRequest);
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain, Authentication authResult) throws IOException, ServletException {
        LoginUser userDetails = (LoginUser) authResult.getPrincipal();
        String token = jwtTokenUtil.generateToken(userDetails);
        response.setHeader("token", token);

    }

    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {

        response.getWriter().write("authentication failed");
    }


}
5,WebSecurityConfig配置类

最后就是spring security的配置类,主要配置用户的密码存储编码,http请求的过滤条件,过滤器的顺序等,代码如下:

package com.sgp.ss.config;

import com.sgp.ss.security.*;
import com.sgp.ss.util.JwtTokenUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @author shanguangpu
 * @date 2023/11/30 15:25
 */
@Configuration
@EnableWebSecurity
//@EnableGlobalMethodSecurity(prePostEnabled = true)
//@EnableConfigurationProperties(JwtProperties.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception{
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

//    @Bean
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()//关闭csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//不通过session获取securityContext
                .and()
                .authorizeRequests()
//                .antMatchers("/").permitAll()
                .antMatchers("/hello").permitAll()
//                .antMatchers(HttpMethod.POST).permitAll()
//                .antMatchers("/user/login").anonymous()//对于登录接口,允许匿名访问
                .anyRequest().authenticated();//所有请求都要经过鉴权认证
        http.exceptionHandling()
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                .accessDeniedHandler(new CustomAccessDeniedHandler());

        //把token校验过滤器添加到过滤器链中
        http.addFilter(new CustomAuthenticationFilter(jwtTokenUtil, authenticationManager(), "/user/login"));
        http.addFilterBefore(jwtAuthenticationTokenFilter, CustomAuthenticationFilter.class);

        http.cors();

    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

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

    public static void main(String[] args) {
        String encode = passwordEncoder().encode("4321");
        System.out.println(encode);
    }

}

上述配置中,还有两个认证失败和权限失败两个处理异常类,代码如下:

package com.sgp.ss.security;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author shanguangpu
 * @date 2023/12/8 16:00
 */
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        Throwable t = (Throwable)request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
        response.setContentType("application/json;charset=utf-8");
        response.getWriter().write("{\"code\":\"403\",\"message\":\"请重新登录\"}");
    }
}
package com.sgp.ss.security;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author shanguangpu
 * @date 2023/12/8 15:59
 */
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write("{\"code\":\"403\",\"message\":\"权限不足\"}");
        out.flush();
        out.close();
    }
}
6,JWT工具类
package com.sgp.ss.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.sgp.ss.security.LoginUser;
import com.sgp.ss.vo.JwtProperties;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.DefaultClock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author shanguangpu
 * @date 2023/2/22 10:42
 */
@Component
public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = -1264536648286114018L;

    private Clock clock = DefaultClock.INSTANCE;

    private Map<String, String> tokenMap = new ConcurrentHashMap<>(32);

    public String generateToken(LoginUser loginUser){
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", loginUser.getUsername());
        claims.put("authority", loginUser.getAuthorities());
        String token = generateToken(claims, loginUser.getId() + "");
        if (! StringUtils.isEmpty(token)){

        }
        return token;
    }

    /**
     * 生成token
     * @param claims
     * @param subject
     * @return
     */
    private String generateToken(Map<String, Object> claims, String subject) {
        final Date createdDate = clock.now();
        final Date expirationDate = generateExpirationDate(createdDate);
        return Jwts.builder()
                // 自定义属性
                .setClaims(claims)
                .setSubject(subject)
                // 创建时间
                .setIssuedAt(createdDate)
                // 过期时间
                .setExpiration(expirationDate)
                // 签名算法及秘钥
                .signWith(SignatureAlgorithm.HS512, "sgpsgp")
                .compact();
    }

    private Date generateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + 21600 * 1000);
    }

    // 从得到的令牌里面获取用户ID
    public Integer getUserIdFromToken(String token) {
        Integer id = null;
        try {
            final Claims claims = getClaimsFromToken(token);
            id = Integer.parseInt(claims.getSubject());
            return id;
        } catch (Exception e) {
        }
        return id;
    }

    // 从得到的令牌里面获取用户名
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = (String) claims.get("username");
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    // 从得到的令牌里面获取权限
    public List<GrantedAuthority> getAuthorityFromToken(String token) {
        List<GrantedAuthority> list = null;
        try {
            final Claims claims = getClaimsFromToken(token);
            list = (List<GrantedAuthority>)claims.get("authority");
            return list;
        } catch (Exception e) {
        }
        return list;
    }

    // 从得到的令牌里面获取创建时间
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            created = null;
        }
        return created;
    }
    // 从得到的令牌里面获取过期时间
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey("sgpsgp")
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }


    /**
     * 检查token是否过期
     * @param token
     * @return
     */
    public Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        boolean flag = expiration.before(new Date());
        if(flag) {
            String username = getUsernameFromToken(token);
            if(username != null) {
                tokenMap.remove(username);
            }
        }
        return flag;
    }

    public Boolean isTokenValid(String username, String token) {
        if(tokenMap.get(username) != null && tokenMap.get(username).equals(token.trim())) {
            return !isTokenExpired(token);
        }
        return false;
    }

    public Boolean isTokenValid(int id, String token) {
        if (tokenMap.get(id) != null && tokenMap.get(id).equals(token.trim())) {
            return !isTokenExpired(token);
        }
        return false;
    }

    public void deleteToken(String token) {
        String username = getUsernameFromToken(token);
        if(username != null) {
            tokenMap.remove(username);
        }
    }

    public static void main(String[] args) {
        HashMap<String, Object> map = new HashMap<>();

        Calendar calendar = Calendar.getInstance();

        calendar.add(Calendar.SECOND, 60);

        String token = JWT.create()
                .withHeader(map)
                .withClaim("userName", "aaa")
                .withClaim("password", "1234")
                .withExpiresAt(calendar.getTime())
                .sign(Algorithm.HMAC256("abcd"));

        System.out.println(token);

        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("abcd")).build();
        DecodedJWT verify = jwtVerifier.verify(token);
        System.out.println(verify.getClaim("userName").asString());
        System.out.println(verify.getClaim("password").asString());
    }

}

postman测试

所有代码编写完成后,可以通过postman进行测试

1,不走登录验证的接口测试

首先验证配置类WebSecurityConfig中放行的/hello地址,这个地址访问的时候不会经过框架验证,直接会得到相应

2,登陆页面返回token测试

然后验证登录页面,使用用户名密码首次登录,会返回token,这个用户名密码需要提前在数据库里创建,为了安全起见,用户密码采用加密的密文存储,加密算法就是代码中配置的BCryptPasswordEncoder()方法,这个可以通过一个简单的Java函数得到,具体如下:

public static PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

public static void main(String[] args) {
    String encode = passwordEncoder().encode("4321");
    System.out.println(encode);
}

在数据库里存的样式就是下图的样子

在使用postman进行测似的时候,请求体里的用户密码使用明文即可,因为我们在配置类WebSecurityConfig中配置了默认登录地址为/user/login,所以访问这个地址就会返回token,这里注意,登录需要使用POST请求

因为我们没有编写登录成功后的页面,所以响应的body是空的,但是响应头里会看到系统生成的token返回给了用户

3,携带token请求测试

下一步我们验证数据库的查询接口,也就是index.java中的第二个接口,同时也验证我们的mybatis是否集成成功,这时候需要注意的是,我们需要携带着用户生成的token去访问,否则就会验证失败

首先验证不携带token的情况,,请求头里的token没有打勾,可以看到会直接调用我们之前复写的认证不成功的返回信息

然后我们给请求头里的token打勾,再次发送,可以看到得到了正确的响应内容

至此,所有功能测试完毕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1313918.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

linux内核使用ppm图片开机

什么是ppm图片 PPM&#xff08;Portable Pixmap&#xff09;是一种用于存储图像的文件格式。PPM图像文件以二进制或ASCII文本形式存储&#xff0c;并且是一种简单的、可移植的图像格式。PPM格式最初由Jef Poskanzer于1986年创建&#xff0c;并经过了多次扩展和修改。 PPM图像…

金蝶云星空协同开发环境应用内执行单据类型脚本

文章目录 金蝶云星空协同开发环境应用内执行单据类型脚本业务界面查询单据类型表数据导出数据执行数据库脚本单据类型xml检验是否执行成功检查数据库检查业务数据 金蝶云星空协同开发环境应用内执行单据类型脚本 业务界面 查询单据类型表数据 先使用类型中文在单据类型多语言…

Windows10之wsl-Linux子系统安装JDK、Maven环境

Windows10之wsl-Linux子系统安装JDK、Maven环境 文章目录 1.环境2.安装2.1安装JDK2.1安装maven 3.配置setting.xml4.下载编译项目插件5.总结 1.环境 首先需要在windwos10上安装wsl的Linux子系统&#xff0c;我选择的是CentOs的操作系统的镜像(之前的文章中采用的是docker拉取一…

嵌入式开发人员需要具备哪些能力?

大家好&#xff0c;今天给大家介绍嵌入式开发人员需要具备哪些能力&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 嵌入式开发人员需要具备以下能力&#xff1a; 熟练掌握C/C语…

腾讯云Linux云服务器禁Ping设置

腾讯云Linux服务器默认是允许ping包的&#xff0c;但是在一些情况下为了安全考虑起见&#xff0c;我们都会把服务器设置为禁ping的模式。 1、首先检查Linux服务器当前是否禁ping 执行命令&#xff1a; cat /proc/sys/net/ipv4/icmp_echo_ignore_all 备注&#xff1a; 0----代…

Android画布Canvas绘图scale translate,Kotlin

Android画布Canvas绘图scale & translate&#xff0c;Kotlin <?xml version"1.0" encoding"utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"…

maven jar sort

1&#xff09;往常项目结构lib包排序 2&#xff09;maven的默认是没有排序的

2019年第八届数学建模国际赛小美赛A题放射性产生的热量解题全过程文档及程序

2019年第八届数学建模国际赛小美赛 A题 放射性产生的热量 原题再现&#xff1a; 假设我们把一块半衰期很长的放射性物质做成一个特定的形状。在这种材料中&#xff0c;原子核在衰变时会以随机的方向释放质子。我们假设携带质子的能量是一个常数。质子在穿过致密物质时&#x…

【STM32】STM32学习笔记-OLED调试工具(09)

00. 目录 文章目录 00. 目录01. STM32调试方式02. OLED简介03. 0.96寸OLED模块04. 0.96寸OLED驱动IC05. 0.96寸OLED原理图06. 硬件电路07. OLED驱动函数08. 附录 01. STM32调试方式 串口调试&#xff1a;通过串口通信&#xff0c;将调试信息发送到电脑端&#xff0c;电脑使用串…

Tekton 克隆 git 仓库

Tekton 克隆 git仓库 介绍如何使用 Tektonhub 官方 git-clone task 克隆 github 上的源码到本地。 git-clone task yaml文件下载地址&#xff1a;https://hub.tekton.dev/tekton/task/git-clone 查看git-clone task yaml内容&#xff1a; 点击Install&#xff0c;选择一种…

微服务组件Sentinel的学习(3)

Sentinel 隔离和降级Feign整合Sentinel线程隔离熔断降级熔断策略 授权规则&#xff1a;自定义异常 隔离和降级 虽然限流可以尽量避免因高并发而引起的服务故障&#xff0c;但服务还会因为其它原因而故障。而要将这些故障控制在一定范用避免雪崩&#xff0c;就要靠线程隔离(舱壁…

jmeter,读取CSV文件数据的循环控制

1、构造csv数据 保存文件时需要注意文件的编码格式 id,name,limit,status,address,start_time 100,小米100,1000,1,某某会展中心101,2023/8/20 14:20 101,小米101,1001,1,某某会展中心102,2023/8/21 14:20 2、在线程组下添加【CSV数据文件设置】元件 3、CSV文件数据的循环控…

IDEA debug窗口左边工具栏隐藏与显示

今天在debug排查代码的时候一不小心点到哪里&#xff0c;结果变成这样 我们可以这样恢复&#xff0c;右键Debug 点击show Toolbar

本地项目添加到gitlab命令操作

gitlab上面创建一个跟项目名同名的文件夹 创建文件夹&#xff0c;填写信息 添加readme文档&#xff0c;先保存下创建的文件夹 回到项目&#xff0c;复制项目的git 地址 然后进入到本地项目的文件夹&#xff0c;如d:/workspace/spring-demo&#xff0c;右键打开git bash弹框 命令…

关于“Python”的核心知识点整理大全22

目录 ​编辑 9.4.2 在一个模块中存储多个类 虽然同一个模块中的类之间应存在某种相关性&#xff0c;但可根据需要在一个模块中存储任意数量的 类。类Battery和ElectricCar都可帮助模拟汽车&#xff0c;因此下面将它们都加入模块car.py中&#xff1a; car.py my_electric_car…

云原生之深入解析Kubernetes本地持久化存储方案OpenEBS LocalPV的最佳实践

一、K8s 本地存储 K8s 支持多达 20 种类型的持久化存储&#xff0c;如常见的 CephFS 、Glusterfs 等&#xff0c;不过这些大都是分布式存储&#xff0c;随着社区的发展&#xff0c;越来越多的用户期望将 K8s 集群中工作节点上挂载的数据盘利用起来&#xff0c;于是就有了 loca…

智能物联网(IoT)VS AI物联网(AIoT)

#IoT# #AIoT# 智能物联网&#xff08;IoT&#xff09;和AI物联网&#xff08;AIoT&#xff09;区别 概念&#xff1a; 物联网&#xff08;IoT&#xff09;&#xff1a;即“万物相连的互联网”&#xff0c;是在互联网基础上延伸和扩展的网络&#xff0c;将各种信息传感设备与网…

最新AI绘画Midjourney绘画提示词Prompt教程

一、Midjourney绘画工具 SparkAi【无需魔法使用】&#xff1a; sparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的…

IDEA设置查看JDK源码

问题 我们在查看JDK源码时&#xff0c;可能会遇到这种情况&#xff0c;步入底层查看JDK源码时&#xff0c;出现一堆var变量&#xff0c;可读性非常之差&#xff0c;例如笔者最近想看到nio包下的SocketChannelImpl的write方法&#xff0c;结果看到这样一番景象&#xff1a; pu…

Java系列-HashMap构造方法

1.无参 只初始化了loadFactor public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {final float loadFactor;public HashMap() {this.loadFactor DEFAULT_LOAD_FACTOR; // all other fields defaulted} }2…