后台基础权限框架搭建实现

本文涉及的产品
对象存储 OSS,20GB 3个月
对象存储 OSS,恶意文件检测 1000次 1年
对象存储 OSS,内容安全 1000 次 1年
简介: 后台基础权限框架搭建实现

@[toc]

后台权限框架搭建:本项目权限主要依赖 SpringSecurity实现,主要涉及的表有 角色表菜单表以及 角色菜单关联表等数据库表。权限部分功能的实现需要使用到自定义配制文件、自定义注解、自定义服务类等等...

1、项目整合SpringSecurity

1.1、引入SpringSecurity依赖

        <!--===================== SpringBoot相关依赖版本  =========================-->
        <springboot.version>2.5.5</springboot.version>
            <!-- security -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-security</artifactId>
                <version>${springboot.version}</version>
            </dependency>

1.2、启动测试

启动项目之后访问接口发现已经被SpringSecurity拦截,出现如下界面说明SpringSecurity已经成功引入

在这里插入图片描述

1.3、自定义实体类继承UserDetails

由于SpringSecurity默认提供的登陆接口会执行loadUserByUsername()方法,此方法的返回值为UserDetails,而SpringSecurity会根据返回值中的加密密码进行密码校验,所以我们需要自定义一个实体类来继承UserDetails
/**
 * @author 木字楠
 * @version 1.0
 * @description 用户信息
 * @date 2022/8/12
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {

    /**
     * 用户主键
     */
    private String id;

    /**
     * 用户名/OpenId
     */
    private String username;

    /**
     * 用户密码
     */
    private String password;

    /**
     * 用户登录方式(1 用户名  2 邮箱  3 QQ  4 微信)
     */
    private Integer loginType;

    /**
     * 是否开启邮箱登录(0 否  1是)
     */
    private Boolean emailLogin;

    /**
     * 是否禁用(0 正常  1禁用)
     */
    private Boolean disabled;

    /**
     * 用户名
     */
    private String nickname;

    /**
     * 用户头像
     */
    private String avatar;

    /**
     * 用户性别(-1 未知  0 仙女  1帅哥)
     */
    private String gender;

    /**
     * 用户邮箱
     */
    private String email;

    /**
     * 用户个人简介
     */
    private String personIntro;

    /**
     * ip地址
     */
    private String ipAddress;

    /**
     * ip来源
     */
    private String ipSource;

    /**
     * 用户创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime gmtCreate;

    /**
     * 用户信息更新时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime gmtUpdate;

    /**
     * 用户最近一次登录时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime lastLoginTime;

    /**
     * 浏览器
     */
    private String brower;

    /**
     * 操作系统
     */
    private String os;

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

    /**
     * 权限列表
     */
    private Set<String> permissionList;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        SimpleGrantedAuthority list = new SimpleGrantedAuthority(role);
        return Collections.singleton(list);
    }

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

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

1.4、自定义配制文件

引入依赖
        <!-- 自定义配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>
自定义配制信息
# custom Configuration
application:
  #Swagger Configuration
  swagger:
    isEnable: true
  #Super_admin Configuration
  highest-authority:
    highest-role-label: super_admin
    highest-role-secret: MuZiNan#$%^&*
    highest-permission: <*><*><*>
我们可以使用@Value的注解进行自定义配制信息的提取,但是那样做法太Low,而且不方便。这里我们采用更加优雅的方式来提起自定义注解信息!使用Properties配制文件来提取自定义配制信息!
/**
 * @author 木字楠
 * @version 1.0
 * @description 最高权限配制文件
 * @date 2022/8/12
 */
@Data
@ConfigurationProperties(HIGHEST_AUTHORITY)
public class HigestAuthorityProperties {

    /**
     * 最高权限角色标识
     */
    private String highestRoleLabel;

    /**
     * 最高权限角色秘钥
     */
    private String highestRoleSecret;

    /**
     * 最高权限标识
     */
    private String highestPermission;
}
我们是用常量的形式来简化字符串的拼接
# ConfigPropertiesConstant

/**
 * @author: 木字楠
 * @date: 2022/8/12
 * @version: 1.0
 */
public class ConfigPropertiesConstant {

    /**
     * 应用相关前缀,位于“Application”配置中
     */
    public static class Application {

        /**
         * 默认前缀
         */
        private static final String DEFAULT_PREFIX = "application";

        /**
         * JWT相关配置信息
         */
        public static final String JWT = DEFAULT_PREFIX + ".jwt";

