加密算法

加密算法是⼀种将明⽂转换为密⽂的过程,以保护数据的安全性和机密性

作用

安全性:

存储明⽂密码是⾮常不安全的。如果数据库被⾮法访问或泄露,攻击者可以直接获取所有
⽤⼾的密码。
减少内部⻛险:
即使企业内有不诚实的员⼯或管理员,他们也⽆法轻易获取其他管理员的密码,因为密码是加密存储的
提升⽤⼾信任度
密码加密有助于提升⽤⼾对⽹站或应⽤程序的信任感,使其更愿意使⽤加密保护的⽹站。

常⻅的加密算法

可逆算法:⼀种可以将加密后的密⽂还原为原始明⽂的算法。
不可逆算法:⼀种⽆法将加密后的密⽂还原为原始明⽂的算法

BCrypt

Bcrypt是⼀种哈希加密算法,被⼴泛应⽤于存储密码和进⾏⾝份验证。
Bcrypt算法在计算时会先⽣成⼀个随机的盐值与⽤⼾密
码⼀起参与计算最终得到⼀个加密后的字符串。由于⽣成的盐值是随机的,所以即使每次使⽤相同的 密码得到结果也是不同的。这样可以有效的防⽌攻击者使⽤⼀些⼿段破解⽤⼾密码。

使用
提供加密算法⼯具类
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
 * 加密算法工具类
 */
public class BCryptUtils {
    /**
     * 生成加密后密文
     *
     * @param password 密码
     * @return 加密字符串
     */
    public static String encryptPassword(String password) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.encode(password);
    }

    /**
     * 判断密码是否相同
     *
     * @param rawPassword    真实密码
     * @param encodedPassword 加密后密文
     * @return 结果
     */
    public static boolean matchesPassword(String rawPassword, String encodedPassword) {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }
}

具体服务中应用

1.先将我们进行加密后的密码存入数据库

2.在服务中需要进行比较的地方,使用matchPassword方法

  if(BCryptUtils.matchesPassword(password,sysUser.getPassword())){
          loginResult.setCode(ResultCode.SUCCESS.getCode());
        loginResult.setMsg(ResultCode.SUCCESS.getMsg());
          return loginResult;
   }

代码优化

之前写的代码太过于冗余,样式相同的代码需要重复书写,这样以后有需求改动的时候,需要修改的地方过多,这里进行了代码的优化

package com.bite.common.core.domain;

import com.bite.common.core.enums.ResultCode;
import lombok.Data;

@Data
public class R <T>{
    private T data;
    private String msg;
    private int code;
    public static <T> R<T> ok() {
        return assembleResult(null, ResultCode.SUCCESS);
    }

    public static <T> R<T> ok(T data) {
        return assembleResult(data, ResultCode.SUCCESS);
    }

    public static <T> R<T> fail() {
        return assembleResult(null, ResultCode.FAILED);
    }

    public static <T> R<T> fail(int code, String msg) {
        return assembleResult(code, msg, null);
    }
    public static <T> R<T> fail(ResultCode resultCode) {
        return assembleResult(null, resultCode);
    }

    private static <T> R<T> assembleResult(T data, ResultCode resultCode) {
        R<T> r = new R<>();
        r.setCode(resultCode.getCode());
        r.setData(data);
        r.setMsg(resultCode.getMsg());
        return r;
    }
    private static <T> R<T> assembleResult(int code, String msg, T data) {
        R<T> r = new R<>();
        r.setCode(code);
        r.setData(data);
        r.setMsg(msg);
        return r;
    }
}

@Service
public class SysUserServiceImp implements ISysUserService{
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public R<Void> login(String userAccount, String password) {
        LambdaQueryWrapper<SysUser> queryWrapper=new LambdaQueryWrapper<>();
       SysUser sysUser=  sysUserMapper.selectOne(queryWrapper.select(SysUser::getPassword).eq(SysUser::getUserAccount,userAccount));
       if(sysUser==null){
           return R.fail(ResultCode.FAILED_USER_NOT_EXISTS);
       }
       if(BCryptUtils.matchesPassword(password,sysUser.getPassword())){

           return R.ok();
       }
        return  R.fail(ResultCode.FAILED_LOGIN);
    }
}

