SpringSecurity实现登录和自定义权限认证

news2024/11/19 13:33:10

介绍

        Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

        Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。根据自己的需要,可以使用适当的过滤器来保护自己的应用程序。

        本文权限认证不采用springsecurity的注解方式,使用自定义的权限认证,这样不需要在控制层的每个接口前面加上权限注解,具体请看下面的权限校验流程。

登录流程和权限校验流程

具体代码查看实现中的github地址

登录流程

        首先,系统会将输入的用户名和密码放入authentication中,之后进入UserDetailsService的实现类中,调用sql,通过用户名查询账号信息和角色信息,统一存储在UserDetails中,之后PasswordEncoder会将查询到的账号密码和authentication中密码进行比对,密码不一致,则抛出异常(认证失败),密码正确则开始执行登录的业务,使用jwt生成token,将UserDetails存储到redis中,然后将token和角色id集返回给客户端。

权限校验流程

        首先,进入自定义的过滤器中,获取客户端传的token,如果token为空,则进入下一层跳出该过滤器,进入配置中,查询是否为不需要权限认证的接口,不是则抛出异常(认证失败);如果token不为空,则使用jwt解析token,获取其中的userId,根据该userId查询redis中存储的用户信息,查询为空则抛出异常(账户过期,请重新登录),之后获取客户端传的role_id(角色id,因为一个用户可能有多个角色,因此需要传个角色id,告诉服务器当前角色),将客户端传的role_id和之前在redis中查询的用户信息比对,查看是否存在该角色,存在,则通过该role_id查询redis中该角色的权限,判断当前请求路径该角色是否拥有权限,有权限则将权限信息封装到authentication中,放行。

 

实现

具体代码请看github地址icon-default.png?t=M85Bhttps://github.com/cn-g/springsecurity

下面展示关键代码实现

springsecurity配置文件

因为新版本SpringSecurity弃用了WebSecurityConfigurerAdapter,所以新版的SpringSecurity需要更换SpringSecurity的配置文件,下面将新版和老版的springsecurity配置代码各自展示了一份

不继承WebSecurityConfigurerAdapter

import com.example.springsecurity.filter.JwtAuthenticationTokenFilter;
import com.example.springsecurity.filter.AuthenticationEntryPointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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

/**
 * @author Admin
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig{

    @Resource
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Resource
    AuthenticationEntryPointImpl authenticationEntryPoint;


    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests()
                //登录接口,允许所有人访问
                .antMatchers("/user/login").permitAll()
                //除了上面的接口,其它接口都需要鉴权认证
                .anyRequest().authenticated();
        //配置登入认证失败、权限认证失败异常处理器
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
        //把token校验过滤器添加到过滤链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        //允许跨域
        http.cors();
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        final List<GlobalAuthenticationConfigurerAdapter> configurers = new ArrayList<>();
        configurers.add(new GlobalAuthenticationConfigurerAdapter() {
                            @Override
                            public void configure(AuthenticationManagerBuilder auth){
                                // auth.doSomething()
                            }
                        }
        );
        return authConfig.getAuthenticationManager();
    }

}

继承WebSecurityConfigurerAdapter

import com.example.springsecurity.filter.JwtAuthenticationTokenFilter;
import com.example.springsecurity.filter.AuthenticationEntryPointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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

/**
 * @author xu
 */
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Resource
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Resource
    AuthenticationEntryPointImpl authenticationEntryPoint;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

   @Override
   protected void configure(HttpSecurity http) throws Exception{
       http
               //关闭csrf
               .csrf().disable()
               //不通过Session获取SecurityContext
               .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               .and()
               .authorizeHttpRequests()
               //登录接口,允许所有人访问
               .antMatchers("/user/login").permitAll()
               //除了上面的接口,其它接口都需要鉴权认证
               .anyRequest().authenticated();
       //配置登入认证失败、权限认证失败异常处理器
       http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
       //把token校验过滤器添加到过滤链中
       http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
       //允许跨域
       http.cors();
   }

   @Bean
   @Override
   public AuthenticationManager authenticationManagerBean() throws Exception{
       return supper.authenticationManagerBean();
   }

}

