springboot实现注册的加密与登录的解密

4,323 阅读7分钟
  • 前情提要:本demo是基于springboot+mybatis-plus实现加密,加密为主,全局异常处理,日志处理为辅,而登录密码加密是每个项目中必须要的,密码不可能明文存入数据,这样没有安全性。

涉及的功能,全局异常处理,日志处理,mybatis-plus实现与数据库的交互,密码加密,restful风格

涉及的工具:IDEA,postman,sqlyog(navicat)

1. 首先我们直接看效果吧,如果你不满意,也就没必要看了

注册

登录

数据库中

如果这正是你想要的效果呢,那你可以继续看下面的内容了

2. 首先,我们看下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.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.jgsu</groupId>
    <artifactId>springboot_rsa_encryption</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_encryption</name>
    <description>Demo project for Spring Boot</description>

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

    <dependencies>
        <!-- WEB依赖 -->
        <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>

        <!-- 热部署依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- mysql的依赖,用于连接数据库-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!-- lombok依赖,简化set/get方法 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>

        <!--druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>

        <!--spring-security实现密码加密-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.1.6.RELEASE</version>
        </dependency>

        <!--fastjsoon-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.51</version>
        </dependency>
    </dependencies>

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

</project>

3. 创建启动类SpringbootEncryptionApplication

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@SpringBootApplication
@MapperScan("com.jgsu.mapper")
public class SpringbootEncryptionApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootEncryptionApplication.class, args);
    }


    /**
     * 将加密工具类加入IOC容器中,便于加密
     * */
    @Bean
    public BCryptPasswordEncoder encoder() {
        return new BCryptPasswordEncoder();
    }
}

4.实体类

这里只有用户名和密码(其他数据自己可扩展)

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User {
    @TableId(value = "id",type = IdType.AUTO)
    private int id;
    private String username;
    private String password;
}

5. service层(业务层)

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jgsu.entity.User;
import com.jgsu.exception.DataAddException;
import com.jgsu.exception.DataMatchException;
import com.jgsu.mapper.UserMapper;
import com.jgsu.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;
    // 数据加密,在启动类中已经注入进IOC容器中
    @Autowired
    private BCryptPasswordEncoder encoder;

    @Override
    public User userLogin(String username,String password) {
        // mybatis-plus的条件构造器,这里实现根据username查询
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper.eq("username", username);
        User userLogin = userMapper.selectOne(wrapper);
 
        /**
         *  encoder.matches(password, userLogin.getPassword(),实现输入的密码与数据库中的密码进 
         *  行匹配,如果匹配成功则返回匹配的数据给controller层,如果失败则抛异常。
         *  为什么没盐,没有解密了?因为这个已经被CryptPasswordEncoder封装好了,
         *  在encoder.matches()方进行解密匹配完全帮你封装好了,所以不必考虑,
         *  只需要将前端传入的密码与数据库中加密后的密码进行匹配就行。
         * **/
        if (userLogin != null && encoder.matches(password, userLogin.getPassword())) {
            log.info("用户{},登录成功",username);
            return userLogin;
        } else {
            log.error("用户名或密码错误");
            throw new DataMatchException("405", "用户名或密码错误");
        }
    }

    @Override
    public User userRegister(String username, String password) {
        User user = new User();
        user.setId(user.getId());
        user.setUsername(username);
        user.setPassword(encoder.encode(password));
       int i = userMapper.insert(user);
       if (i == 1){
           log.info("用户{}注册成功",username);
           return user;
       }else {
           log.error("服务器发生异常,注册失败");
           throw new DataAddException("403","注册失败");
       }
    }
}

6. mapper层

如果不了解,可以建议去看看mybatis-plus官方文档

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.jgsu.entity.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper extends BaseMapper<User> {
}

7.controller层

import com.jgsu.entity.User;
import com.jgsu.service.UserService;
import com.jgsu.utils.CommonResult;
import com.jgsu.utils.ResultCode;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserService userService;

    // 注册,基于restful风格
    @GetMapping("/register/{username}/{password}")
    public CommonResult register(@PathVariable("username") String username,@PathVariable("password") String password){
       User user = userService.userRegister(username, password);
        if (user != null){
            return CommonResult.success(ResultCode.SUCCESS);
        }else {
            return CommonResult.failed(ResultCode.FAILED);
        }
    }
    // 登录,基于restful风格
    @GetMapping("/login/{username}/{password}")
    public CommonResult login(@PathVariable("username") String username,@PathVariable("password") String password) {
        User userLogin = userService.userLogin(username,password);
        if (userLogin != null) {
            return CommonResult.success(ResultCode.SUCCESS);
        } else {
            return CommonResult.failed(ResultCode.USERNAME_OR_PASSWORD_ERROR);
        }
    }
}

