自学内容网 自学内容网

Cookie+Redis+自定义参数解析器+AOP+自定义校验注解实现鉴权+改动CustomException

文章目录

1.数据库表设计

-- auto-generated definition
create table login_user
(
    id          bigint auto_increment comment '用户ID'
        primary key,
    nickname    varchar(255) default '默认昵称' not null comment '用户昵称',
    password    varchar(32)                     not null comment '密码(MD5)',
    salt        varchar(10)                     not null comment '盐值',
    role        varchar(10)                     not null comment '角色',
    create_by   varchar(64)                     null comment '创建人',
    create_time timestamp                       null comment '创建时间',
    update_by   varchar(64)                     null comment '更新人',
    update_time timestamp                       null comment '更新时间',
    is_deleted  tinyint                         null comment '逻辑删除标识(0.未删除 1.已删除)'
)
    charset = utf8mb4;

2.基础环境搭建

1.目录

CleanShot 2024-08-10 at 13.33.33@2x

2.MD5Util.java 加密加盐工具类
package com.sunxiansheng.user.validation.utils;

import org.apache.commons.codec.digest.DigestUtils;

/**
 * Description: MD5加密工具类
 *
 * @Author sun
 * @Create 2024/5/5 14:23
 * @Version 1.1
 */
public class MD5Util {

    /**
     * 将一个字符串转换为MD5
     *
     * @param src 原始字符串
     * @return 加密后的MD5字符串
     */
    public static String md5(String src) {
        return DigestUtils.md5Hex(src);
    }

    /**
     * 使用用户提供的盐值进行一次加密加盐处理
     *
     * @param inputPass 用户输入的密码
     * @param salt      用户提供的盐值
     * @return 加盐并加密后的字符串
     */
    public static String encryptWithSalt(String inputPass, String salt) {
        // 使用盐值对输入的密码进行加盐处理
        String saltedInput = saltString(inputPass, salt);
        // 使用MD5对加盐后的字符串进行加密
        return md5(saltedInput);
    }

    /**
     * 生成加盐后的字符串
     *
     * @param input 原始字符串
     * @param salt  用户提供的盐值
     * @return 加盐后的字符串
     */
    private static String saltString(String input, String salt) {
        // 简单地使用盐值的第一个字符和最后一个字符与输入字符串拼接
        return salt.charAt(0) + input + salt.charAt(salt.length() - 1);
    }

    /**
     * 测试
     * @param args
     */
    public static void main(String[] args) {
        String password = "123456";
        String sun = encryptWithSalt(password, "sun");
        System.out.println("sun = " + sun);
    }
}
3.CookieUtil.java
package com.sunxiansheng.user.validation.utils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * Description: Cookie工具类
 *
 * @Author sun
 * @Create 2024/5/6 16:04
 * @Version 1.0
 */


