基于JWT+SpringSecurity整合一个单点认证授权机制

news2025/6/8 5:23:42

基于 JWT + Spring Security 的授权认证机制,在整体架构设计上体现了高度的安全性与灵活性。其在整合框架中的应用,充分展示了模块化、可扩展性和高效鉴权的设计理念,为开发者提供了一种值得借鉴的安全架构模式。

1.SpringSecurity概念理解

1.1 一般的Web应用需要进行认证和授权。

认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户。

授权:经过认证后判断当前用户是否有权限进行某些操作

1.2 RBAC权限模型的理解

可以理解为用户对应角色,角色又对应权限,在不考虑加入部门等参数的干预下,整个RBAC模型可以简化为5张表来描述:

1.3 SpringSecurity机制:

整个SpringSecurity机制可以理解为是一个过滤器链机制,前端发请求给后端,请求会经过整个过滤器链的认证和授权逻辑才能执行后续正常的系统接口逻辑。下方三个为核心过滤器

第一个负责处理在登录页面填写用户名和密码后登录请求

第二个负责处理过滤器链中抛出的授权和认证异常

第三个是负责权限校验的过滤器

认证点认证流程:

2.认证架构设计

2.1 登录的整体流程设计:

从login()逻辑开始执行

2.1 SpringSecurity的配置类

登录接口实现:

 /**
 * 登录方法
 * @param loginBody 登录信息
 * @return 结果
 */
@Operation(summary = "登录方法")
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) {
    AjaxResult ajax = AjaxResult.success();
    // 生成令牌
    String token = loginService.login(
            loginBody.getUsername(),
            loginBody.getPassword(),
            loginBody.getCode(),
            loginBody.getUuid());
    ajax.put(Constants.TOKEN, token);
    return ajax;
}

/**
 * 登录验证
 * @param username 用户名
 * @param password 密码
 * @param code 验证码
 * @param uuid 唯一标识
 * @return 结果
 */