8.配置类(返回json数据的类)

  • 封装错误码的接口

    public interface IErrorCode {
        long getState();
    
        String getMessage();
    }
    
  • 枚举了一些常用API操作码

    public enum ResultCode implements IErrorCode {
        /**
         * 成功
         */
        SUCCESS(200, "ok"),
        /**
         * 失败
         */
        FAILED(500, "server error"),
    
        /**
         * 验证过期
         */
        VALIDATE_FAILED(404, "undefined"),
        /**
         * 未登录
         */
        UNAUTHORIZED(401, "未登录"),
        /**
         * 用户名或密码错误
         */
        USERNAME_OR_PASSWORD_ERROR(405, "用户名或密码错误"),
        /**
         * 数据查询错误
         */
        DATA_Not_Exist_ERROR(603, "数据不存在"),
        /**
         * 数据添加出现问题
         */
        DATA_ADD_ERROR(604, "数据添加异常"),
    
        /**
         * 文件
         */
        FILE_ERROR(605, "上传文件出现错误"),
        /**
         * 数据查询出现问题
         */
        IMAGE_ERROR(606, "图片处理出现错误"),
        /**
         * 权限不够
         */
        FORBIDDEN(403, "forbidden");
    
    
        private long state;
        private String stateInfo;
    
         ResultCode(long state, String stateInfo) {
            this.state = state;
            this.stateInfo = stateInfo;
        }
    
        @Override
        public long getState() {
            return state;
        }
    
        @Override
        public String getMessage() {
            return stateInfo;
        }
    }
    
  • 通用返回对象

    public class CommonResult<T> {
        private long state;
        private String stateInfo;
        private T data;
    
        public CommonResult() {
        }
    
        public CommonResult(long state, String stateInfo, T data) {
            this.state = state;
            this.stateInfo = stateInfo;
            this.data = data;
        }
        public CommonResult(long state, String stateInfo) {
                this.state = state;
                this.stateInfo = stateInfo;
            }
    
        /**
         * 成功返回结果
         *
         * @param data 获取的数据
         */
        public static <T> CommonResult<T> success(T data) {
            return new CommonResult<T>(ResultCode.SUCCESS.getState(), ResultCode.SUCCESS.getMessage(), data);
        }
    
        /**
         * 成功返回结果
         *  @param data 获取的数据
         * @param  message 提示信息
         * @return
         */
        public static <T> CommonResult<T> success(T data, String message) {
            return new CommonResult<T>(ResultCode.SUCCESS.getState(), message, data);
        }
    
        /**
         * 失败返回结果
         * @param errorCode 错误码
         */
        public static <T> CommonResult<T> failed(IErrorCode errorCode) {
            return new CommonResult<T>(errorCode.getState(), errorCode.getMessage(), null);
        }
    
        /**
         * 失败返回结果
         * @param message 提示信息
         */
        public static <T> CommonResult<T> failed(String message) {
            return new CommonResult<T>(ResultCode.FAILED.getState(), message, null);
        }
    
        /**
         * 失败返回结果
         * @param code
         * @param message
         */
        public static <T> CommonResult<T> failed(int code, String message) {
            return failed(ResultCode.FAILED);
        }
    
        /**
         * 参数验证失败返回结果
         */
        public static <T> CommonResult<T> validateFailed() {
            return failed(ResultCode.VALIDATE_FAILED);
        }
    
        /**
         * 参数验证失败返回结果
         * @param message 提示信息
         */
        public static <T> CommonResult<T> validateFailed(String message) {
            return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getState(), message, null);
        }
    
        /**
         * 未登录返回结果
         */
        public static <T> CommonResult<T> unauthorized(T data) {
            return new CommonResult<T>(ResultCode.UNAUTHORIZED.getState(), ResultCode.UNAUTHORIZED.getMessage(), data);
        }
    
        /**
         * 未授权返回结果
         */
        public static <T> CommonResult<T> forbidden(T data) {
            return new CommonResult<T>(ResultCode.FORBIDDEN.getState(), ResultCode.FORBIDDEN.getMessage(), data);
        }
    
        public long getState() {
            return state;
        }
    
        public void setState(long state) {
            this.state = state;
        }
    
        public String getStateInfo() {
            return stateInfo;
        }
    
        public void setStateInfo(String stateInfo) {
            this.stateInfo = stateInfo;
        }
    
        public T getData() {
            return data;
        }
    
        public void setData(T data) {
            this.data = data;
        }
    }
    

9. 异常类

  • 全局异常拦截类

    @Slf4j
    @ControllerAdvice
    @ResponseBody
    public class GlobalExceptionHander {
    
        @ExceptionHandler(value = Exception.class)
        public CommonResult handlerException(Exception e){
            if (e instanceof DataAddException){
                log.error("【全局异常拦截】DataAddException: 请求方法 {}, 请求路径 {}",((DataAddException)e).getCode(),((DataAddException)e).getMessage());
                return CommonResult.failed(ResultCode.DATA_ADD_ERROR);
            }else if (e instanceof DataMatchException){
                log.error("【全局异常拦截】DataMatchException: 请求方法 {}, 请求路径 {}",((DataMatchException)e).getCode(),((DataMatchException)e).getMessage());
                return CommonResult.failed(ResultCode.USERNAME_OR_PASSWORD_ERROR);
            } else {
                log.error("服务器内部出错{}",e);
                return CommonResult.failed(ResultCode.FAILED);
            }
        }
    }
    
  • 自定义数据添加异常类

    public class DataAddException extends RuntimeException {
        private String code;
        private String message;
        public DataAddException(String code, String message) {
            this.code = code;
            this.message = message;
        }
    
        public String getCode() {
            return code;
        }
    
        public void setCode(String code) {
            this.code = code;
        }
    
        @Override
        public String getMessage() {
            return message;
        }
    
        public void setMessage(String message) {
            this.message = message;
        }
    }
    
    
  • 自定义数据匹配异常类

    public class DataMatchException extends RuntimeException {
           private String code;
           private String message;
           public DataMatchException(String code, String message) {
               this.code = code;
               this.message = message;
           }
    
           public String getCode() {
               return code;
           }
    
           public void setCode(String code) {
               this.code = code;
           }
    
           @Override
           public String getMessage() {
               return message;
           }
    
           public void setMessage(String message) {
               this.message = message;
           }
    }
    

    以上是实现该功能的所有类,当然进行密码加密处理的类只涉及service层以及启动类,其他类为基本类,如果你只想了解怎么进行加密处理存入数据库以及怎么登陆就看看service层就行。