public class CookieUtil {
    /**
     * 得到 Cookie 的值, 不编码
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String
            cookieName) {
        return getCookieValue(request, cookieName, false);
    }

    /**
     * 得到 Cookie 的值, *
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String
            cookieName, boolean isDecoder) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    if (isDecoder) {
                        retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
                    } else {
                        retValue = cookieList[i].getValue();
                    }
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 得到 Cookie 的值, *
     *
     * @param request
     * @param cookieName
     * @param encodeString
     * @return
     */
    public static String getCookieValue(HttpServletRequest request, String
            cookieName, String encodeString) {
        Cookie[] cookieList = request.getCookies();
        if (cookieList == null || cookieName == null) {
            return null;
        }
        String retValue = null;
        try {
            for (int i = 0; i < cookieList.length; i++) {
                if (cookieList[i].getName().equals(cookieName)) {
                    retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
                    break;
                }
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return retValue;
    }

    /**
     * 设置 Cookie 的值 不设置生效时间默认浏览器关闭即失效,也不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
            response, String cookieName, String cookieValue) {
        setCookie(request, response, cookieName, cookieValue, -1);
    }

    /**
     * 设置 Cookie 的值 在指定时间内生效,但不编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
            response, String cookieName, String cookieValue, int cookieMaxage) {
        setCookie(request, response, cookieName, cookieValue, cookieMaxage,
                false);
    }

    /**
     * 设置 Cookie 的值 不设置生效时间,但编码
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
            response, String cookieName, String cookieValue, boolean isEncode) {
        setCookie(request, response, cookieName, cookieValue, -1, isEncode);
    }

    /**
     * 设置 Cookie 的值 在指定时间内生效, 编码参数
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
            response, String cookieName, String cookieValue, int cookieMaxage, boolean
                                         isEncode) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage,
                isEncode);
    }

    /**
     * 设置 Cookie 的值 在指定时间内生效, 编码参数(指定编码)
     */
    public static void setCookie(HttpServletRequest request, HttpServletResponse
            response, String cookieName, String cookieValue, int cookieMaxage, String
                                         encodeString) {
        doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
    }

    /**
     * 删除 Cookie 带 cookie 域名
     */
    public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {
        doSetCookie(request, response, cookieName, "", -1, false);
    }

    /**
     * 设置 Cookie 的值,并使其在指定时间内生效
     *
     * @param cookieMaxage cookie 生效的最大秒数
     */
    private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue,
                                          int cookieMaxage, boolean isEncode) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else if (isEncode) {
                cookieValue = URLEncoder.encode(cookieValue, "utf-8");
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0) {
                cookie.setMaxAge(cookieMaxage);
            }
            // if (null != request) {// 设置域名的 cookie
            // String domainName = getDomainName(request);
            // if (!"localhost".equals(domainName)) {
            // cookie.setDomain(domainName);
            // }
            // }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 设置 Cookie 的值,并使其在指定时间内生效
     *
     * @param cookieMaxage cookie 生效的最大秒数
     */
    private static final void doSetCookie(HttpServletRequest request,
                                          HttpServletResponse response, String cookieName, String cookieValue,
                                          int cookieMaxage, String encodeString) {
        try {
            if (cookieValue == null) {
                cookieValue = "";
            } else {
                cookieValue = URLEncoder.encode(cookieValue, encodeString);
            }
            Cookie cookie = new Cookie(cookieName, cookieValue);
            if (cookieMaxage > 0) {
                cookie.setMaxAge(cookieMaxage);
            }
            if (null != request) {// 设置域名的 cookie
                String domainName = getDomainName(request);
                System.out.println(domainName);
                if (!"localhost".equals(domainName)) {
                    cookie.setDomain(domainName);
                }
            }
            cookie.setPath("/");
            response.addCookie(cookie);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 得到 cookie 的域名
     */
    private static final String getDomainName(HttpServletRequest request) {
        String domainName = null;
        // 通过 request 对象获取访问的 url 地址
        String serverName = request.getRequestURL().toString();
        if ("".equals(serverName)) {
            domainName = "";
        } else {
            // 将 url 地下转换为小写
            serverName = serverName.toLowerCase();
            // 如果 url 地址是以 http://开头 将 http://截取
            if (serverName.startsWith("http://")) {
                serverName = serverName.substring(7);
            }
            int end = serverName.length();
            // 判断 url 地址是否包含"/"
            if (serverName.contains("/")) {
                // 得到第一个"/"出现的位置
                end = serverName.indexOf("/");
            }
            // 截取
            serverName = serverName.substring(0, end);
            // 根据"."进行分割
            final String[] domains = serverName.split("\\.");
            int len = domains.length;
            if (len > 3) {
                // www.abc.com.cn
                domainName = domains[len - 3] + "." + domains[len - 2] + "." +
                        domains[len - 1];
            } else if (len > 1) {
                // abc.com or abc.cn
                domainName = domains[len - 2] + "." + domains[len - 1];
            } else {
                domainName = serverName;
            }
        }
        if (domainName.indexOf(":") > 0) {
            String[] ary = domainName.split("\\:");
            domainName = ary[0];
        }
        return domainName;
    }
}
4.其余的都是使用EasyCode自动生成的,不再赘述
5.测试是否可以访问

CleanShot 2024-08-10 at 13.47.32@2x

CleanShot 2024-08-10 at 13.47.28@2x

3.用户注册

1.LoginUserController.java
package com.sunxiansheng.user.validation.controller;

import com.sunxiansheng.response.Result;
import com.sunxiansheng.user.validation.entity.req.LoginUserReq;
import com.sunxiansheng.user.validation.service.LoginUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * (LoginUser)控制层
 *
 * @author sun
 * @since 2024-08-10 13:24:05
 */
@RestController
@RequestMapping("/login/user")
@Slf4j
public class LoginUserController {
    /**
     * 服务对象
     */
    @Resource
    private LoginUserService loginUserService;

    @ResponseBody
    @RequestMapping("/register")
    public Result<Boolean> register(@Valid @RequestBody LoginUserReq loginUserReq) {
        try {
            Boolean flag = loginUserService.register(loginUserReq);
            return Result.ok(flag);
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("用户注册失败!错误原因{}", e.getMessage(), e);
            }
            return Result.fail("注册失败!");
        }
    }

}

2.UserConstant.java 用户常量
package com.sunxiansheng.user.validation.constant;

/**
 * 用户常量
 */
public interface UserConstant {

    /**
     * 默认角色
     */
    String DEFAULT_ROLE = "user";

    /**
     * 管理员角色
     */
    String ADMIN_ROLE = "admin";

    /**
     * 被封号
     */
    String BAN_ROLE = "ban";

}
3.LoginUserReq.java 请求
package com.sunxiansheng.user.validation.entity.req;

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

import javax.validation.constraints.NotBlank;
import java.io.Serializable;

/**
 * (LoginUser)Req实体类:接受前端请求
 *
 * @author sun
 * @since 2024-08-10 13:26:50
 */
@Data
@Accessors(chain = true) // 支持链式调用
public class LoginUserReq implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 密码(MD5)
     */
    @NotBlank(message = "密码不能为空")
    private String password;

}

4.LoginUserService.java
package com.sunxiansheng.user.validation.service;

import com.sunxiansheng.user.validation.entity.req.LoginUserReq;

/**
 * (LoginUser)service接口
 *
 * @author sun
 * @since 2024-08-10 13:24:05
 */
public interface LoginUserService {