过滤器实现

/**
 * token校验以及权限校验
 * 
 * @author xu
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private RedisCache redisCache;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        // 获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            // 放行
            filterChain.doFilter(request, response);
            return;
        }
        String userId;
        // 解析token
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userId = claims.getSubject();
        } catch (Exception e) {
            logger.error("解析token失败");
            WebUtil.renderString(response, JSON.toJSONString(ResponseModels.loginException()));
            return;
        }
        // 从redis中获取用户信息
        String redisKey = "login:" + userId;
        LoginUser loginUser = redisCache.getCacheObject(redisKey);
        if (ObjectUtils.isEmpty(loginUser)) {
            logger.error("用户信息获取失败");
            WebUtil.renderString(response, JSON.toJSONString(ResponseModels.commonException("账户过期,请重新登录")));
            return;
        }
        String roleId = request.getHeader("role_id");
        if(ObjectUtils.isEmpty(roleId)){
            logger.error("无角色id");
            WebUtil.renderString(response, JSON.toJSONString(ResponseModels.loginException()));
            return;
        }
        // 校验是否有该角色
        if (!loginUser.getPermissions().contains(roleId)) {
            logger.error("角色不匹配");
            WebUtil.renderString(response, JSON.toJSONString(ResponseModels.noPowerException()));
            return;
        }
        String url = request.getRequestURI();
        String role = redisCache.getCacheObject("role:role_" + roleId);
        // 校验角色是否存在该路径
        if (!role.contains(url)) {
            logger.error("该接口路径无权限");
            WebUtil.renderString(response, JSON.toJSONString(ResponseModels.noPowerException()));
            return;
        }
        // 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
            new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        // 存入securityContextHolder
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        // 放行
        filterChain.doFilter(request, response);
    }

UserDetailsServiceImpl实现:

/**
 * UserDetailsService实现类
 * 
 * @author xu
 */
@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String userName) {
        log.info("用户名称为:{}", userName);
        User user = userMapper
            .selectOne(Wrappers.lambdaQuery(User.class).eq(User::getUsername, userName).eq(User::getStatus, 0));
        if (ObjectUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("用户名不存在");
        }
        // 获取当前用户角色信息
        List<String> list = roleMapper.selectRoleByUserId(user.getId());
        log.info("用户的角色为:{}", list);
        //LoginUser为UserDetails的实现类
        return new LoginUser(user, list);
    }
}

登录、登出业务实现

    public ResponseModelDto login(User user) {
        UsernamePasswordAuthenticationToken authenticationToken =
            new UsernamePasswordAuthenticationToken(user.getUsername(),         user.getPassword());
        Authentication authentication =     authenticationManager.authenticate(authenticationToken);
        if (ObjectUtils.isEmpty(authentication)) {
            throw new CommonException("用户名或密码错误");
        }
        log.info("用户登录成功:{}", authentication);
        // 使用userId生成token
        LoginUser loginUser = (LoginUser)authentication.getPrincipal();
        String userId = loginUser.getUser().getId().toString();
        String jwt = JwtUtil.createJWT(userId);
        redisCache.setCacheObject("login:" + userId, loginUser);
        // 获取当前用户角色信息
        List<String> list = roleMapper.selectRoleByUserId(Long.valueOf(userId));
        HashMap<String, String> map = new HashMap<>();
        map.put("token", jwt);
        map.put("role", String.join(",",list));
        //返回token和角色id集
        return ResponseModels.ok(map);
    }

    @Override
    public ResponseModelDto logout() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginUser loginUser = (LoginUser)authentication.getPrincipal();
        Long userId = loginUser.getUser().getId();
        redisCache.deleteObject("login:" + userId);
        return ResponseModels.ok("登出成功");
    }

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

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

相关文章

怎么看l2接口十档行情?