        /**
         * 最高权限配制信息
         */
        public static final String HIGHEST_AUTHORITY = DEFAULT_PREFIX + ".highest-authority";

        /**
         * 阿里云Oss对象存储
         */
        public static final String ALIYUN_OSS = DEFAULT_PREFIX + ".aliyun.oss";

        /**
         * 七牛云Kodo对象存储
         */
        public static final String QINIU_KODO = DEFAULT_PREFIX + ".qiniu.kodo";

        /**
         * 服务器Ftp上传
         */
        public static final String ECS_FTP = DEFAULT_PREFIX + ".ecs.ftp";

    }

}
开启配制文件的使用
/**
 * @author 木字楠
 * @version 1.0
 * @description 配制文件是否开始使用
 * @date 2022/8/12
 */
@Configuration
@EnableConfigurationProperties({
        HigestAuthorityProperties.class,
})
public class PropertiesConfig {
}

1.5、重写loadUserByUsername方法

/**
 * @author 木字楠
 * @version 1.0
 * @description 自定义服务实现
 * @date 2022/8/12
 */
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private HttpServletRequest request;
    private final UserAuthService userAuthService;
    private final UserInfoService userInfoService;
    private final RoleService roleService;
    private final MenuService menuService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserAuth userAuth = usernamePasswordCheck(username);
        return convertToUser(request, userAuth);
    }

    /**
     * 用户数据转护
     *
     * @param request  request
     * @param userAuth 用户权限信息
     * @return {@link User} 用户信息
     */
    private User convertToUser(HttpServletRequest request, UserAuth userAuth) {
        User user = BeanCopyUtil.copyObject(userAuth, User.class);
        // 查询用户基础信息
        UserInfo userInfo = userInfoService.getOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getId, userAuth.getUserInfoId()));

        //region 获取用户Ip相关信息
        String ipAddress = IpUtil.getIpAddress(request);
        String ipSource = IpUtil.getIpSource(ipAddress);
        UserAgent userAgent = IpUtil.getUserAgent(request);
        //endregion

        //region 获取用户权限信息
        Set<String> permisstionSet = new HashSet<>(99);
        Role role = roleService.getOne(new LambdaQueryWrapper<Role>().eq(Role::getId, userAuth.getUserRoleId()));
        if (null != role) {
            permisstionSet = menuService.getUserPermissionList(role);
        }
        //endregion

        //数据封装
        String visitor = "游客";
        user.setNickname(userInfo.getNickname());
        user.setAvatar(userInfo.getAvatar());
        user.setGender(GenderEnum.getGenderValueByCode(userInfo.getGender()));
        user.setEmail(userInfo.getEmail());
        user.setPersonIntro(user.getPersonIntro());
        user.setIpAddress(ipAddress);
        user.setIpSource(ipSource);
        user.setGmtCreate(userInfo.getGmtCreate());
        user.setGmtUpdate(userInfo.getGmtUpdate());
        user.setLastLoginTime(userInfo.getLastLoginTime());
        user.setRole(null == role ? visitor : role.getRoleName());
        user.setPermissionList(permisstionSet);
        user.setOs(userAgent.getOperatingSystem().getName());
        user.setBrower(userAgent.getBrowser().getName());

        return user;
    }

    /**
     * 用户名密码校验
     */
    private UserAuth usernamePasswordCheck(String username) {
        // 用户名非空校验
        if (StringUtils.isEmpty(username)) {
            throw new BaseException(HttpCodeEnum.USERNAME_CAN_NOT_BE_EMPTY.getMessage());
        }

        // 查询用户信息
        UserAuth userAuth = userAuthService.getOne(new LambdaQueryWrapper<UserAuth>().eq(UserAuth::getUsername, username)
                .eq(UserAuth::getIsDisabled, SystemConstant.NOT_DISABLED)
                .eq(UserAuth::getIsDeleted, SystemConstant.NOT_DELETED));

        if (null == userAuth) {
            throw new BaseException(HttpCodeEnum.USERNAME_OR_PASSWORD_ERROR.getMessage());
        }

        return userAuth;
    }

}

1.6、自定义匿名访问注解

自定义匿名访问注解,届时我们会使用SpringSecurity拦截所有请求,未登录用户访问接口时将会统一返回 请登陆后再进行访问!,而带有匿名注解的接口将会被放行,无论用户是否登录,都可以正常访问。
/**
 * @author 木字楠
 * @version 1.0
 * @description 匿名访问注解
 * @date 2022/8/12
 */