    /**
     * 注册
     *
     * @param loginUserReq
     * @return
     */
    public Boolean register(LoginUserReq loginUserReq);

}

5.LoginUserServiceImpl.java
package com.sunxiansheng.user.validation.service.impl;

import com.sunxiansheng.user.validation.constant.UserConstant;
import com.sunxiansheng.user.validation.entity.po.LoginUserPo;
import com.sunxiansheng.user.validation.entity.req.LoginUserReq;
import com.sunxiansheng.user.validation.mapper.LoginUserMapper;
import com.sunxiansheng.user.validation.service.LoginUserService;
import com.sunxiansheng.user.validation.utils.MD5Util;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.UUID;

/**
 * (LoginUser)service实现类
 *
 * @author sun
 * @since 2024-08-10 13:24:05
 */
@Service("loginUserService")
public class LoginUserServiceImpl implements LoginUserService {

    @Resource
    private LoginUserMapper loginUserMapper;

    /**
     * 注册
     *
     * @param loginUserReq
     * @return
     */
    @Override
    public Boolean register(LoginUserReq loginUserReq) {
        // 获取信息
        String password = loginUserReq.getPassword();
        // 使用uuid作为用户id
        String userId = UUID.randomUUID().toString().replace("-", "");
        // 密码加密加盐
        // 这里使用固定的盐测试
        String salt = "sun";
        String userPassword = MD5Util.encryptWithSalt(password, salt);
        // 构造po
        LoginUserPo user = new LoginUserPo()
                .setNickname(userId)
                .setPassword(userPassword)
                .setSalt(salt)
                .setRole(UserConstant.DEFAULT_ROLE)
                .setCreateBy(userId)
                .setCreateTime(new Date())
                .setIsDeleted(0);
        // 插入数据库
        int insert = loginUserMapper.insert(user);
        return insert > 0;
    }
}

6.测试

CleanShot 2024-08-10 at 14.32.37@2x

4.用户登录基本实现

1.目录

CleanShot 2024-08-10 at 15.26.12@2x

2.LoginUserController.java 用户传递用户名和密码
    /**
     * 用户登录
     */
    @ResponseBody
    @RequestMapping("/login")
    public Result<Boolean> login(@RequestParam String nickname, @RequestParam String password, HttpServletRequest request, HttpServletResponse response) {
        try {
            LoginUserReq loginUserReq = new LoginUserReq().setNickname(nickname).setPassword(password);
            Boolean flag = loginUserService.login(loginUserReq, request, response);
            return Result.ok(flag);
        } catch (Exception e) {
            if (log.isErrorEnabled()) {
                log.error("用户登录失败!错误原因{}", e.getMessage(), e);
            }
            return Result.fail("登录失败!");
        }
    }
2.LoginUserService.java
    /**
     * 登录
     *
     * @param loginUserReq
     * @return
     */
    Boolean login(LoginUserReq loginUserReq, HttpServletRequest request, HttpServletResponse response);
3.LoginUserServiceImpl.java 将token -> userId存储到redis中,并将token也放到cookie中
    /**
     * 登录
     *
     * @param loginUserReq
     * @return
     */
    @Override
    public Boolean login(LoginUserReq loginUserReq, HttpServletRequest request, HttpServletResponse response) {
        // 获取信息(这里的nickname就相当于唯一标识了,其实应该是另一个字段比如userId,这里设计的不是很好)
        String userId = loginUserReq.getNickname();
        String password = loginUserReq.getPassword();
        // 首先查询有没有该用户
        LoginUserPo loginUserPo = new LoginUserPo()
                .setNickname(userId);
        List<LoginUserPo> loginUserPos = loginUserMapper.queryAllByLimit(loginUserPo);
        // 如果没有该用户,直接返回
        if (CollectionUtils.isEmpty(loginUserPos)) {
            throw new RuntimeException("用户不存在,请先注册!");
        }
        LoginUserPo userPo = loginUserPos.get(0);
        // 日志
        if (log.isInfoEnabled()) {
            log.info("login user:{}", userPo);
        }
        // 将密码加密后与数据库中的密码比对
        String encryptWithSalt = MD5Util.encryptWithSalt(password, userPo.getSalt());
        if (!encryptWithSalt.equals(userPo.getPassword())) {
            throw new RuntimeException("密码错误!");
        }
        // 登录成功则将用户信息存入redis
        // 1.生成一个随机的uuid作为token来标识用户
        String token = UUID.randomUUID().toString().replace("-", "");
        String tokenKey = redisUtil.buildKey(UserConstant.USER_TOKEN_PREFIX, token);
        // 2.将用户的userId存入redis
        redisUtil.set(tokenKey, userId);
        // 3.将这个token放到Cookie中
        CookieUtil.setCookie(request, response, "token", token);
        // 解释:目前redis中存着token.uuid -> userId,然后cookie中有token -> uuid
        // 这样就可以从cookie中拿到uuid从而从redis中拿到userId
        return true;
    }
4.常量 UserConstant.java

CleanShot 2024-08-10 at 15.38.18@2x

5.测试
1.用户登录,cookie会被设置token

CleanShot 2024-08-10 at 15.38.39@2x

2.redis中也会被设置token

CleanShot 2024-08-10 at 15.39.28@2x

5.使用自定义参数解析器,来校验登录并封装用户信息

1.目录

CleanShot 2024-08-10 at 17.14.20@2x

2.UserValidation.java 用户参数校验的实体类
package com.sunxiansheng.user.validation.entity.calibration;

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

import java.io.Serializable;
import java.util.Date;

/**
 * Description: 用户参数校验的实体类
 *
 * @Author sun
 * @Create 2024/8/10 15:53
 * @Version 1.0
 */
@Data
@Accessors(chain = true) // 支持链式调用
public class UserValidation implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Long id;

