我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Springboot如何实现认证和动态权限管理

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Springboot如何实现认证和动态权限管理

今天小编给大家分享一下Springboot如何实现认证和动态权限管理的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

知识点补充

Shiro缓存

流程分析

在原来的项目当中,由于没有配置缓存,因此每次需要验证当前主体有没有访问权限时,都会去查询数据库。由于权限数据是典型的读多写少的数据,因此,我们应该要对其加入缓存的支持。

当我们加入缓存后,shiro在做鉴权时先去缓存里查询相关数据,缓存里没有,则查询数据库并将查到的数据写入缓存,下次再查时就能从缓存当中获取数据,而不是从数据库中获取。这样就能改善我们的应用的性能。

接下来,我们去实现shiro的缓存管理部分。

Shiro会话机制

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如 web 容器 tomcat),不管 JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储 / 持久化、容器无关的集群、失效 / 过期支持、对 Web 的透明支持、SSO 单点登录的支持等特性。

我们将使用 Shiro 的会话管理来接管我们应用的web会话,并通过Redis来存储会话信息。

整合步骤

添加缓存

CacheManager

在Shiro当中,它提供了CacheManager这个类来做缓存管理。

使用Shiro默认的EhCache实现

在shiro当中,默认使用的是EhCache缓存框架。EhCache 是一个纯Java的进程内缓存框架,具有快速、精干等特点。

引入shiro-EhCache依赖
<dependency>    <groupId>org.apache.shiro</groupId>    <artifactId>shiro-ehcache</artifactId>    <version>1.4.0</version></dependency>

在SpringBoot整合Redis的过程中,还要注意版本匹配的问题,不然有可能报方法未找到的异常。

在ShiroConfig中添加缓存配置
private void enableCache(MySQLRealm realm){    //开启全局缓存配置    realm.setCachingEnabled(true);    //开启认证缓存配置    realm.setAuthenticationCachingEnabled(true);    //开启授权缓存配置    realm.setAuthorizationCachingEnabled(true);    //为了方便操作,我们给缓存起个名字    realm.setAuthenticationCacheName("authcCache");    realm.setAuthorizationCacheName("authzCache");    //注入缓存实现    realm.setCacheManager(new EhCacheManager());}

然后再在getRealm中调用这个方法即可。

提示:在这个实现当中,只是实现了本地的缓存。也就是说缓存的数据同应用一样共用一台机器的内存。如果服务器发生宕机或意外停电,那么缓存数据也将不复存在。当然你也可通过cacheManager.setCacheManagerConfigFile()方法给予缓存更多的配置。

接下来我们将通过Redis缓存我们的权限数据

使用Redis实现

添加依赖
<!--shiro-redis相关依赖-->        <dependency>            <groupId>org.crazycake</groupId>            <artifactId>shiro-redis</artifactId>            <version>3.1.0</version>            <!--    里面这个shiro-core版本较低,会引发一个异常ClassNotFoundException: org.apache.shiro.event.EventBus                    需要排除,直接使用上面的shiro                    shiro1.3 加入了时间总线。-->            <exclusions>                <exclusion>                    <groupId>org.apache.shiro</groupId>                    <artifactId>shiro-core</artifactId>                </exclusion>            </exclusions>        </dependency>
配置redis

在application.yml中添加redis的相关配置

spring:   redis:     host: 127.0.0.1     port: 6379     password: hewenping     timeout: 3000     jedis:       pool:         min-idle: 5         max-active: 20         max-idle: 15

修改ShiroConfig配置类,添加shiro-redis插件配置

@Configurationpublic class ShiroConfig {    private static final String CACHE_KEY = "shiro:cache:";    private static final String SESSION_KEY = "shiro:session:";    private static final int EXPIRE = 18000;    @Value("${spring.redis.host}")    private String host;    @Value("${spring.redis.port}")    private int port;    @Value("${spring.redis.timeout}")    private int timeout;    @Value("${spring.redis.password}")    private String password;    @Value("${spring.redis.jedis.pool.min-idle}")    private int minIdle;    @Value("${spring.redis.jedis.pool.max-idle}")    private int maxIdle;    @Value("${spring.redis.jedis.pool.max-active}")    private int maxActive;    @Bean    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);        return authorizationAttributeSourceAdvisor;    }        @Bean(name = "shiroFilterFactoryBean")    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        shiroFilterFactoryBean.setSecurityManager(securityManager);        //配置不拦截路径和拦截路径,顺序不能反        HashMap<String, String> map = new HashMap<>(5);        map.put("/authc    @Bean    public DefaultWebSecurityManager getSecurityManager( Realm realm){        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();        securityManager.setRealm(realm);            securityManager.setCacheManager(cacheManager());        SecurityUtils.setSecurityManager(securityManager);        return securityManager;    }        @Bean    public RedisManager redisManager() {        RedisManager redisManager = new RedisManager();        redisManager.setHost(host);        redisManager.setPort(port);        redisManager.setTimeout(timeout);        redisManager.setPassword(password);        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();        jedisPoolConfig.setMaxTotal(maxIdle+maxActive);        jedisPoolConfig.setMaxIdle(maxIdle);        jedisPoolConfig.setMinIdle(minIdle);        redisManager.setJedisPoolConfig(jedisPoolConfig);        return redisManager;    }    @Bean    public RedisCacheManager cacheManager() {        RedisCacheManager redisCacheManager = new RedisCacheManager();        redisCacheManager.setRedisManager(redisManager());        redisCacheManager.setKeyPrefix(CACHE_KEY);        // shiro-redis要求放在session里面的实体类必须有个id标识        //这是组成redis中所存储数据的key的一部分        redisCacheManager.setPrincipalIdFieldName("username");        return redisCacheManager;    }}