怎么看l2接口十档行情&#xff1f;打开任意个股&#xff0c;在盘口即可查看买一至买十&#xff0c;卖一至卖十。Level行情1只能看五档报价&#xff0c;但是五档报价看起来很平静&#xff0c;但是主力很可能会在五档报价之外悄悄布局。使用Level2接口&#xff0c;你可以看到10档…

python制作刮刮乐惊喜揭秘呀~【趣味代码】

前言 大家早好、午好、晚好吖 ❤ ~ 在今天&#xff0c;在我百般无聊的时候&#xff0c;突然发现一存货 于是&#xff0c;我把从犄角旮旯里翻出来的代码一运行 嘿&#xff0c;真不错~那在这里就分享给大家 素材 首先我们准备一些图片以及一首下载好的音乐&#xff08;游戏音…

C++ 数学与算法系列之认识格雷码

1. 前言 程序中所涉及到的任何数据&#xff0c;计算机底层均需转换成二进制数值后方可存储&#xff0c;这个过程也称为编码。反之&#xff0c;把底层二进制数据转换成应用数据称为解码&#xff0c; 不同的数据类型需要不同的编&#xff08;解&#xff09;码方案&#xff0c;如…

论文投稿指南——中国(中文EI)期刊推荐(第2期)

&#x1f680; EI是国际知名三大检索系统之一&#xff0c;在学术界的知名度和认可度仅次于SCI&#xff01;&#x1f384;&#x1f388; 【前言】 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊。其中&#xf…

基于jsp+ssm闲置图书分享平台系统-计算机毕业设计

项目介绍 本闲置图书管理系统主要包括系统图书交换模块、图书分享管理模块、图书借阅管理模块、新闻公告管理模块、图书类别管理、图书信息管理、登录模块、和退出模块等多个模块,采用目前最流行的B/S结构和java中流行的MVC三层设计模式和eclipse编辑器、MySQL 数据库设计并实…

代码提速100倍,怎么实现的?

众所周知&#xff0c;Python的简单和易读性是靠牺牲性能为代价的 尤其是在计算密集的情况下&#xff0c;比如多重for循环。不过现在&#xff0c;大佬胡渊鸣说了&#xff1a; 只需import 一个叫做“Taichi”的库&#xff0c;就可以把代码速度提升100倍&#xff01; 不信&…

VTK - vtkPolyData数据的Remesh

欢迎加入我的VTK社区 雪易VTK社区-CSDN社区云 前言&#xff1a;在研究3-matic软件中smooth Edge和Local Smoothing功能时&#xff0c;先对数据的网格进行了重新的划分&#xff0c;即3-matic软件中的Remesh功能。本博文主要针对Remesh进行展开。 目录 网格质量 vtkMeshQuali…

【JavaScript】ESLint 深入浅出

▒ 目录 ▒&#x1f6eb; 导读需求开发环境1️⃣ 初体验安装lint配置.eslintrc.js忽略文件package.json中添加eslint脚本2️⃣ vscode中使用插件安装作用3️⃣ 规避报错问题行添加注释问题文件开头添加注释修改配置文件.eslintrc.js4️⃣ 常见错误汇总Cannot read property nam…

站在巨人的肩膀上,用Node+ChatGPT模块实现一个接口

目录 前言 准备工作 起步 写在最后 前言 蹭一下最近比较火的人工智能ChatGPT的热度&#xff0c;最近看到许多小伙伴都在调戏ChatGPT&#xff0c;看到这我就坐不住了&#xff0c;这种事怎么能少了我&#xff0c;于是闲&#xff08;划&#xff09;暇&#xff08;水&#xff0…

什么事Jupyter Notebook?

Jupyter Notebook是基于网页的用于交互计算的应用程序。其可被应用于全过程计算&#xff1a;开发、文档编写、运行代码和展示结果。 简而言之&#xff0c;Jupyter Notebook是以网页的形式打开&#xff0c;可以在网页页面中直接编写代码和运行代码&#xff0c;代码的运行结果也…