    /**
     * 用户昵称
     */
    private String nickname;

    /**
     * 密码(MD5)
     */
    private String password;

    /**
     * 盐值
     */
    private String salt;

    /**
     * 角色
     */
    private String role;

    /**
     * 创建人
     */
    private String createBy;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新人
     */
    private String updateBy;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 逻辑删除标识(0.未删除 1.已删除)
     */
    private Integer isDeleted;

}
3.LoginUserController.java 注意不要加@RequestParam、@RequestBody、@PathVariable 等注解

因为他们的作用是告诉 Spring MVC 以特定方式解析该参数,例如从请求体、请求参数或路径变量中提取数据

    /**
     * 测试使用自定义参数解析器获取用户信息
     */
    @ResponseBody
    @RequestMapping("/getUserInfo")
    public Result<UserValidation> test(UserValidation userValidation) {
        return Result.ok(userValidation);
    }

4.LoginUserService.java
    /**
     * 校验并返回用户信息
     */
    UserValidation verifyAndGetUserInformation(HttpServletRequest request, HttpServletResponse response);

5.LoginUserServiceImpl.java 校验并返回用户信息
    /**
     * 校验并返回用户信息
     * @param request
     * @param response
     * @return
     */
    @Override
    public UserValidation verifyAndGetUserInformation(HttpServletRequest request, HttpServletResponse response) {
        // 获取Cookie中的token
        String token = CookieUtil.getCookieValue(request, "token");
        // 从redis中获取userId
        String tokenKey = redisUtil.buildKey(UserConstant.USER_TOKEN_PREFIX, token);
        String userId = redisUtil.get(tokenKey, String.class);
        // 从数据库中获取用户信息
        LoginUserPo loginUserPo = new LoginUserPo()
                .setNickname(userId);
        List<LoginUserPo> loginUserPos = loginUserMapper.queryAllByLimit(loginUserPo);
        // 如果有该用户,则返回UserValidation
        if (!CollectionUtils.isEmpty(loginUserPos)) {
            LoginUserPo userPo = loginUserPos.get(0);
            // 将po转为UserValidation
            return new UserValidation()
                    .setId(userPo.getId())
                    .setNickname(userPo.getNickname())
                    .setPassword(userPo.getPassword())
                    .setSalt(userPo.getSalt())
                    .setRole(userPo.getRole())
                    .setCreateBy(userPo.getCreateBy())
                    .setCreateTime(userPo.getCreateTime())
                    .setUpdateBy(userPo.getUpdateBy())
                    .setUpdateTime(userPo.getUpdateTime())
                    .setIsDeleted(userPo.getIsDeleted());
        }
        // 如果没有该用户,直接返回
        return null;
    }