public String login(String username, String password, String code, String uuid)
{
    // 验证码校验
    validateCaptcha(username, code, uuid);
    // 登录前置校验
    loginPreCheck(username, password);
    String ip = IpUtils.getIpAddr();
    // 验证 IP 是否被封锁
    passwordService.validateIp(ip);
    // 用户验证
    Authentication authentication = null;
    try
    {
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
        AuthenticationContextHolder.setContext(authenticationToken);
        // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
        authentication = authenticationManager.authenticate(authenticationToken);
    }
    catch (Exception e)
    {
        if (e instanceof BadCredentialsException)
        {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        else
        {
            passwordService.incrementIpFailCount(ip);
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
            throw new ServiceException(e.getMessage());
        }
    }
    finally
    {
        AuthenticationContextHolder.clearContext();
    }
    AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
    LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    recordLoginInfo(loginUser.getUserId());
    // 生成token
    return tokenService.createToken(loginUser);
}

2.2 SpringSecurity配置类

/**
 * spring security配置
 *
 * @author Dftre
 */
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig {
    /**
     * 自定义用户认证逻辑
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 认证失败处理类
     */
    @Autowired
    private AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 退出处理类
     */
    @Autowired
    private LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * token认证过滤器
     */
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;

    /**
     * 跨域过滤器
     */
    @Autowired
    private CorsFilter corsFilter;

    /**
     * 允许匿名访问的地址
     */
    @Autowired
    private PermitAllUrlProperties permitAllUrl;

    /**
     * @return
     * @throws Exception
     */
    @Bean
    AuthenticationManager authenticationManager() {
        DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(userDetailsService);
        daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
        return new ProviderManager(daoAuthenticationProvider);
    }

    /**
     * anyRequest | 匹配所有请求路径
     * access | SpringEl表达式结果为true时可以访问
     * anonymous | 匿名可以访问
     * denyAll | 用户不能访问
     * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
     * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
     * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
     * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
     * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
     * hasRole | 如果有参数,参数表示角色,则其角色可以访问
     * permitAll | 用户可以任意访问
     * rememberMe | 允许通过remember-me登录的用户访问
     * authenticated | 用户登录后可访问
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity
                // CSRF禁用,因为不使用session
                .csrf(csrf -> csrf.disable())
                // 禁用HTTP响应标头
                .headers((headersCustomizer) -> {
                    headersCustomizer.cacheControl(cache -> cache.disable())
                            .frameOptions(options -> options.sameOrigin());
                })
                // 认证失败处理类
                .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
                // 基于token,所以不需要session
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 注解标记允许匿名访问的url
                .authorizeHttpRequests((requests) -> {
                    permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());
                    // 对于登录login 注册register 验证码captchaImage 允许匿名访问
                    requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()
                            // 静态资源,可匿名访问
                            .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
                                    "/profile/**")
                            .permitAll()
                            .requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs",
                                    "/druid/**", "/*/api-docs/**")
                            .permitAll()
                            .requestMatchers("/websocket/**").permitAll()
                            // 除上面外的所有请求全部需要鉴权认证
                            .anyRequest().authenticated();
                })
                // 添加Logout filter
                .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
                // 添加JWT filter
                .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
                // 添加CORS filter
                .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
                .addFilterBefore(corsFilter, LogoutFilter.class)
                .build();
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2.3 UserDetailsServiceImpl 重写 UserDetailsService接口的loadUserbyUsername方法

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    SysUser user = userService.selectUserByUserName(username);
    if (StringUtils.isNull(user)) {
        log.info("登录用户:{} 不存在.", username);
        throw new ServiceException("登录用户:" + username + " 不存在");
    } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
        log.info("登录用户:{} 已被删除.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
    } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
        log.info("登录用户:{} 已被停用.", username);
        throw new ServiceException("对不起,您的账号:" + username + " 已停用");
    }

    passwordService.validate(user);

    return createLoginUser(user);
}

public UserDetails createLoginUser(SysUser user) {
    return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}

2.4 保存LoginUser到Redis的逻辑在创建token的方法逻辑中实现

2.5 拦截器配置

/**
 * token过滤器 验证token有效性
 * 
 * @author ruoyi
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{
    @Autowired
    private TokenService tokenService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws ServletException, IOException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
        {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
        chain.doFilter(request, response);
    }
}

3.授权架构设计

权限的验证最核心的是使用的Spring Security的提供的权限注解`@PreAuthorize `

当 @PreAuthorize 注解被应用于某个方法时,Spring Security 在该方法执行前会先对当前认证的用户进行权限检查。如果检查通过,方法调用得以继续;否则,框架会抛出相应的权限异常(如 AccessDeniedException),阻止方法执行。

  • @ss 引用了名为 "ss" 的 Spring Bean,即我们的 PermissionService
  • hasPermi('manage:order:list') 调用了 PermissionService 的 hasPermi 方法,检查用户是否拥有 "manage:order:list" 权限

授权相关校验操作实现:

@Service("ss")
public class PermissionService implements IPermissionService {
    /** 所有权限标识 */
    private static final String ALL_PERMISSION = "*:*:*";

    /** 管理员角色权限标识 */
    private static final String SUPER_ADMIN = "admin";

    private static final String ROLE_DELIMETER = ",";

    private static final String PERMISSION_DELIMETER = ",";

    /**
     * 验证用户是否具备某权限
     * 
     * @param permission 权限字符串
     * @return 用户是否具备某权限
     */
    public boolean hasPermi(String permission) {
        if (StringUtils.isEmpty(permission)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
            return false;
        }
        PermissionContextHolder.setContext(permission);
        return hasPermissions(loginUser.getPermissions(), permission);
    }

    /**
     * 验证用户是否具有以下任意一个权限
     *
     * @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
     * @return 用户是否具有以下任意一个权限
     */
    public boolean hasAnyPermi(String permissions) {
        if (StringUtils.isEmpty(permissions)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
            return false;
        }
        PermissionContextHolder.setContext(permissions);
        Set<String> authorities = loginUser.getPermissions();
        for (String permission : permissions.split(PERMISSION_DELIMETER)) {
            if (permission != null && hasPermissions(authorities, permission)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断用户是否拥有某个角色
     * 
     * @param role 角色字符串
     * @return 用户是否具备某角色
     */
    public boolean hasRole(String role) {
        if (StringUtils.isEmpty(role)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
            return false;
        }
        for (SysRole sysRole : loginUser.getUser().getRoles()) {
            String roleKey = sysRole.getRoleKey();
            if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {
                return true;
            }
        }
        return false;
    }

    /**
     * 验证用户是否具有以下任意一个角色
     *
     * @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
     * @return 用户是否具有以下任意一个角色
     */
    public boolean hasAnyRoles(String roles) {
        if (StringUtils.isEmpty(roles)) {
            return false;
        }
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
            return false;
        }
        for (String role : roles.split(ROLE_DELIMETER)) {
            if (hasRole(role)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断是否包含权限
     * 
     * @param permissions 权限列表
     * @param permission  权限字符串
     * @return 用户是否具备某权限
     */
    private boolean hasPermissions(Set<String> permissions, String permission) {
        return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2403759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IDEA 打开文件乱码

问题&#xff1a;文件乱码 底部编码无法切换 解决方案&#xff1a; 第一步 使用Nodepad 查询文件编码 本项目设置为 转为 UTF-8 无 BOM 第二步&#xff1a;在 IntelliJ IDEA 中&#xff1a;右键点击文件 → File Encoding → 选择目标编码&#xff08;如 UTF-8&#xff09; 最…

第2章:Neo4j安装与配置

在了解了Neo4j的基本概念和优势之后&#xff0c;下一步就是将其安装并配置好&#xff0c;以便开始实际操作。本章将详细介绍Neo4j的各种部署方式&#xff0c;涵盖不同操作系统的安装步骤&#xff0c;深入探讨关键配置项&#xff0c;并介绍常用的管理工具&#xff0c;为读者顺利…

Shell 命令及运行原理 + 权限的概念(7)

文章目录 Shell 命令以及运行原理&#xff08;4-1.22.08&#xff09;Linux权限的概念1. 什么是权限2. 认识人&#xff08;普通用户&#xff0c;root用户&#xff09;以及两种用户的切换认识普通用户和root用户两种用户之间的切换指令提权 3. 文件的属性解析 权限属性指令ll显示…

抽奖系统核心——抽奖管理

目录 前端逻辑&#xff1a; 核心全局变量&#xff1a; reloadConf函数&#xff1a; nextStep函数&#xff1a; 后端实现&#xff1a; 抽奖接口: Controller层: Service层: MqReceiver&#xff1a; drawPrizeService&#xff1a; statusConvert()方法: activityStatu…

机器学习:集成学习概念和分类、随机森林、Adaboost、GBDT

本文目录&#xff1a; 一、集成学习概念**核心思想&#xff1a;** 二、集成学习分类&#xff08;一&#xff09;Bagging集成&#xff08;二&#xff09;Boosting集成&#xff08;三&#xff09;两种集成方法对比 三、随机森林&#xff08;一&#xff09;构造过程&#xff08;二…

基于J2EE架构的在线考试系统设计与实现【源码+文档】

目录 摘要: Abstract: 1 引言 2 在线考试系统构架 2.1 在线考试系统一般需求分析 2.2 当前在线考试系统现状分析 2.3 基于J2EE的在线考试系统架构介绍及拥有的优势 2.3.1 结构总体介绍 2.3.2 客户层组件 2.3.2.1 Applets 2.3.2.2 应用程序客户端 2.3.3 …

联想拯救者R9000P 网卡 Realtek 8852CE Ubuntu/Mint linux 系统睡眠后,无线网卡失效

联想拯救者R9000P 网卡型号 Realtek PCle GbE Family Controller Realtek 8852CE WiFi 6E PCI-E NIC 系统版本 Ubuntu 24.04 / mint 22.1 问题现象 rtw89_8852ce&#xff0c;Link is Down&#xff0c;xtal si not ready&#xff0c;mac init fail&#xff0c;xtal si not …

Python训练营打卡 Day46

道注意力(SE注意力) 知识点回顾&#xff1a; 不同CNN层的特征图&#xff1a;不同通道的特征图什么是注意力&#xff1a;注意力家族&#xff0c;类似于动物园&#xff0c;都是不同的模块&#xff0c;好不好试了才知道。通道注意力&#xff1a;模型的定义和插入的位置通道注意力后…

解决微软应用商店 (Microsoft store) 打不开,无网络连接的问题!

很多小伙伴都会遇见微软应用商店 (Microsoft store)打开后出现无网络的问题&#xff0c;一般出现这种问题基本都是因为你的电脑安装了某些银行的网银工具&#xff0c;因为网银工具为了安全会关闭Internet 选项中的最新版本的TLS协议&#xff0c;而微软商店又需要最新的TLS协议才…

《影像引导下骨盆创伤手术的术前骨折复位规划:基于学习的综合流程》|文献速递-深度学习医疗AI最新文献

Title 题目 Preoperative fracture reduction planning for image-guided pelvic trauma surgery: A comprehensive pipeline with learning 《影像引导下骨盆创伤手术的术前骨折复位规划&#xff1a;基于学习的综合流程》 01 文献速递介绍 《影像引导下骨盆创伤手术的术前…

如何使用Webhook触发器,在 ONLYOFFICE 协作空间构建智能工作流

在数字化办公中&#xff0c;ONLYOFFICE 协作空间作为一款功能强大的文档协作平台&#xff0c;提供了丰富的自动化功能。对于开发者而言&#xff0c;Webhook 触发器是实现业务流程自动化与系统集成的关键工具。本文将深入探讨如何在 ONLYOFFICE 协作空间中高效利用 Webhook&…

什么是DevOps智能平台的核心功能?

在数字化转型的浪潮中&#xff0c;DevOps智能平台已成为企业提升研发效能、加速产品迭代的核心工具。然而&#xff0c;许多人对“DevOps智能平台”的理解仍停留在“自动化工具链”的表层概念。今天&#xff0c;我们从一个真实场景切入&#xff1a;假设你是某互联网公司的技术负…

Windows账户管理,修改密码,创建帐户...(无需密码)

前言 我们使用wWindows操作系统时&#xff0c;账户是非常重要的概念 它不仅能够帮助我们区分文档主题权限等等 嗯还有最重要的解锁电脑的作用&#xff01; 但想要管理他&#xff0c;不仅需要原本的密码&#xff0c;而且设置中的管理项也非常的不全。 Windows有一款netplwi…

【Java后端基础 005】ThreadLocal-线程数据共享和安全

&#x1f4da;博客主页&#xff1a;代码探秘者 ✨专栏&#xff1a;文章正在持续更新ing… ✅C语言/C&#xff1a;C&#xff08;详细版&#xff09; 数据结构&#xff09; 十大排序算法 ✅Java基础&#xff1a;JavaSE基础 面向对象大合集 JavaSE进阶 Java版数据结构JDK新特性…

【C语言】C语言经典小游戏:贪吃蛇(下)

文章目录 一、游戏前准备二、游戏开始1、游戏开始函数&#xff08;GameStart&#xff09;1&#xff09;打印欢迎界⾯&#xff08;WelcomeToGame&#xff09;2&#xff09;创建地图&#xff08;CreateMap&#xff09;3&#xff09;初始化蛇⾝&#xff08;InitSnake&#xff09;4…

NTT印地赛车:数字孪生技术重构赛事体验范式,驱动观众参与度革命

引言&#xff1a;数字孪生技术赋能体育赛事&#xff0c;开启沉浸式观赛新纪元 在传统体育赛事观赛模式遭遇体验天花板之际&#xff0c;NTT与印地赛车系列赛&#xff08;NTT INDYCAR SERIES&#xff09;的深度合作&#xff0c;通过数字孪生&#xff08;Digital Twin&#xff09…

30.【新型数据架构】-区块链数据架构

30.【新型数据架构】-区块链数据架构:分布式账本,不可篡改性,用于数据溯源 一、区块链数据架构的本质:分布式账本的革新 区块链的核心是分布式账本技术(Distributed Ledger Technology, DLT),它颠覆了传统中心化数据库的架构模式: 去中心化存储: 账本数据不再集中存储…

在CSDN发布AWS Proton解决方案:实现云原生应用的标准化部署

引言&#xff1a;云原生时代的部署挑战 在云原生应用开发中&#xff0c;基础设施管理的复杂性已成为团队面临的核心挑战。随着微服务架构的普及&#xff0c;每个服务可能包含数十个AWS资源&#xff08;如Lambda、API Gateway、ECS集群等&#xff09;&#xff0c;传统的手动配置…

C++11 Move Constructors and Move Assignment Operators 从入门到精通

文章目录 一、引言二、基本概念2.1 右值引用&#xff08;Rvalue References&#xff09;2.2 移动语义&#xff08;Move Semantics&#xff09; 三、移动构造函数&#xff08;Move Constructors&#xff09;3.1 定义和语法3.2 示例代码3.3 使用场景 四、移动赋值运算符&#xff…

11 - ArcGIS For JavaScript -- 高程分析

这里写自定义目录标题 描述代码实现结果 描述 高程分析是地理信息系统(GIS)中的核心功能之一&#xff0c;主要涉及对地表高度数据(数字高程模型, DEM)的处理和分析。 ArcGIS For JavaScript4.32版本的发布&#xff0c;提供了Web端的针对高程分析的功能。 代码实现 <!doct…