全局异常处理

使用全局异常处理的原因

  • 统一管理异常,避免程序崩溃

当程序运行中出现未捕获的异常(如空指针、文件读取失败等),若没有全局处理机制,程序会直接崩溃并抛出错误信息。全局异常处理可捕获这些异常,避免系统突然终止。

  • 提升用户体验,隐藏技术细节
  • 集中记录日志,便于问题追踪
  • 减少代码冗余,提高开发效率

1.在oj-common下,创建oj-common-security⼦⼯程

2.引入依赖

 <dependencies>
        <dependency>
            <groupId>com.bite</groupId>
            <artifactId>oj-common-core</artifactId>
            <version>${oj-common-core.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

3.创建GlobalExceptionHandler类

package com.bite.common.security;

import com.bite.common.core.domain.R;
import com.bite.common.core.enums.ResultCode;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice

@Slf4j
public class GlobalExceptionHandler {
    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<?> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return R.fail(ResultCode.ERROR);
    }
    /**
     * 拦截运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public R<?> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生运行时异常.", requestURI, e);
        return R.fail(ResultCode.ERROR);
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public R<?> handleException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生异常.", requestURI, e);
        return R.fail(ResultCode.ERROR);
    }
}
@RestControllerAdvice:当抛出异常时, @RestControllerAdvice 标注的类将被⾃动调⽤,
并根据异常类型和处理程序的注解来决定如何处理该异常。这使得开发者可以在整个应⽤程序范围内统⼀处理异常。
@ExceptionHandler: @ExceptionHandler⼀般与@RestControllerAdvice配合使⽤,使⽤其来捕获和处理不同类型的异常。

4.创建org.springframework.boot.autoconfigure.AutoConfiguration.imports⽂件

com.bite.common.security.GlobalExceptionHandler

5.具体服务引入依赖

<dependency>
            <groupId>com.bite</groupId>
            <artifactId>oj-common-security</artifactId>
            <version>${oj-common-security.version}</version>
        </dependency>

⽇志框架引⼊

为了更加方便我们进行代码错误的定位,我们这里需要引入日志框架

这里选择slf4j+logback

为什么选择slf4j+logback

主要原因:配置灵活,springBoot的默认日志框架

配置⽂件

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 日志存放路径 -->
    <property name="log.path" value="logs/oj-system" />
    <!-- 日志输出格式 -->
    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />

    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>

    <!-- 系统日志输出 -->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/info.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 10天 -->
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>INFO</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 10天 -->
            <maxHistory>10</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>ERROR</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>

    <!--日志级别-->
    <root level="info">
        <appender-ref ref="file_info" />
        <appender-ref ref="console" />
        <appender-ref ref="file_error" />
    </root>

</configuration>

详细内容解释

配置日志的存放路径

日志输出格式

控制台路径输出格式

文件名字以及日志文件存放路径

滚动策略

最大文件存在时间

日志格式

过滤级别(接受日志级别)

日志级别配置

注意:日志只能接受比比日志级别高的日志

eg:定义为info时候可以接受到比它级别高的error日志,但是定义为error时候不能接受带info级别日志

测试使用

@RestControllerAdvice

@Slf4j
public class GlobalExceptionHandler {
    /**
     * 请求方式不支持
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public R<?> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
        return R.fail(ResultCode.ERROR);
    }
    /**
     * 拦截运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public R<?> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生运行时异常.", requestURI, e);
        return R.fail(ResultCode.ERROR);
    }

    /**
     * 系统异常
     */
    @ExceptionHandler(Exception.class)
    public R<?> handleException(Exception e, HttpServletRequest request) {
        String requestURI = request.getRequestURI();
        log.error("请求地址'{}',发生异常.", requestURI, e);
        return R.fail(ResultCode.ERROR);
    }
}

更多推荐