(六)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')") 直接加在方法前就可以了,然后每次回自动去验证。