当前位置: 首页 > article >正文

(六)Spring Boot学习——spring security做基于方法的认证

卧槽,越学越乱

我的是基于数据库分配权限,然后在controller中基于方法权限的认证,别跟着代码配置运行,要看完全部的,后边补充来后边学习的,顺序也是有点乱的。

关于数据库表格:主要的有用户表、角色表、权限表,然后对应关系的表是:用户-角色表 、角色-权限表。(记得设置好主键外键这些)

1.用户表

DROP TABLE IF EXISTS `tb_base_user`;
CREATE TABLE `tb_base_user`  (
  `update_date` datetime NULL DEFAULT NULL COMMENT '更新时间',
  `is_main` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `sex` int NULL DEFAULT NULL COMMENT '性别',
  `mobile` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '电话',
  `login_flag` int NULL DEFAULT NULL COMMENT '是否登录',
  `sort` int NULL DEFAULT NULL COMMENT '排序',
  `del_flag` int NULL DEFAULT NULL COMMENT '是否删除\r\n',
  `company_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '公司id',
  `company_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '公司名称',
  `office_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '部门id',
  `office_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '部门名称',
  `login_name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登录名',
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `name` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '1' COMMENT 'id',
  `job` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '职位',
  `token` varchar(2550) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'token',
  `failed_login_num` int NULL DEFAULT NULL,
  `is_locked` int NULL DEFAULT NULL,
  PRIMARY KEY (`id`, `login_name`) USING BTREE,
  INDEX `fk_oaid_profile`(`update_date`) USING BTREE,
  INDEX `fk_userid_profile`(`office_name`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = DYNAMIC;

SET FOREIGN_KEY_CHECKS = 1;

个人根据自己需求设置的哈。我的这里增加了2个字段failed_login_num和is_locked,目的是为了保证安全性,密码输错5次就锁定。

(1)xml中配置

<update id="increaseFailNum" parameterType="string">
        UPDATE tb_base_user
        SET failed_login_num = failed_login_num + 1,
            is_locked = CASE WHEN failed_login_num + 1 > 5 THEN 1 ELSE 0 END
        WHERE login_name = #{loginName}
</update>

(2)dao中配置

     /**
     * 用户失败验证,失败则增加1,超过5次锁定用户
     * 这里默认数据库某个字段自行+1
     * @param oaId
     * @return
     */
    int increaseFailNum(String oaId);

(3)userDetailsImpl中配置,代码里包含了权限获取的那部分代码了,后面也要用到


@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    //引入数据库接口
    @Autowired
    private BaseUserDao baseUserDao;
    @Autowired
    private BaseRolePermissionDao baseRolePermissionDao;
    @Autowired
    private BasePermissionDao basePermissionDao;
    @Autowired
    private BaseUserRoleDao baseUserJobRoleDao;
    /**
     * 重写loadUserByUsername方法
     * @param username the username identifying the user whose data is required.
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        System.out.println("进入loadUserByUsername:"+username);
        BaseUser baseUsers = new BaseUser();
        baseUsers.setLoginName(username);
        List<BaseUser> baseUserList = baseUserDao.queryUserList(baseUsers);
        //判断用户是否存在
        if (baseUserList == null || baseUserList.isEmpty()) {
            System.out.println("------------->loadUserByUsername验证失败,用户 "+baseUsers.getLoginName()+" 不存在!");
            throw new UsernameNotFoundException("usernameNotFound:"+username);
        }
        //判断用户是否锁定
        if(baseUserList.get(0).getIsLocked()==1){
            System.out.println("------------->loadUserByUsername验证失败,用户 "+baseUsers.getLoginName()+" 已被锁定!");
            throw new UsernameNotFoundException("userIsLocked:"+username);
        }
        //增加权限设置
        List<BaseUserRole> baseUserJobRoleList = baseUserJobRoleDao.queryUserRoleList(baseUserList.get(0).getLoginName());
        List<GrantedAuthority> authorities = new ArrayList<>();
        for(int i=0 ; i<baseUserJobRoleList.size(); i++){
            List<BaseRolePermission> baseRolePermissionList = baseRolePermissionDao.queryRolePermissionList(baseUserJobRoleList.get(i).getRoleId());
            for(int j=0 ; j<baseRolePermissionList.size(); j++){
                List<BasePermission> basePermissionList = basePermissionDao.queryPermissionList(baseRolePermissionList.get(j).getPermissionId());
                for(int k=0 ; k<basePermissionList.size(); k++){
                    authorities.add(new SimpleGrantedAuthority(basePermissionList.get(k).getPermissionName()));
                }

            }
        }
        return new UserDetailsImpl(baseUserList.get(0),authorities);	// UserDetailsImpl 是我们实现的类
    }
}

(4)在密码验证中设置


@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    //引入数据库接口
    @Autowired
    private BaseUserDao baseUserDao;
    @Autowired
    private BaseRolePermissionDao baseRolePermissionDao;
    @Autowired
    private BasePermissionDao basePermissionDao;
    @Autowired
    private BaseUserRoleDao baseUserJobRoleDao;
    /**
     * 重写loadUserByUsername方法
     * @param username the username identifying the user whose data is required.
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
        System.out.println("进入loadUserByUsername:"+username);
        BaseUser baseUsers = new BaseUser();
        baseUsers.setLoginName(username);
        List<BaseUser> baseUserList = baseUserDao.queryUserList(baseUsers);
        //判断用户是否存在
        if (baseUserList == null || baseUserList.isEmpty()) {
            System.out.println("------------->loadUserByUsername验证失败,用户 "+baseUsers.getLoginName()+" 不存在!");
            throw new UsernameNotFoundException("usernameNotFound:"+username);
        }
        //判断用户是否锁定
        if(baseUserList.get(0).getIsLocked()==1){
            System.out.println("------------->loadUserByUsername验证失败,用户 "+baseUsers.getLoginName()+" 已被锁定!");
            throw new UsernameNotFoundException("userIsLocked:"+username);
        }
        //增加权限设置
        List<BaseUserRole> baseUserJobRoleList = baseUserJobRoleDao.queryUserRoleList(baseUserList.get(0).getLoginName());
        List<GrantedAuthority> authorities = new ArrayList<>();
        for(int i=0 ; i<baseUserJobRoleList.size(); i++){
            List<BaseRolePermission> baseRolePermissionList = baseRolePermissionDao.queryRolePermissionList(baseUserJobRoleList.get(i).getRoleId());
            for(int j=0 ; j<baseRolePermissionList.size(); j++){
                List<BasePermission> basePermissionList = basePermissionDao.queryPermissionList(baseRolePermissionList.get(j).getPermissionId());
                for(int k=0 ; k<basePermissionList.size(); k++){
                    authorities.add(new SimpleGrantedAuthority(basePermissionList.get(k).getPermissionName()));
                }

            }
        }
        return new UserDetailsImpl(baseUserList.get(0),authorities);	// UserDetailsImpl 是我们实现的类
    }
}

(5)url中要在登录成功后重置failed_login_num字段。另外加一个弱密码判定吧,另外一个util。


import java.util.regex.Pattern;

public class PasswordUtil {
    // 强密码的正则表达式
    private static final String PASSWORD_PATTERN =
            "^(?=.*[A-Z])" +     // 至少包含一个大写字母
                    "(?=.*[a-z])" +     // 至少包含一个小写字母
                    "(?=.*\\d)" +       // 至少包含一个数字
                    "(?=.*[@$!%*?&])" + // 至少包含一个特殊字符
                    ".{8,}$";           // 密码长度至少为 8 个字符

    // 连续数字的正则表达式,检测是否包含连续三个及以上的数字
    private static final String CONSECUTIVE_NUMBERS_PATTERN = "(\\d)\\1{2,}"; // 匹配连续的数字(如 123, 234, 987)

    /**
     * 判断密码是否符合强密码的标准
     * @param password 用户输入的密码
     * @return true 如果是强密码,false 如果是弱密码
     */
    public static boolean isStrongPassword(String password) {
        if (password == null  || password.isBlank()) {
            return false;  // 密码不能为空
        }
        // 使用正则表达式匹配密码
        Pattern pattern = Pattern.compile(PASSWORD_PATTERN);
        boolean isPasswordValid = pattern.matcher(password).matches();
        // 检查密码是否包含连续的 3 个及以上的数字
        if (isPasswordValid && containsConsecutiveNumbers(password)) {
            return false;  // 如果包含连续数字,认为是弱密码
        }
        return isPasswordValid;
    }

    /**
     * 判断密码是否为弱密码
     * @param password 用户输入的密码
     * @return true 如果是弱密码,false 如果是强密码
     */
    public static boolean isWeakPassword(String password) {
        return !isStrongPassword(password);  // 如果密码不是强密码,则认为是弱密码
    }

    /**
     * 判断密码是否包含连续三个或以上的数字
     * @param password 用户输入的密码
     * @return true 如果密码包含连续数字,false 否则
     */
    private static boolean containsConsecutiveNumbers(String password) {
        Pattern pattern = Pattern.compile(CONSECUTIVE_NUMBERS_PATTERN);
        return pattern.matcher(password).find();
    }

    public static void main(String[] args) {
        // 测试用例
        String password = "Passw0rd!"; // 强密码示例

        if (isWeakPassword(password)) {
            System.out.println("Password is weak.");
        } else {
            System.out.println("Password is strong.");
        }

        String weakPassword = "password123"; // 弱密码示例
        if (isWeakPassword(weakPassword)) {
            System.out.println("Password is weak.");
        } else {
            System.out.println("Password is strong.");
        }

        String consecutivePassword = "Password12345"; // 包含连续数字的密码
        if (isWeakPassword(consecutivePassword)) {
            System.out.println("Password is weak.");
        } else {
            System.out.println("Password is strong.");
        }
    }
}

2.其它表不想列了。跳过到userDetails,其中的userDetailsImpl在上面有提到了。

package com.lz.boot.lzwork.security.service.impl;

import com.lz.boot.lzwork.entity.BaseUser;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

@Data
//@AllArgsConstructor
//@NoArgsConstructor    // 这三个注解可以帮我们自动生成 get、set、有参、无参构造函数
public class UserDetailsImpl implements UserDetails {

    private BaseUser baseUser;
    private Collection<? extends GrantedAuthority> authorities;

    public  UserDetailsImpl(BaseUser baseUser, Collection<? extends GrantedAuthority> authorities) {
        this.baseUser = baseUser;
        this.authorities = authorities;
    }

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

    @Override
    public String getPassword() {
        return baseUser.getPassword();
    }

    @Override
    public String getUsername() {
        return baseUser.getLoginName();
    }

    @Override
    public boolean isAccountNonExpired() {  // 检查账户是否 没过期。
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {   // 检查账户是否 没有被锁定。
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {  //检查凭据(密码)是否 没过期。
        return true;
    }

    @Override
    public boolean isEnabled() {    // 检查账户是否启用。
        return true;
    }
    // 这个方法是 @Data注解 会自动帮我们生成,用来获取 loadUserByUsername 中最后我们返回的创建UserDetailsImpl对象时传入的User。
    // 如果你的字段包含 username和password 的话可以用强制类型转换, 把 UserDetailsImpl 转换成 User。如果不能强制类型转换的话就需要用到这个方法了
    public BaseUser getUser() {
        return baseUser;
    }
}

3.在controller中使用是@PreAuthorize("hasAuthority('MENU_MANAGE1')")  直接加在方法前就可以了,然后每次回自动去验证。


http://www.kler.cn/a/588657.html

相关文章:

  • IMX6ULL学习整理篇——UBoot的一些基础知识(1.编译流程)
  • 使用yolov8+flask实现精美登录界面+图片视频摄像头检测系统
  • 电机控制常见面试问题(十一)
  • 【CSS】一、基础选择器
  • 蓝桥杯好题推荐---激光炸弹
  • 【蓝桥杯每日一题】3.16
  • 【Agent】OpenManus 项目架构分析
  • 技术栈分享之----Swagger
  • 专题|Python贝叶斯金融数据应用实例合集:随机波动率SV模型、逻辑回归、参数更新、绩效比较BEST分析亚马逊股票、普尔指数...
  • 思维训练让你更高、更强 |【逻辑思维能力】「刷题训练笔记」假设法模式逻辑训练题(6-16)
  • JVM常用概念之即时常量
  • Bash语言的智能家居
  • airtest用法
  • 网络VLAN技术详解:原理、类型与实战配置
  • 重生之我在学Vue--第16天 Vue 3 插件开发
  • Django中的查询条件封装总结
  • 蓝桥杯——车牌(dfs全排列问题)
  • 【R语言入门】向量,存储和计算
  • Oracle Linux Server 7.9安装fail2ban
  • 【QA】建造者模式在Qt有哪些应用