SpringBoot&Shiro实现权限管理

您所在的位置:网站首页 springboot权限管理rbac SpringBoot&Shiro实现权限管理

SpringBoot&Shiro实现权限管理

2023-06-20 17:45| 来源: 网络整理| 查看: 265

SpringBoot&Shiro实现权限管理 引言

相信大家前来看这篇文章的时候,是有SpringBoot和Shiro基础的,所以本文只介绍整合的步骤,如果哪里写的不好,恳请大家能指出错误,谢谢!依赖以及一些配置文件请在源码里参考,请参见 https://github.com/Slags/springboot-learn/tree/master/1.springboot-shiro-authentication ,

个人博客:www.fqcoder.cn

一、数据库模板设计

在本文中,我们使用RBAC(Role-Based Access Control,基于角色的访问控制)模型设计用户,角色和权限间的关系。简单地说,一个用户拥有若干角色,每一个角色拥有若干权限。这样,就构造成“用户-角色-权限”的授权模型。在这种模型中,用户与角色之间,角色与权限之间,一般者是多对多的关系。如下图所示:

QQ截图20171214123601.png

然后我们在来根据这个模型图,设计数据库表,记得自己添加一点测试数据哦

CREATE TABLE `tb_permission` ( `id` int(11) NOT NULL AUTO_INCREMENT, `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact; CREATE TABLE `tb_role` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '角色名称', `description` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL COMMENT '描述', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact; CREATE TABLE `tb_role_permission` ( `role_id` int(11) NOT NULL COMMENT '角色id', `permission_id` int(11) NOT NULL COMMENT '权限id' ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact; CREATE TABLE `tb_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL, `create_time` datetime(0) DEFAULT NULL, `status` int(10) DEFAULT NULL COMMENT '状态', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact; CREATE TABLE `tb_user_role` ( `role_id` int(11) NOT NULL COMMENT '角色id', `user_id` int(11) NOT NULL COMMENT '用户id' ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact; 二、Pojo设计

我们创建对应的类,笔者这里用了lombok插件,记得先安装插件

@Data public class User implements Serializable { private Integer id; private String username; private String password; private Date createTime; private Integer status; } @Data public class Role implements Serializable { private Integer id; private String name; private String description; } @Data public class Permission implements Serializable { private Integer id; private String url; private String name; } 三、Dao层设计

因为我们只是做一个演示,只涉及到用户登录,用户角色、权限查找,并未实现过多方法

创建UserMapper 、RolePermissionMapper 、UserRoleMapper 三个接口

注意:记得在Mapper接口上面加一个扫描注解@Mapper或者在boot启动类上加一个@MapperScan(value = "mapper包路径")注解

public interface UserMapper { @Select("select * from tb_user where username=#{username}") User selectByName(String username); } -------------------------- public interface UserRoleMapper { /** * * 查询用户角色(可能一个用户有多个角色) * @param username * @return */ @Select("select r.id,r.name,r.description from tb_role r " + "left join tb_user_role ur on(r.id = ur.role_id)" + "left join tb_user u on(u.id=ur.user_id)" + "where u.username =#{username}") List findByUserName(String username); } ------------------------------------------------ public interface RolePermissionMapper { /** * 通过角色id查询权限 * @param roleId * @return */ @Select("select p.id,p.url,p.name from tb_permission p " + "left join tb_role_permission rp on(p.id=rp.permission_id)" + "left join tb_role r on(r.id=rp.role_id)" + "where r.id=#{roleId}") List findByRoleId(Integer roleId); } 四、Shiro整合实现思路

好了,前面的一些东西,都是可以算是准备工作,现在才是真正开始整合Shiro了,我们先来屡一下思路,实现认证权限功能主要可以归纳为3点:

1.定义一个ShiroConfig配置类,配置 SecurityManager Bean , SecurityManager为Shiro的安全管理器,管理着所有Subject;

2.在ShiroConfig中配置 ShiroFilterFactoryBean ,它是Shiro过滤器工厂类,依赖SecurityManager ;

3.自定义Realm实现类,包含 doGetAuthorizationInfo()和doGetAuthenticationInfo()方法 ,

五、定义ShiroConfig配置类 /** * @ClassName ShiroConfig * @Description TODO * @Author fqCoder * @Date 2020/2/29 3:08 * @Version 1.0 */ @Configuration public class ShiroConfig { /** * 这是shiro的大管家,相当于mybatis里的SqlSessionFactoryBean * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //登录 shiroFilterFactoryBean.setLoginUrl("/login"); //首页 shiroFilterFactoryBean.setSuccessUrl("/index"); //错误页面,认证不通过跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); //页面权限控制 shiroFilterFactoryBean.setFilterChainDefinitionMap(ShiroFilterMapFactory.shiroFilterMap()); shiroFilterFactoryBean.setSecurityManager(securityManager); return shiroFilterFactoryBean; } /** * web应用管理配置 * @param shiroRealm * @param cacheManager * @param manager * @return */ @Bean public DefaultWebSecurityManager securityManager(Realm shiroRealm, CacheManager cacheManager, RememberMeManager manager) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setCacheManager(cacheManager); securityManager.setRememberMeManager(manager);//记住Cookie securityManager.setRealm(shiroRealm); securityManager.setSessionManager(sessionManager()); return securityManager; } /** * session过期控制 * @return * @author fuce * @Date 2019年11月2日 下午12:49:49 */ @Bean public DefaultWebSessionManager sessionManager() { DefaultWebSessionManager defaultWebSessionManager=new DefaultWebSessionManager(); // 设置session过期时间3600s Long timeout=60L*1000*60;//毫秒级别 defaultWebSessionManager.setGlobalSessionTimeout(timeout); return defaultWebSessionManager; } /** * 加密算法 * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("MD5");//采用MD5 进行加密 hashedCredentialsMatcher.setHashIterations(1);//加密次数 return hashedCredentialsMatcher; } /** * 记住我的配置 * @return */ @Bean public RememberMeManager rememberMeManager() { Cookie cookie = new SimpleCookie("rememberMe"); cookie.setHttpOnly(true);//通过js脚本将无法读取到cookie信息 cookie.setMaxAge(60 * 60 * 24);//cookie保存一天 CookieRememberMeManager manager=new CookieRememberMeManager(); manager.setCookie(cookie); return manager; } /** * 缓存配置 * @return */ @Bean public CacheManager cacheManager() { MemoryConstrainedCacheManager cacheManager=new MemoryConstrainedCacheManager();//使用内存缓存 return cacheManager; } /** * 配置realm,用于认证和授权 * @param hashedCredentialsMatcher * @return */ @Bean public AuthorizingRealm shiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) { MyShiroRealm shiroRealm = new MyShiroRealm(); //校验密码用到的算法 // shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher); return shiroRealm; } /** * 启用shiro方言,这样能在页面上使用shiro标签 * @return */ @Bean public ShiroDialect shiroDialect() { return new ShiroDialect(); } /** * 启用shiro注解 *加入注解的使用,不加入这个注解不生效 */ @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } @Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } }

注意:(当时笔者遇到的一个小问题,贴出来给大家涨姿势)

注解无效,登录时不会执行验证角色和权限的方法,只会执行登录验证方法,遂查询资料,得知shiro在subject.login(token)方法时不会执行doGetAuthorizationInfo方法,只有在访问到有权限验证的接口时会调用查看权限,于是猜想注解无效,发现shiro的权限注解需要开启才能有用,添加在配置文件中加入advisorAutoProxyCreator和getAuthorizationAttributeSourceAdvisor两个bean开启shiro注解,解决问题。

六.创建ShiroFilterMapFactory类

注意:

1.这里要用LinkedHashMap 保证有序

2.filterChain基于短路机制,即最先匹配原则,

3.像anon、authc等都是Shiro为我们实现的过滤器,我给出了一张表,在文章尾附录,自行查看

/** * @ClassName ShiroFilterMapFactory * @Description TODO * @Author fqCoder * @Date 2020/2/29 3:09 * @Version 1.0 */ public class ShiroFilterMapFactory { public static Map shiroFilterMap() { // 设置路径映射,注意这里要用LinkedHashMap 保证有序 LinkedHashMap filterChainDefinitionMap = new LinkedHashMap(); //对所有用户认证 filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/logout", "logout"); //对所有页面进行认证 filterChainDefinitionMap.put("/**", "authc"); return filterChainDefinitionMap; } }

配置完了ShiroConfig后,实现自己的Realm,然后注入到SecurityManager里

七、实现自定义Realm类

自定义Realm类需要继承 AuthorizingRealm 类,实现 doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可 ,

doGetAuthorizationInfo() 方法是进行授权的方法,获取角色的权限信息

doGetAuthenticationInfo()方法是进行用户认证的方法,验证用户名和密码

/** * @ClassName MyShiroRealm * @Description TODO * @Author fqCoder * @Date 2020/2/29 3:08 * @Version 1.0 */ @Service public class MyShiroRealm extends AuthorizingRealm { @Autowired private UserMapper userMapper; @Autowired private UserRoleMapper userRoleMapper; @Autowired private RolePermissionMapper rolePermissionMapper; /** * 获取用户角色和权限 * @param principal * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { if(principal == null){ throw new AuthorizationException("principals should not be null"); } User userInfo= (User) SecurityUtils.getSubject().getPrincipal(); System.out.println("用户-->"+userInfo.getUsername()+"获取权限中"); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //用户获取角色集 List roleList=userRoleMapper.findByUserName(userInfo.getUsername()); Set roleSet=new HashSet(); for (Role r:roleList){ Integer roleId=r.getId();//获取角色id simpleAuthorizationInfo.addRole(r.getName());//添加角色名字 List permissionList=rolePermissionMapper.findByRoleId(roleId); for (Permission p:permissionList){ //添加权限 simpleAuthorizationInfo.addStringPermission(p.getName()); } } System.out.println("角色为-> " + simpleAuthorizationInfo.getRoles()); System.out.println("权限为-> " + simpleAuthorizationInfo.getStringPermissions()); return simpleAuthorizationInfo; } /** * 登录认证 * @param token * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //获取用户输入的用户名密码 String username= (String) token.getPrincipal(); String password=new String((char[])token.getCredentials()); System.out.println("用户输入--->username:"+username+"-->password:"+password); //在数据库中查询 User userInfo=userMapper.selectByName(username); if (userInfo == null) { throw new UnknownAccountException("用户名或密码错误!"); } if (!password.equals(userInfo.getPassword())) { throw new IncorrectCredentialsException("用户名或密码错误!"); } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo, // 用户名 userInfo.getPassword(), // 密码 getName() // realm name ); return authenticationInfo; } }

其中UnknownAccountException等异常为Shiro自带异常,Shiro具有丰富的运行时AuthenticationException层次结构,可以准确指出尝试失败的原因。

八、控制层设计 1.创建一个LoginController.class类

用来处理登录访问请求

/** * @ClassName LoginController * @Description TODO * @Author fqCoder * @Date 2020/2/29 6:06 * @Version 1.0 */ @Controller public class LoginController { @GetMapping("/login") public String login(){ return "login"; } @GetMapping("/") public String home(){ return "redirect:/index"; } @GetMapping("/index") public String index(Model model){ User user= (User) SecurityUtils.getSubject().getPrincipal(); model.addAttribute("user",user); return "index"; } @PostMapping("login") @ResponseBody public AjaxResult login(User user,Boolean rememberMe){ System.out.println("user = " + user); UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); //获取Subject 对象 Subject subject= SecurityUtils.getSubject(); try { if (rememberMe){ token.setRememberMe(true); } subject.login(token); return AjaxResult.success("/index"); } catch (UnknownAccountException e) { return AjaxResult.error(e.getMessage()); } catch (IncorrectCredentialsException e) { return AjaxResult.error(e.getMessage()); } } @GetMapping("/403") public String forbid(){ return "403"; } } 2.创建一个UserController.class类

用于处理User类的访问请求,并使用Shiro权限注解控制权限:

/** * @ClassName UserController * @Description TODO * @Author fqCoder * @Date 2020/3/3 15:14 * @Version 1.0 */ @RestController @RequestMapping("/user") public class UserController { @RequiresPermissions("user:queryAll") @GetMapping("/queryAll") public String queryAll(){ //只演示框架...功能不实现 return "查询列表"; } @RequiresPermissions("user:add") @GetMapping("/add") public String userAdd(){ return "添加用户"; } @RequiresPermissions("user:delete") @GetMapping("/delete") public String userDelete(){ return "删除用户"; } } 九、前端页面设计 1.编写login.html页面

这里我只贴重要代码,具体的代码,到这里找哦!

-------js代码---- $.fn.serializeObject = function () { var o = {}; var a = this.serializeArray(); $.each(a, function () { if (o[this.name]) { if (!o[this.name].push) { o[this.name] = [o[this.name]]; } o[this.name].push(this.value); } else { o[this.name] = this.value || ''; } }); return o; }; $(function () { $("#loginBut").click(function () { var arr=$('#loginForm').serializeObject(); $.ajax({ url: '/login', type: 'post', data: arr, dataType: "json", success: function (data) { if (data.code==200){ location.href=data.msg; } else { alert(data.msg); } }, error: function (data) { alert(data.msg); } }) }); });

当用户登录进来的时候调到index.html

2.编写index.html页面 首页 番茄欢迎您! 登录用户:【[[${user.username}]]】 注销 权限测试 获取用户全部信息 添加用户 删除用户 3.编写403页面

比较简单,此处能用就行

403 403权限不够 首页 十、测试&问题

启动项目:访问http://localhost:8080/,它会自动拦截,页面重定向到 http://localhost:8080/login ,登录成功跳转到http://localhost:8080/index

问题:

登录测试用户的时候,访问没有权限的链接请求时,后台抛出org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method异常

当时以为在ShiroConfig配置类中配置了shiroFilterFactoryBean.setUnauthorizedUrl("/403");

没有权限的请求会自动从定向到/403,然后却是抛出了异常,后来在一篇文章中看到了,说这个设置只对filterChain起作用 ,针对这个问题,我们可以定义一个全局异常捕获类:

@ControllerAdvice @Order(value = Ordered.HIGHEST_PRECEDENCE) public class GlobalExceptionHandler { @ExceptionHandler(value = AuthorizationException.class) public String handleAuthorizationException() { return "403"; } }

然后再启动项目,登录测试账号,访问没有权限的请求,页面成功定向到/403

源码链接: https://github.com/Slags/springboot-learn/tree/master/1.springboot-shiro-authentication

至此,笔者刚开始写,不是写的很好,欢迎各位网友踊跃指出不足,谢谢!

附录: 1.Shiro拦截机制表 Filter Name Class Description anon org.apache.shiro.web.filter.authc.AnonymousFilter 匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例/static/**=anon authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter 基于表单的拦截器;如/**=authc,如果没有登录会跳到相应的登录页面登录 authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter Basic HTTP身份验证拦截器 logout org.apache.shiro.web.filter.authc.LogoutFilter 退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/),示例/logout=logout noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter 不创建会话拦截器,调用subject.getSession(false)不会有什么问题,但是如果subject.getSession(true)将抛出DisabledSessionException异常 perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例/user/**=perms["user:create"] port org.apache.shiro.web.filter.authz.PortFilter 端口拦截器,主要属性port(80):可以通过的端口;示例/test= port[80],如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样 rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter rest风格拦截器,自动根据请求方法构建权限字符串;示例/users=rest[user],会自动拼出user:read,user:create,user:update,user:delete权限字符串进行权限匹配(所有都得匹配,isPermittedAll) roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 角色授权拦截器,验证用户是否拥有所有角色;示例/admin/**=roles[admin] ssl org.apache.shiro.web.filter.authz.SslFilter SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口443;其他和port拦截器一样; user org.apache.shiro.web.filter.authc.UserFilter 用户拦截器,用户已经身份验证/记住我登录的都可;示例/**=user


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3