6.UserArgumentResolver.java 自定义参数解析器
package com.sunxiansheng.user.validation.config;

import com.sunxiansheng.exception.CustomException;
import com.sunxiansheng.exception.response.RespBeanEnum;
import com.sunxiansheng.user.validation.entity.calibration.UserValidation;
import com.sunxiansheng.user.validation.service.LoginUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Description: 用户参数解析器
 *
 * @Author sun
 * @Create 2024/8/10 16:10
 * @Version 1.0
 */
@Component
@Slf4j
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

    @Resource
    private LoginUserService loginUserService;

    /*
     * 判断是否支持要转换的参数类型,简单来说,就是在这里设置要解析的参数类型
     */
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        // 只解析UserValidation类型的参数
        if (methodParameter.getParameterType() == UserValidation.class) {
            return true;
        }
        return false;
    }

    /**
     * 编写参数解析的逻辑
     *
     * @param methodParameter
     * @param modelAndViewContainer
     * @param nativeWebRequest
     * @param webDataBinderFactory
     * @return
     * @throws Exception
     */
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
        // 首先获取request和response
        HttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);
        HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);
        // 进行校验并获取用户信息
        UserValidation userValidation = loginUserService.verifyAndGetUserInformation(request, response);
        // 日志
        log.info("解析参数成功,参数为:{}", userValidation);
        // 如果校验失败就直接抛出自定义异常
        if (userValidation == null) {
            throw new CustomException(RespBeanEnum.USER_NOT_LOGIN);
        }
        return userValidation;
    }

}
7.WebConfig.java 将自定义参数解析器加入到spring容器中
package com.sunxiansheng.user.validation.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;
import java.util.List;

