β

从零搭建自己的SpringBoot后台框架(十四)

Harries Blog™ 266 阅读

Hello大家好,本章我们添加shiro权限保护接口功能 。有问题可以联系我mr_ bean y@163.com。另求各路大神指点,感谢

一:什么是shiro

Shiro是一个Java平台的 开源 权限框架,用于 认证 和访问授权。具体来说,满足对如下元素的支持:

  • 用户,角色,权限(仅仅是操作权限, 数据 权限必须与业务 需求 紧密结合),资源(url)。
  • 用户分配角色,角色定义权限。
  • 访问授权时支持角色或者权限,并且支持多级的权限定义。

简单来说shiro是通过自己的 配置 ,来保证接口只能被指定的角色或权限访问,保证了接口的 安全

二:添加shiro依赖

<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring-boot-web-starter</artifactId>
   <version>1.4.0-RC2</version>
</dependency>

三:创建权限,角色,用户角色权限关系表等 数据库

1:修改原有 userInfo

添加 password(用户密码,经过加密之后的),salt(加密盐值)

修改之后结构为下图

从零搭建自己的SpringBoot后台框架(十四)

其中 password 数据为123456加密之后生成的

2:添加角色表

CREATE TABLE `sys_role` (
  `id` varchar(36) NOT NULL COMMENT '角色名称',
  `role_name` varchar(255) DEFAULT NULL COMMENT '角色名称,用于显示',
  `role_desc` varchar(255) DEFAULT NULL COMMENT '角色描述',
  `role_value` varchar(255) DEFAULT NULL COMMENT '角色值,用于权限判断',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_disable` int(1) DEFAULT NULL COMMENT '是否禁用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色表';

3:添加用户角色关系表

CREATE TABLE `user_role` (
  `id` varchar(36) NOT NULL,
  `user_id` varchar(36) DEFAULT NULL COMMENT '用户ID',
  `role_id` varchar(36) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户角色关系表';

4:添加权限表

CREATE TABLE `sys_perm` (
  `id` varchar(36) NOT NULL,
  `perm_name` varchar(255) DEFAULT NULL COMMENT '权限名称',
  `perm_desc` varchar(255) DEFAULT NULL COMMENT '权限描述',
  `perm_value` varchar(255) DEFAULT NULL COMMENT '权限值',
  `create_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
  `is_disable` int(1) DEFAULT NULL COMMENT '是否禁用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

5:添加角色权限表

CREATE TABLE `role_perm` (
  `id` varchar(36) NOT NULL,
  `perm_id` varchar(32) DEFAULT NULL COMMENT '权限id',
  `role_id` varchar(32) DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色权限表';

表关系大致为下图,一个用户对应多个角色,一个角色对用多个权限

原谅我辣鸡的画图

从零搭建自己的SpringBoot后台框架(十四)

四:利用 代码生成器 生成四张新表的 mapper ,dao,service和controller

大家直接生成就可以,由于 代码 过多,这里不做展示,详情可以去码 上参考

五:添加查询角色和权限的方法

UserRoleMapper.xml

<!--根据用户id查询该用户所有角色-->
<select id="getRolesByUserId" resultType="string" parameterType="string">
  select sr.role_value
  from user_role ur
  left join sys_role sr on ur.role_id = sr.id
  where ur.user_id = #{userId,jdbcType=VARCHAR}
  and sr.is_disable = 0
</select>

UserRoleMapper. java

List<String> getRolesByUserId(String userId);

RolePermMapper.xml

<select id="getPermsByUserId" resultType="string" parameterType="string">
  select distinct
      p.perm_value
  from
      sys_perm p,
      role_perm rp,
      user_role ur
  where
      p.id = rp.perm_id
      and ur.role_id = rp.role_id
      and ur.user_id = #{userId,jdbcType=VARCHAR}
      and p.is_disable = 0
</select>

说明,这里查询出该用户对应的所有权限,由于角色与权限是多对多的关系,所以查询出的用户的权限可能会有重复,需要 distinct 去重。

RolePermMapper.java

List<String> getPermsByUserId(String userId);

修改userInfo实体类为如下

package com.example.demo.model;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Transient;
import java.util.HashSet;
import java.util.Set;

/**
 * @author 张瑶
 * @Description:
 * @time 2018/4/18 11:55
 */
public class UserInfo {

    /**
     * 主键
     */
    @Id
    private String id;

    /**
     * 用户名
     */
    @Column(name = "user_name")
    private String userName;

    private String password;

    /**
     * 加密盐值
     */
    private String salt;

    /**
     * 用户所有角色值,用于shiro做角色权限的判断
     */
    @Transient
    private Set<String> roles;

    /**
     * 用户所有权限值,用于shiro做资源权限的判断
     */
    @Transient
    private Set<String> perms;



    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Set<String> getRoles() {
        return roles;
    }

    public void setRoles(Set<String> roles) {
        this.roles = roles;
    }

    public Set<String> getPerms() {
        return perms;
    }

    public void setPerms(Set<String> perms) {
        this.perms = perms;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}

六:自定义Realm

创建 core →shiro→CustomRealm.java

1:登录认证实现

在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.

Shiro的认证过程最终会交由Realm执行,这时会调用Realm的 getAuthenti cat ionInfo( token ) 方法。

该方法主要执行以下操作:

AuthenticationInfo
AuthenticationException

而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。

2:链接权限的实现

重载doGet Authorization Info(),来定义如何获取用户的角色和权限的逻辑,给shiro做权限判断

完整代码如下

package com.example.demo.core.shiro;


import com.example.demo.model.UserInfo;
import com.example.demo.service.RolePermService;
import com.example.demo.service.UserInfoService;
import com.example.demo.service.UserRoleService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 自定义如何查询用户信息,如何查询用户的角色和权限,如何校验密码等逻辑
 */
public class CustomRealm extends AuthorizingRealm {

    @Autowired
    private UserInfoService userService;
    @Autowired
    private UserRoleService userRoleService;
    @Autowired
    private RolePermService rolePermService;

    /**
     * 告诉shiro如何根据获取到的用户信息中的密码和盐值来校验密码
     */
    {
        //设置用于匹配密码的CredentialsMatcher
        HashedCredentialsMatcher hashMatcher = new HashedCredentialsMatcher();
        hashMatcher.setHashAlgorithmName("md5");
        hashMatcher.setStoredCredentialsHexEncoded(true);
        //加密的次数
        hashMatcher.setHashIterations(1024);
        this.setCredentialsMatcher(hashMatcher);
    }


    /**
     *  定义如何获取用户的角色和权限的逻辑,给shiro做权限判断
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }
        UserInfo user = (UserInfo) getAvailablePrincipal(principals);
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(user.getRoles());
        info.setStringPermissions(user.getPerms());
        return info;
    }

    /**
     * 定义如何获取用户信息的业务逻辑,给shiro做登录
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }
        UserInfo userDB = userService.selectBy("userName",username);
        if (userDB == null) {
            throw new UnknownAccountException("No account found for admin [" + username + "]");
        }
        //查询用户的角色和权限存到SimpleAuthenticationInfo中,这样在其它地方
        //SecurityUtils.getSubject().getPrincipal()就能拿出用户的所有信息,包括角色和权限
        List<String> roleList = userRoleService.getRolesByUserId(userDB.getId());
        List<String> permList = rolePermService.getPermsByUserId(userDB.getId());
        Set<String> roles = new HashSet(roleList);
        Set<String> perms = new HashSet(permList);
        userDB.setRoles(roles);
        userDB.setPerms(perms);

        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userDB, userDB.getPassword(), getName());
        info.setCredentialsSalt(ByteSource.Util.bytes(userDB.getSalt()));
        return info;

    }

}

七:添加shiro配置

创建core→configurer→ShiroConfigurer

package com.example.demo.core.configurer;

import com.example.demo.core.shiro.CustomRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
public class ShiroConfigurer {

    /**
     * 注入自定义的realm,告诉shiro如何获取用户信息来做登录或权限控制
     */
    @Bean
    public Realm realm() {
        return new CustomRealm();
    }

    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        /**
         * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
         * 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。
         * 加入这项配置能解决这个bug
         */
        creator.setUsePrefix(true);
        return creator;
    }

    /**
     * 这里统一做鉴权,即判断哪些请求路径需要用户登录,哪些请求路径不需要用户登录
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
        chain.addPathDefinition( "/userInfo/selectById", "authc, roles[admin]");
        chain.addPathDefinition( "/logout", "anon");
        chain.addPathDefinition( "/userInfo/selectAll", "anon");
        chain.addPathDefinition( "/userInfo/login", "anon");
        chain.addPathDefinition( "/**", "authc");
        return chain;
    }
}

shiro提供和多个默认的过滤器,我们可以用这些过滤器来配置控制指定url的权限:

配置缩写 对应的过滤器 功能
anon AnonymousFilter 指定url可以匿名访问
authc FormAuthenticationFilter 指定url需要form表单登录,默认会从请求中获取 username password , rememberMe 参数 并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以 定制
authcBasic BasicHttpAuthenticationFilter 指定url需要basic登录
logout LogoutFilter 登出过滤器,配置指定url就可以实现退出功能,非常方便
noSessionCreation NoSessionCreationFilter 禁止创建会话
perms PermissionsAuthorizationFilter 需要指定权限才能访问
port PortFilter 需要指定 端口 才能访问
rest HttpMethodPermissionFilter http 请求方法转化成相应的动词来构造一个权限字符串
roles RolesAuthorizationFilter 需要指定角色才能访问
ssl SslFilter 需要 https 请求才能访问
user UserFilter 需要已登录或“记住我”的用户才能访问

八:在UserInfoController中添加登录方法

@PostMapping("/login")
public RetResult<UserInfo> login(String userName, String password) {
    Subject currentUser = SecurityUtils.getSubject();
    //登录
    try {
        currentUser.login(new UsernamePasswordToken(userName, password));
    }catch (IncorrectCredentialsException i){
        throw new ServiceException("密码输入错误");
    }
    //从session取出用户信息
    UserInfo user = (UserInfo) currentUser.getPrincipal();
    return RetResponse.makeOKRsp(user);
}

九:数据库添加权限数据

INSERT INTO `sys_role` VALUES ('1', '财务', '负责发工资', 'cw', '2018-05-26 00:37:52', null, '0');
INSERT INTO `sys_role` VALUES ('2', '人事', '负责员工', 'rs', '2018-05-26 00:38:18', null, '0');
INSERT INTO `user_role` VALUES ('1', '1', '1');
INSERT INTO `user_role` VALUES ('2', '1', '2');
INSERT INTO `sys_perm` VALUES ('1', '创建', '创建权限', 'create', '2018-05-26 00:39:16', null, '0');
INSERT INTO `sys_perm` VALUES ('2', '删除', '删除权限', 'delete', '2018-05-26 00:39:39', null, '0');
INSERT INTO `sys_perm` VALUES ('3', '修改', '修改权限', 'update', '2018-05-26 00:39:58', null, '0');
INSERT INTO `sys_perm` VALUES ('4', '查询', '查询权限', 'select', '2018-05-26 00:40:16', null, '0');
INSERT INTO `role_perm` VALUES ('1', '1', '1');
INSERT INTO `role_perm` VALUES ('2', '2', '1');
INSERT INTO `role_perm` VALUES ('3', '1', '2');
INSERT INTO `role_perm` VALUES ('4', '2', '2');
INSERT INTO `role_perm` VALUES ('5', '3', '2');
INSERT INTO `role_perm` VALUES ('6', '4', '2');

十:添加shiroUtilsController

package com.example.demo.controller;

import com.example.demo.core.ret.ServiceException;
import com.example.demo.model.UserInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


@RestController
@RequestMapping("shiroUtils")
public class ShiroUtilsController {

    @GetMapping("/noLogin")
    public void noLogin() {
        throw new UnauthenticatedException();
    }

    @GetMapping("/noAuthorize")
    public void noAuthorize() {
        throw new UnauthorizedException();
    }


    @PostMapping("/getNowUser")
    public UserInfo getNowUser() {
        UserInfo u = (UserInfo) SecurityUtils.getSubject().getPrincipal();
        return u;
    }

}

十一:添加错误异常码

package com.example.demo.core.ret;

/**
 * @Description: 响应码枚举,参考HTTP状态码的语义
 * @author 张瑶
 * @date 2018/4/19 09:42
 */
public enum RetCode {

   // 成功
   SUCCESS(200),

   // 失败
   FAIL(400),

   // 未认证(签名错误)
   UNAUTHORIZED(401),

   /** 未登录 */
   UNAUTHEN(4401),

   /** 未授权,拒绝访问 */
   UNAUTHZ(4403),

   // 服务器内部错误
   INTERNAL_SERVER_ERROR(500);

   public int code;

   RetCode(int code) {
      this.code = code;
   }
}

十二:添加异常拦截

@Override
	public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		exceptionResolvers.add(new HandlerExceptionResolver() {
			@Override
			public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
				RetResult<Object> result = new RetResult<Object>();
				// 业务失败的异常,如“账号或密码错误”
				if (e instanceof ServiceException) {
					result.setCode(RetCode.FAIL).setMsg(e.getMessage()).setData(null);
					LOGGER.info(e.getMessage());
				} else if (e instanceof NoHandlerFoundException) {
					result.setCode(RetCode.NOT_FOUND).setMsg("接口 [" + request.getRequestURI() + "] 不存在");
				} else if (e instanceof UnauthorizedException) {
					result.setCode(RetCode.UNAUTHEN).setMsg("用户没有访问权限").setData(null);
				}else if (e instanceof UnauthenticatedException) {
					result.setCode(RetCode.UNAUTHEN).setMsg("用户未登录").setData(null);
				}else if (e instanceof ServletException) {
					result.setCode(RetCode.FAIL).setMsg(e.getMessage());
				} else {
					result.setCode(RetCode.INTERNAL_SERVER_ERROR).setMsg("接口 [" + request.getRequestURI() + "] 内部错误,请联系管理员");
					String message;
					if (handler instanceof HandlerMethod) {
						HandlerMethod handlerMethod = (HandlerMethod) handler;
						message = String.format("接口 [%s] 出现异常,方法:%s.%s,异常摘要:%s", request.getRequestURI(), handlerMethod.getBean().getClass().getName(), handlerMethod.getMethod()
								.getName(), e.getMessage());
					} else {
						message = e.getMessage();
					}
					LOGGER.error(message, e);
				}
				responseResult(response, result);
				return new ModelAndView();
			}
		});
	}

十三:添加shiro路径配置

application.properties 中添加

#shiro配置
#用户未登录
shiro.loginUrl=/shiroUtils/noLogin
#用户没有权限
shiro.unauthorizedUrl=/shiroUtils/noAuthorize

十四: 测试

输入localhost:8080/userInfo/selectAll

我们可以看到可以拿到数据

从零搭建自己的SpringBoot后台框架(十四)

输入localhost:8080/userInfo/selectById

从零搭建自己的SpringBoot后台框架(十四)

然后登陆

从零搭建自己的SpringBoot后台框架(十四)

再次访问localhost:8080/userInfo/selectById,我们可以看到shiro已经生效

从零搭建自己的SpringBoot后台框架(十四)

修改用户访问权限,重新启动,重新登陆

chain.addPathDefinition( "/userInfo/selectById", "authc, roles[cw]");

再次访问localhost:8080/userInfo/selectById

从零搭建自己的SpringBoot后台框架(十四)

十五:优化

优化一 :权限不可能一直不变,当我们需要修改权限时,我们需要手动修改shiro配置文件,显然是不合理的,最好的办法是把权限存储在数据库中,修改时修改数据库

1:创建url资源表并添加数据

CREATE TABLE `sys_permission_init` (
  `id` varchar(255) NOT NULL,
  `url` varchar(255) DEFAULT NULL COMMENT '程序对应url地址',
  `permission_init` varchar(255) DEFAULT NULL COMMENT '对应shiro权限',
  `sort` int(100) DEFAULT NULL COMMENT '排序',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `sys_permission_init` VALUES ('1', '/userInfo/login', 'anon', '1');
INSERT INTO `sys_permission_init` VALUES ('2', '/userInfo/selectAll', 'anon', '2');
INSERT INTO `sys_permission_init` VALUES ('3', '/logout', 'anon', '3');
INSERT INTO `sys_permission_init` VALUES ('4', '/**', 'authc', '0');
INSERT INTO `sys_permission_init` VALUES ('5', '/userInfo/selectAlla', 'authc, roles[admin]', '6');
INSERT INTO `sys_permission_init` VALUES ('6', '/sysPermissionInit/aaa', 'anon', '5');

这里我们需要让 '/**' 始终在最后,所以需要添加 sort 进行排序

2:根据工具生成 map per,dao,service,controller并添加我们需要的查询方法

SysPermissionInitMapper.xml

<sql id="Base_Column_List">
  id, url, permission_init, sort
</sql>

<select id="selectAllOrderBySort" resultMap="BaseResultMap">
  SELECT
  <include refid="Base_Column_List"/>
  from sys_permission_init
  order by sort desc
</select>

SysPermissionInitMapper.java

List<SysPermissionInit> selectAllOrderBySort();

3:修改 ShiroConfigurer.java

修改后如下

package com.example.demo.core.configurer;

import com.example.demo.core.shiro.CustomRealm;
import com.example.demo.model.SysPermissionInit;
import com.example.demo.service.SysPermissionInitService;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class ShiroConfigurer {

    @Resource
    private SysPermissionInitService sysPermissionInitService;

    /**
     * 注入自定义的realm,告诉shiro如何获取用户信息来做登录或权限控制
     */
    @Bean
    public Realm realm() {
        return new CustomRealm();
    }

    @Bean
    public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        /**
         * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
         * 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。
         * 加入这项配置能解决这个bug
         */
        creator.setUsePrefix(true);
        return creator;
    }

    /**
     * 这里统一做鉴权,即判断哪些请求路径需要用户登录,哪些请求路径不需要用户登录
     * @return
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition();
        List<SysPermissionInit> list = sysPermissionInitService.selectAllOrderBySort();
        for(int i = 0,length = list.size();i<length;i++){
            SysPermissionInit sysPermissionInit = list.get(i);
            chain.addPathDefinition(sysPermissionInit.getUrl(), sysPermissionInit.getPermissionInit());
        }
        return chain;
    }
}

4:测试

输入localhost:8080/userInfo/selectById

从零搭建自己的SpringBoot后台框架(十四)

然后登陆

从零搭建自己的SpringBoot后台框架(十四)

再次访问localhost:8080/userInfo/selectById,我们可以看到shiro已经生效

从零搭建自己的SpringBoot后台框架(十四)

修改数据库用户访问权限,重新启动,重新登陆

UPDATE sys_permission_init
SET permission_init = 'authc, roles[cw]'
WHERE
	id = 5

再次访问localhost:8080/userInfo/selectById

从零搭建自己的SpringBoot后台框架(十四)

优化二 :每次当我们修改权限信息时,都需要重启服务器,这显然也是不合理的

1:创建shiroService

package com.example.demo.service;

import java.util.Map;

/**
 * shiro 动态更新权限
 */
public interface ShiroService {

    Map<String, String> loadFilterChainDefinitions();

    /**
     * 动态修改权限
     */
    void updatePermission();
}

2:创建shiroServiceImpl

package com.example.demo.service.impl;

import com.example.demo.model.SysPermissionInit;
import com.example.demo.service.ShiroService;
import com.example.demo.service.SysPermissionInitService;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class ShiroServiceImpl implements ShiroService {

    @Autowired
    ShiroFilterFactoryBean shiroFilterFactoryBean;

    @Autowired
    SysPermissionInitService sysPermissionInitService;

    /**
     * 初始化权限
     */
    @Override
    public Map<String, String> loadFilterChainDefinitions() {
        // 权限控制map.从数据库获取
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        List<SysPermissionInit> list = sysPermissionInitService.selectAllOrderBySort();
        for (SysPermissionInit sysPermissionInit : list) {
            filterChainDefinitionMap.put(sysPermissionInit.getUrl(),
                    sysPermissionInit.getPermissionInit());
        }
        return filterChainDefinitionMap;
    }


    /**
     * 重新加载权限
     */
    @Override
    public void updatePermission() {
        synchronized (shiroFilterFactoryBean) {
            AbstractShiroFilter shiroFilter = null;
            try {
                shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean
                        .getObject();
            } catch (Exception e) {
                throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!");
            }
            PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter
                    .getFilterChainResolver();
            DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver
                    .getFilterChainManager();
            // 清空老的权限控制
            manager.getFilterChains().clear();
            shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
            shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitions());
            // 重新构建生成
            Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();
            for (Map.Entry<String, String> entry : chains.entrySet()) {
                String url = entry.getKey();
                String chainDefinition = entry.getValue().trim().replace(" ", "");
                manager.createChain(url, chainDefinition);
            }
            System.out.println("更新权限成功!!");
        }
    }
}

3:修改shiroUtilsController

添加如下代码

/**
 * @Description: 重新加载shiro权限
 * @throws Exception
 */
@PostMapping("/updatePermission")
public void updatePermission() throws Exception {
    shiroService.updatePermission();
}

4:测试

输入localhost:8080/userInfo/selectById

从零搭建自己的SpringBoot后台框架(十四)

修改权限

UPDATE sys_permission_init
SET permission_init = 'authc, roles[cw1]'
WHERE
id = 5

输入localhost:8080/shiroUtils/updatePermission  重新加载权限

注意:此刻我们并没有修改程序任何一个地方,也没有重启服务器

输入localhost:8080/userInfo/selectById

从零搭建自己的SpringBoot后台框架(十四)

成功!

项目地址

git ee.com/beany/mySpr…

文章 不易,如对您有帮助,请帮忙点下star 从零搭建自己的SpringBoot后台框架(十四)

结尾

添加shiro权限保护接口功能已完成,后续功能接下来陆续更新,有问题可以联系我mr_beany@163.com。另求各路大神指点,感谢大家。

原文

https://juejin.im/post/5b08af10f265da0dd71690f5

本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。 PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处: Harries Blog™ » 从零搭建自己的SpringBoot后台框架(十四)

作者:Harries Blog™
追心中的海,逐世界的梦
原文地址:从零搭建自己的SpringBoot后台框架(十四), 感谢原作者分享。

发表评论