public @interface Anonymous {
}
    private final ApplicationContext applicationContext;
    /**
     * 查找可以匿名访问的接口
     *
     * @return 匿名访问接口集合
     */
    private Set<String> listAnonymous() {
        Map<RequestMappingInfo, HandlerMethod> handlerMethods =
                applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
        Set<String> anonymousUrls = new HashSet<>();
        for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
            HandlerMethod handlerMethod = infoEntry.getValue();
            AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
            if (anonymousAccess != null) {
                assert infoEntry.getKey().getPatternsCondition() != null;
                anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
            }
        }
       System.out.println("可以匿名访问的url:{}", anonymousUrls);
        return anonymousUrls;
    }

1.8、编写SpringSecurity配制类

自定义配制类继承 WebSecurityConfigurerAdapter,并且重写其中的三个 config方法

在这里插入图片描述
在这里插入图片描述

public void configure(WebSecurity web) 内部重新实现,主要放行静态资源
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().mvcMatchers(
                "/js/**", "/css/**", "/img/**", "/fonts/**",
                "/", "/index.html", "/favicon.ico", "/doc.html",
                "/swagger-ui.html", "/webjars/**", "/swagger-resources/**", "/v3/**");
    }
protected void configure(AuthenticationManagerBuilder auth) 内部重新实现,指定UserDetailsService执行方式以及密码加密方式
    private final UserDetailsServiceImpl userDetailsService;
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }
相关实践学习
通义万相文本绘图与人像美化
本解决方案展示了如何利用自研的通义万相AIGC技术在Web服务中实现先进的图像生成。
目录
相关文章
|
存储 测试技术 数据库
数据驱动测试和关键词驱动测试的区别
数据驱动测试 数据驱动测试或 DDT 也被称为参数化测试。
230 1
|
开发框架 前端开发 JavaScript
React、Vue.js 和 Angular主流前端框架和选择指南
在当今的前端开发领域,选择合适的框架对于项目的成功至关重要。本文将介绍几个主流的前端框架——React、Vue.js 和 Angular,探讨它们各自的特点、开发场景、优缺点,并提供选择框架的建议。
289 6
|
NoSQL Ubuntu Linux
redis的基本安装配置启动使用
redis的基本安装配置启动使用
306 0
|
JSON 算法 数据可视化
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
这篇文章是关于如何通过算法接口返回的目标检测结果来计算性能指标的笔记。它涵盖了任务描述、指标分析(包括TP、FP、FN、TN、精准率和召回率),接口处理,数据集处理,以及如何使用实用工具进行文件操作和数据可视化。文章还提供了一些Python代码示例,用于处理图像文件、转换数据格式以及计算目标检测的性能指标。
273 0
测试专项笔记(一): 通过算法能力接口返回的检测结果完成相关指标的计算(目标检测)
|
XML 存储 网络协议
在Linux中,如何使用Wireshark进行网络协议分析?
在Linux中,如何使用Wireshark进行网络协议分析?
|
存储 机器学习/深度学习 大数据
云计算与大数据:合作与创新
本文探讨了大数据技术与云计算的背景和发展,大数据的5V特征(量、速度、多样、复杂、不确定)及云计算的3S特点(服务、共享、可扩展)。两者相互依赖,云计算为大数据提供计算与存储资源。核心算法涉及分布式计算、数据挖掘和机器学习,如线性回归、逻辑回归等。通过代码示例展示了Hadoop的MapReduce、Scikit-learn的KNN和TensorFlow的线性回归应用。未来趋势包括数据量增长、实时处理、AI与ML集成及数据安全挑战。附录解答了大数据、云计算等相关问题。
750 3
|
前端开发
使用CSS样式化占位文本
使用CSS样式化占位文本
89 0
|
缓存 前端开发
前端知识笔记(三十六)———HTTP 缓存机制
前端知识笔记(三十六)———HTTP 缓存机制
248 0
|
SQL NoSQL 关系型数据库
PostgreSQL 准确且快速的数据对比方法
作为一款强大而广受欢迎的开源关系型数据库管理系统,PostgreSQL 在数据库领域拥有显著的市场份额。其出色的可扩展性、稳定性使其成为众多企业和项目的首选数据库。而在很多场景下(开发 | 生产环境同步、备份恢复验证、数据迁移、数据合并等),不同环境中的数据库数据可能导致数据的不一致,因此,进行数据库之间的数据对比变得至关重要。
570 0