/**
 * Description: 将自定义参数解析器加入到spring容器中
 *
 * @Author sun
 * @Create 2024/8/10 16:18
 * @Version 1.0
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Resource
    private UserArgumentResolver userArgumentResolver;

    /**
     * 将自定义参数解析器加入到spring容器中
     * @param resolvers
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(userArgumentResolver);
    }

}
8.测试

CleanShot 2024-08-10 at 17.16.59@2x

6.使用AOP+自定义注解完成身份校验

1.目录

CleanShot 2024-08-11 at 13.21.29@2x

CleanShot 2024-08-11 at 13.21.35@2x

2.AuthCheck.java 自定义身份校验注解
package com.sunxiansheng.user.validation.annotation;

import com.sunxiansheng.user.validation.enums.UserRoleEnum;

import java.lang.annotation.*;

/**
 * 权限校验注解
 *
 * @author sunxiansheng
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthCheck {

    /**
     * 必须具有的角色,默认为普通用户
     */
    UserRoleEnum[] mustRoles() default UserRoleEnum.USER;

}
3.UserRoleEnum.java 用户角色枚举
package com.sunxiansheng.user.validation.enums;

import lombok.Getter;

import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 用户角色枚举
 *
 * @author sunxiansheng
 */
@Getter
public enum UserRoleEnum {

    USER(0, "普通用户"),
    ADMIN(1, "管理员"),
    BAN(2, "被封号"),
    ;

    private int code;
    private String desc;

    UserRoleEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    /**
     * 将枚举转换为map(静态初始化)
     */
    public static Map<Integer, UserRoleEnum> channelEnumMap = Stream.of(UserRoleEnum.values()).
            collect(Collectors.toMap(UserRoleEnum::getCode, Function.identity()));

    /**
     * 根据code来获取枚举
     */
    public static UserRoleEnum getByCode(int code) {
        return channelEnumMap.get(code);
    }

}

4.AuthInterceptor.java aop拦截注解
package com.sunxiansheng.user.validation.aop;

import com.sunxiansheng.exception.CustomException;
import com.sunxiansheng.exception.response.RespBeanEnum;
import com.sunxiansheng.user.validation.annotation.AuthCheck;
import com.sunxiansheng.user.validation.entity.calibration.UserValidation;
import com.sunxiansheng.user.validation.enums.UserRoleEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * Description: 权限校验AOP
 *
 * @Author sun
 * @Create 2024/8/11 12:11
 * @Version 1.0
 */
@Component
@Aspect
@Slf4j
public class AuthInterceptor {

    /**
     * 拦截注解所在的方法
     *
     * @param joinPoint
     * @param authCheck
     * @return
     * @throws Throwable
     */
    @Around("@annotation(authCheck)")
    public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
        // 获取注解中的值
        UserRoleEnum[] userRoleEnums = authCheck.mustRoles();
        // 获取方法信息
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof UserValidation) {
                // 存储用户角色
                UserRoleEnum userRole = null;
                // 直接获取用户信息
                UserValidation userValidation = (UserValidation) arg;
                // 这里其实存储code比较好,由于在数据库中存储的是String,所以这里简单做一下映射,正常情况下应该是使用code来获取对应的枚举
                String role = userValidation.getRole();
                // 简单做一下映射,为了测试
                if ("user".equals(role)) {
                    userRole = UserRoleEnum.USER;
                } else if ("admin".equals(role)) {
                    userRole = UserRoleEnum.ADMIN;
                } else if ("ban".equals(role)) {
                    userRole = UserRoleEnum.BAN;
                }
                // 日志打印用户应该有的权限和用户的权限,并打印该接口的全类名
                log.info("用户应该有的权限:{},用户的权限:{},接口:{}", userRoleEnums, userRole, joinPoint.getSignature());
                // 遍历注解中的值,判断是否有权限
                for (UserRoleEnum userRoleEnum : userRoleEnums) {
                    if (userRoleEnum == userRole) {
                        return joinPoint.proceed();
                    }
                }
                // 如果没有,则打印该接口的全类名以及用户应该有的权限和用户的权限
                log.error("接口角色权限校验异常:{},接口应有权限:{},用户权限:{}", joinPoint.getSignature(), userRoleEnums, userRole);
                throw new CustomException(RespBeanEnum.USER_ROLE_VALIDATION_EXCEPTION);
            }
        }
        // 判断是否有UserValidation类型的参数,如果没有则抛出自定义异常,提示未校验用户登录
        throw new CustomException(RespBeanEnum.UNCHECKED_LOGIN);
    }

}
5.UserRoleEnum.java 用户角色枚举
package com.sunxiansheng.user.validation.enums;