【javascript】 初见浏览器端日志系统 log4js、bunyan

▒ 目录 ▒&#x1f6eb; 导读需求开发环境1️⃣ log4js配置实现2️⃣ bunyan自定义MyRawStream实现&#x1f6ec; 文章小结&#x1f4d6; 参考资料&#x1f6eb; 导读 需求 用习惯了python、java的日志系统&#xff0c;现在使用console.log等览器端js接口打印日志&#xff0c…

m基于FPGA的多级抽取滤波器组verilog设计,包括CIC滤波,HB半带滤波以及DA分布式FIR滤波

目录 1.算法描述 2.仿真效果预览 3.verilog核心程序 4.完整FPGA 1.算法描述 数字下变频中的低通滤波器是由多级抽取滤波器组实现的。信号的同相分量和正交分量再分别经由积分梳状滤波器(CIC)、半带滤波器(HB)和有限长单位脉冲响应(FIR)滤波器构成的多级抽取滤波器组进行滤波…

vcenter开机报错activating swap-devices in /etc/fstab

问题&#xff1a;昨天IDC机房一台存储断电了&#xff0c;恰巧vcenter在这台存储上&#xff0c;重启存储后再重启vcenter报了以下错误&#xff1a; 参考文档&#xff1a; https://www.virtualizestuff.com/2015/10/29/vcsa_fstab_failed/ https://kb.vmware.com/s/article/2069…

政企数智办公潮水里的融云「答卷」

在这张集合了党政机关、金融保险、交通、能源电力等中大型组织复杂办公需求的高难度答卷上&#xff0c;融云在扎实耐打的通信底层之上&#xff0c;保持灵活的身段和强大的进化能力&#xff0c;稳定而轻盈&#xff0c;在不断变化的环境中正在成为确定性本身。 作者|皮爷 出品…

手把手教你打造一款个人专属Android桌面

实现方式两种 1.从头到尾写一个apk然后把系统的属性加上去&#xff0c;然后启动的时候默认就指定到这个apk的包名&#xff0c;他就启动&#xff0c; 2.我们基于Androidlauncher3的源码去做一个定制化的修改 分析一下这两种的区别&#xff0c; 自定义&#xff0c;要有丰富的…

使用SuperMap iDesktopX如何去掉“耗子尾巴”

在项目中&#xff0c;通过会遇到一些错误的矢量数据&#xff0c;比如“耗子尾巴”。什么是“耗子尾巴呢”&#xff0c;我们所说的“耗子尾巴图斑”&#xff0c;是不规则图斑的一种形态。规则的图斑&#xff0c;应该形态接近圆、矩形、菱形或凸多边形的图斑。自然界中多数图斑应…

[附源码]计算机毕业设计JAVA政府项目管理平台

[附源码]计算机毕业设计JAVA政府项目管理平台 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

ASEMI整流桥MB10F、MB6S、ABS10参数对比

编辑-Z 封装和参数是工程师在选型整流桥时的两大重要依据&#xff0c;下面我们就把比较常见的ASEMI整流桥MB10F、MB6S、ABS10参数做个对比&#xff0c;方便大家选型。 整流桥MB10F参数&#xff1a; 型号&#xff1a;MB10F 封装&#xff1a;MBF-4 最大重复峰值反向电压&…

Redis - Linux下载与安装

1.通过apt方式安装Redis 在终端中输入&#xff0c;如下命令进行安装&#xff1a; # 更新软件源 sudo apt update # 安装redis-server sudo apt install redis-serverredis服务安装完成后&#xff0c;服务将自动启动。通过如下命令查看服务进程是否启动&#xff1a; ps aux |…

快速上手Django(八) -Django之 统一异常、Response处理

文章目录快速上手Django(八) -Django之 统一异常、Response处理一、统一Response处理二、统一异常处理1. 需求背景2. Django、drf统一异常处理3. Django、drf异常处理基础4. 纯django场景下5. 【重要】使用drf场景下&#xff0c;实现思路编写自定义异常处理方法在settings/dev.…