修改MySQLRealm中的doGetAuthenticationInfo方法,将User对象整体作为SimpleAuthenticationInfo的第一个参数。shiro-redis将根据RedisCacheManagerprincipalIdFieldName属性值从第一个参数中获取id值作为redis中数据的key的一部分。

@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {    if(token==null){        return null;    }    String principal = (String) token.getPrincipal();    User user = userService.findByUsername(principal);    SimpleAuthenticationInfo simpleAuthenticationInfo = new MyAuthcInfo(            //由于shiro-redis插件需要从这个属性中获取id作为redis的key            //所有这里传的是user而不是username            user,            //凭证信息            user.getPassword(),            //加密盐值            new CurrentSalt(user.getSalt()),            getName());        return simpleAuthenticationInfo;}

并修改MySQLRealm中的doGetAuthorizationInfo方法,从User对象中获取主身份信息。

@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {   User user = (User) principals.getPrimaryPrincipal();    String username = user.getUsername();    List<Role> roleList = roleService.findByUsername(username);    SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();    for (Role role : roleList) {        authorizationInfo.addRole(role.getRoleName());    }    List<Long> roleIdList  = new ArrayList<>();    for (Role role : roleList) {        roleIdList.add(role.getRoleId());    }    List<Resource> resourceList = resourceService.findByRoleIds(roleIdList);    for (Resource resource : resourceList) {        authorizationInfo.addStringPermission(resource.getResourcePermissionTag());    }    return authorizationInfo;}
自定义Salt

由于Shiro里面默认的SimpleByteSource没有实现序列化接口,导致ByteSource.Util.bytes()生成的salt在序列化时出错,因此需要自定义Salt类并实现序列化接口。并在自定义的Realm的认证方法使用new CurrentSalt(user.getSalt())传入盐值。

public class CurrentSalt extends SimpleByteSource implements Serializable {    public CurrentSalt(String string) {        super(string);    }    public CurrentSalt(byte[] bytes) {        super(bytes);    }    public CurrentSalt(char[] chars) {        super(chars);    }    public CurrentSalt(ByteSource source) {        super(source);    }    public CurrentSalt(File file) {        super(file);    }    public CurrentSalt(InputStream stream) {        super(stream);    }}

添加Shiro自定义会话

添加自定义会话ID生成器

public class ShiroSessionIdGenerator implements SessionIdGenerator {        @Override    public Serializable generateId(Session session) {        Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);        return String.format("login_token_%s", sessionId);    }}

添加自定义会话管理器

public class ShiroSessionManager extends DefaultWebSessionManager {    //定义常量    private static final String AUTHORIZATION = "Authorization";    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";    //重写构造器    public ShiroSessionManager() {        super();        this.setDeleteInvalidSessions(true);    }        @Override    public Serializable getSessionId(ServletRequest request, ServletResponse response) {        String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);        //如果请求头中存在token 则从请求头中获取token        if (!StringUtils.isEmpty(token)) {            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);            return token;        } else {            // 这里禁用掉Cookie获取方式            return null;        }    }}

配置自定义会话管理器

在ShiroConfig中添加对会话管理器的配置

@Beanpublic ShiroSessionIdGenerator sessionIdGenerator(){    return new ShiroSessionIdGenerator();}@Beanpublic RedisSessionDAO redisSessionDAO() {    RedisSessionDAO redisSessionDAO = new RedisSessionDAO();    redisSessionDAO.setRedisManager(redisManager());    redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());    redisSessionDAO.setKeyPrefix(SESSION_KEY);    redisSessionDAO.setExpire(EXPIRE);    return redisSessionDAO;}@Beanpublic SessionManager sessionManager() {    ShiroSessionManager shiroSessionManager = new ShiroSessionManager();    shiroSessionManager.setSessionDAO(redisSessionDAO());    //禁用cookie    shiroSessionManager.setSessionIdCookieEnabled(false);    //禁用会话id重写    shiroSessionManager.setSessionIdUrlRewritingEnabled(false);    return shiroSessionManager;}

目前最新版本(1.6.0)中,session管理器的setSessionIdUrlRewritingEnabled(false)配置没有生效,导致没有认证直接访问受保护资源出现多次重定向的错误。将shiro版本切换为1.5.0后就解决了这个bug。

本来这篇文章应该是昨晚发的,因为这个原因搞了好久,所有今天才发。。。

修改自定义Realm的doGetAuthenticationInfo认证方法

在认证信息返回前,我们需要做一个判断:如果当前用户已在旧设备上登录,则需要将旧设备上的会话id删掉,使其下线。

@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {    if(token==null){        return null;    }    String principal = (String) token.getPrincipal();    User user = userService.findByUsername(principal);    SimpleAuthenticationInfo simpleAuthenticationInfo = new MyAuthcInfo(            //由于shiro-redis插件需要从这个属性中获取id作为redis的key            //所有这里传的是user而不是username            user,            //凭证信息            user.getPassword(),            //加密盐值            new CurrentSalt(user.getSalt()),            getName());    //清除当前主体旧的会话,相当于你在新电脑上登录系统,把你之前在旧电脑上登录的会话挤下去    ShiroUtils.deleteCache(user.getUsername(),true);    return simpleAuthenticationInfo;}

修改login接口

我们将会话信息存储在redis中,并在用户认证通过后将会话Id以token的形式返回给用户。用户请求受保护资源时带上这个token,我们根据token信息去redis中获取用户的权限信息,从而做访问控制。

@PostMapping("/login")public HashMap<Object, Object> login(@RequestBody LoginVO loginVO) throws AuthenticationException {    boolean flags = authcService.login(loginVO);    HashMap<Object, Object> map = new HashMap<>(3);    if (flags){        Serializable id = SecurityUtils.getSubject().getSession().getId();        map.put("msg","登录成功");        map.put("token",id);        return map;    }else {        return null;    } }

添加全局异常处理

@ControllerAdvice(basePackages = "pers.lbf.springbootshiro")public class AuthExceptionHandler {    //==================认证异常====================//    @ExceptionHandler(ExpiredCredentialsException.class)    @ResponseBody    public String expiredCredentialsExceptionHandlerMethod(ExpiredCredentialsException e) {        return "凭证已过期";    }    @ExceptionHandler(IncorrectCredentialsException.class)    @ResponseBody    public String incorrectCredentialsExceptionHandlerMethod(IncorrectCredentialsException e) {        return "用户名或密码错误";    }    @ExceptionHandler(UnknownAccountException.class)    @ResponseBody    public String unknownAccountExceptionHandlerMethod(IncorrectCredentialsException e) {        return "用户名或密码错误";    }        @ExceptionHandler(LockedAccountException.class)    @ResponseBody    public String lockedAccountExceptionHandlerMethod(IncorrectCredentialsException e) {        return "账户被锁定";    }    //=================授权异常=====================//    @ExceptionHandler(UnauthorizedException.class)    @ResponseBody    public String unauthorizedExceptionHandlerMethod(UnauthorizedException e){        return "未授权!请联系管理员授权";    }}

实际开发中,应该对返回结果统一化,并给出业务错误码。这已经超出了本文的范畴,如有需要,请根据自身系统特点考量。

进行测试

认证

登录成功的情况

Springboot如何实现认证和动态权限管理

用户名或密码错误的情况

Springboot如何实现认证和动态权限管理

为了安全起见,不要暴露具体是用户名错误还是密码错误。

访问受保护资源

认证后访问有权限的资源

Springboot如何实现认证和动态权限管理

认证后访问无权限的资源

Springboot如何实现认证和动态权限管理

未认证直接访问的情况

Springboot如何实现认证和动态权限管理

查看redis

Springboot如何实现认证和动态权限管理

三个键值分别对应认证信息缓存、授权信息缓存和会话信息缓存。

以上就是“Springboot如何实现认证和动态权限管理”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Springboot如何实现认证和动态权限管理

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Springboot如何实现认证和动态权限管理

今天小编给大家分享一下Springboot如何实现认证和动态权限管理的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。知识点补充
2023-06-19

SpringBoot如何使用Sa-Token实现权限认证

今天小编给大家分享一下SpringBoot如何使用Sa-Token实现权限认证的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
2023-07-06

SpringBoot Security使用MySQL实现验证与权限管理

安全管理是软件系统必不可少的的功能。根据经典的“墨菲定律”——凡是可能,总会发生。如果系统存在安全隐患,最终必然会出现问题,这篇文章主要介绍了SpringBoot安全管理Spring Security基本配置
2022-11-13

MongoDB的权限管理与用户认证怎么实现

MongoDB的权限管理和用户认证是通过创建用户和设置角色来实现的。下面是MongoDB权限管理和用户认证的步骤:创建管理员用户:首先,在MongoDB中创建一个管理员用户,用于管理数据库的用户和角色。创建普通用户:使用管理员用户登录Mon
MongoDB的权限管理与用户认证怎么实现
2024-05-07

如何使用Python实现对ElasticSearch的安全认证和权限管理?(在Python中,怎样进行ElasticSearch的安全认证和权限设置?)

在Python中实现Elasticsearch安全认证和权限管理涉及以下步骤:创建用户,设置密码和角色。创建角色,指定索引权限。将角色与用户关联。启用安全。启用TLS以加密通信。使用用户名和密码进行身份验证。检查用户权限。最佳实践包括使用强密码、定期轮换密码、创建多个角色和限制特权访问。遵循这些步骤可确保Elasticsearch集群免受未经授权的访问和安全威胁。
如何使用Python实现对ElasticSearch的安全认证和权限管理?(在Python中,怎样进行ElasticSearch的安全认证和权限设置?)
2024-04-02

如何解决PHP开发中的安全认证和权限管理

随着互联网的发展,Web应用程序的安全性变得越来越重要。在PHP开发中,安全认证和权限管理是必不可少的。本文将介绍如何解决PHP开发中的安全认证和权限管理,并提供具体的代码示例。一、安全认证(Authentication)安全认证是验证用户
2023-10-21

Android动态权限申请如何实现

本篇内容介绍了“Android动态权限申请如何实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Google 在 Android 6.0 开
2023-07-05

Django如何实现RBAC权限管理

这篇文章主要介绍了Django如何实现RBAC权限管理问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-12-20

如何在PHP项目中实现用户认证和权限控制?

如何在PHP项目中实现用户认证和权限控制?在现代的Web应用程序中,用户认证和权限控制是非常重要的功能之一。用户认证用于验证用户的身份和权限,而权限控制则确定用户对系统中各种资源的访问权限。在PHP项目中实现用户认证和权限控制,可以保护用户
如何在PHP项目中实现用户认证和权限控制?
2023-11-02

Java如何实现权限管理系统

这篇文章主要介绍了Java如何实现权限管理系统,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。springboot+mybatis使用面向切面编程(AOP)实现的权限管理系统。
2023-06-22

Mysql如何实现用户权限管理

小编给大家分享一下Mysql如何实现用户权限管理,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1. MySQL 权限介绍mysql中存在4个控制权限的表,分别为u
2023-06-15

Prometheus系统如何实现多租户和权限管理

Prometheus系统可以通过以下方式实现多租户和权限管理:使用Prometheus的基于角色的访问控制(RBAC)功能:Prometheus支持基于角色的访问控制,可以为每个租户定义不同的角色和权限,从而实现多租户和权限管理。管理员可以
Prometheus系统如何实现多租户和权限管理
2024-03-04

如何使用vue-router实现动态权限控制

本篇内容介绍了“如何使用vue-router实现动态权限控制”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!使用vue开发带权限管理系统,尤其
2023-07-04

权限管理模块中动态加载Vue组件怎么实现

本篇内容介绍了“权限管理模块中动态加载Vue组件怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!登录状态保存当用户登录成功之后,需要将
2023-06-19

如何使用SpringSecurity实现动态加载权限信息

这篇文章主要介绍了如何使用SpringSecurity实现动态加载权限信息,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。①数据库中资源与角色对应关系,以及角色和用户对应关系如
2023-06-22

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录