import lombok.Getter;

import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 用户角色枚举
 *
 * @author sunxiansheng
 */
@Getter
public enum UserRoleEnum {

    USER(0, "普通用户"),
    ADMIN(1, "管理员"),
    BAN(2, "被封号"),
    ;

    private int code;
    private String desc;

    UserRoleEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    /**
     * 将枚举转换为map(静态初始化)
     */
    public static Map<Integer, UserRoleEnum> channelEnumMap = Stream.of(UserRoleEnum.values()).
            collect(Collectors.toMap(UserRoleEnum::getCode, Function.identity()));

    /**
     * 根据code来获取枚举
     */
    public static UserRoleEnum getByCode(int code) {
        return channelEnumMap.get(code);
    }

}
6.CustomException.java 自定义异常类修改
package com.sunxiansheng.exception;

import com.sunxiansheng.exception.response.RespBeanEnum;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * Description: 自定义异常类,具有响应枚举的属性,并允许传递自定义的消息。
 *
 * @Author sun
 * @Create 2024/5/6 15:15
 * @Version 1.1
 */
@Data
@NoArgsConstructor
public class CustomException extends RuntimeException {
    private RespBeanEnum respBeanEnum;
    private String customMessage;

    /**
     * 如果只传入枚举,那么使用枚举的信息,作为异常信息
     *
     * @param respBeanEnum
     */
    public CustomException(RespBeanEnum respBeanEnum) {
        super(respBeanEnum != null ? respBeanEnum.getMessage() : null);
        this.respBeanEnum = respBeanEnum;
    }

    /**
     * 如果传入枚举和自定义消息,那么使用自定义消息,作为异常信息
     *
     * @param respBeanEnum
     * @param customMessage
     */
    public CustomException(RespBeanEnum respBeanEnum, String customMessage) {
        super(customMessage != null ? customMessage : (respBeanEnum != null ? respBeanEnum.getMessage() : null));
        this.respBeanEnum = respBeanEnum;
        this.customMessage = customMessage;
    }

    @Override
    public String getMessage() {
        if (customMessage != null) {
            return customMessage;
        }
        return respBeanEnum != null ? respBeanEnum.getMessage() : super.getMessage();
    }

    @Override
    public String toString() {
        return "CustomException{" +
                "respBeanEnum=" + (respBeanEnum != null ? respBeanEnum.toString() : "null") +
                ", customMessage='" + customMessage + '\'' +
                '}';
    }
}
7.RespBeanEnum.java 响应枚举

CleanShot 2024-08-11 at 13.25.58@2x

8.测试
1.登录校验使用UserValidation参数解析器,身份校验使用AuthCheck注解

CleanShot 2024-08-11 at 13.26.57@2x

2.校验失败情况

CleanShot 2024-08-11 at 13.27.29@2x

3.校验成功情况

CleanShot 2024-08-11 at 13.28.03@2x

CleanShot 2024-08-11 at 13.28.57@2x

CleanShot 2024-08-11 at 13.28.33@2x


原文地址:https://blog.csdn.net/m0_64637029/article/details/144793192

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!