100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > SpringBoot整合Shiro实现权限管理

SpringBoot整合Shiro实现权限管理

时间:2020-06-19 18:33:07

相关推荐

SpringBoot整合Shiro实现权限管理

超详细的Java知识点路线图

目录

概述RBAC权限管理Shiro介绍Shiro入门自定义Realm密码加密加盐SpringBoot+MyBatis+Shiro整合RememberMe结束

概述

系统安全性是目前大大小小软件项目都会考虑的问题,这里我们来认识认识Apache的Shiro框架,了解如何用它来实现权限管理。

RBAC权限管理

问题:在企业OA系统中,部门主管和普通员工在系统中看到的菜单和操作的功能应该一样吗?

在企业中员工的职责不同,登录软件系统后不同的用户对于数据的操作和查询权限也肯定不应该相同,否则对于企业来说,内部管理和安全性都会出现问题。

RBAC(Role Based Access Control )权限管理,基于角色的访问控制。

主要对象:

1)用户

2)角色

3)权限

不同的用户登录系统后,拥有不同的角色,不同的角色可以对系统资源执行不同的操作。

用户和角色是多对多关系,角色和权限是多对多关系。

Shiro介绍

Apache的Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

主要API

SecurityManager 安全管理器,完成核心业务

Subject 提供方法给开发者调用

Realm 提供用户登录和授权的数据

SecurityUtils 工具类,用于整合其他的组件

Shiro入门

1、 添加依赖

<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-all</artifactId><version>1.4.0</version></dependency>

2、在resources下添加shiro.ini文件,内容如下

[users]zhang=123,role1,role2wang=123,role2[roles]role1=user:create,user:updaterole2=user:create,user:deleterole3=user:create

shiro.ini是Shiro的配置文件,[users]是用户配置,格式是:账号=密码,角色1,角色2…

[roles]是权限配置,格式是:角色=权限1,权限2…

3、测试

//创建基于Ini文件的安全管理器工厂IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");//获得安全管理器SecurityManager instance = factory.getInstance();//配置安全管理器SecurityUtils.setSecurityManager(instance);//获得Subject对象Subject subject = SecurityUtils.getSubject();//创建账号密码tokenUsernamePasswordToken user = new UsernamePasswordToken("wang", "123");//登录验证subject.login(user);//权限判断System.out.println("是否登录成功:" + subject.isAuthenticated());System.out.println("是否拥有role1角色:" + subject.hasRole("role1"));System.out.println("是否拥有delete权限:" + subject.isPermitted("user:delete"));

使用wang登录的效果:

使用zhang登录的效果:

密码错误会抛出异常:

自定义Realm

上面的案例中使用ini文件配置用户、密码、角色和权限等,过于简单,不适合企业级项目的使用,真正项目中用户、角色、权限这些重要数据都保存在数据库中,需要我们通过自己编写的类和方法实现登录和授权。

AuthorizingRealm类实现Realm接口,提供两个方法:

doGetAuthenticationInfo 返回登录验证信息,该方法在subject执行login方法后调用doGetAuthorizationInfo 返回用户授权信息,该方法在subject进行权限判断时调用

1) 定义Realm类,此案例为简单起见,账号密码是固定的zhang,123,添加角色role1,添加权限user:select、user:insert、user:delete

/*** 用户Realm*/public class MyRealm extends AuthorizingRealm {@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//获得登录用户String username = (String) principalCollection.getPrimaryPrincipal();System.out.println("登录用户授权:" + username);//授权信息SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();//添加角色info.addRole("role1");//添加权限info.addStringPermission("user:select");info.addStringPermission("user:insert");info.addStringPermission("user:delete");return info;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//获得用户输入的账号String username = authenticationToken.getPrincipal().toString();if(!username.equals("zhang")){throw new UnknownAccountException("此用户不存在");}//返回验证信息,参数:1、用户名 2、正确密码 3、realm名称return new SimpleAuthenticationInfo(username,"123", getName());}}

2) 使用自定义Realm进行登录和授权

//创建默认安全管理器DefaultSecurityManager securityManager = new DefaultSecurityManager();//安全管理器配置自定义RealmsecurityManager.setRealm(new MyRealm());//SecurityUtils配置安全管理器SecurityUtils.setSecurityManager(securityManager);//获得Subject对象Subject subject = SecurityUtils.getSubject();//创建账号密码tokenUsernamePasswordToken user = new UsernamePasswordToken("zhang", "123");//登录验证subject.login(user);//权限判断System.out.println("是否拥有role1角色:" + subject.hasRole("role1"));System.out.println("是否拥有delete权限:" + subject.isPermitted("user:delete"));

密码加密加盐

用户密码一般不会以明文方式保存,这样无法保证安全性,所以一般都需要加密。

SimpleHash类可以实现基本的加密,几种创建方式:

new SimpleHash("加密算法","原始密码")new SimpleHash("加密算法","原始密码",盐)new SimpleHash("加密算法","原始密码",盐,迭代次数)

参数说明:

加密算法一般使用常用的md5算法

盐的作用是提高密码安全性,如两个用户的原始密码都是123,则加密后的密文都是相同的,如果破解了一个用户的密码,另一个用户的密码也一同破解了,如果给密码加盐,每个用户的盐不同,加密后密码就都会不同,增加了破解难度。

迭代次数是加密一次后,再对密文再次加密,也能提高安全性。

下面我们以md5算法对“123456”加密,盐是“007”,迭代次数为10。

SimpleHash md5 = new SimpleHash("md5", "123456",ByteSource.Util.bytes("007"), 10);System.out.println(md5);输出:44202d045439dc33a2e43d2828d08e19

修改MyRealm的doGetAuthenticationInfo方法,这里将密文和盐直接写在代码中,实际应用时密文和盐是通过用户名从数据库中查询出来的。

//返回验证信息,参数:1、用户名 2、正确密码 3、盐 4、realm名称return new SimpleAuthenticationInfo(username,"44202d045439dc33a2e43d2828d08e19", ByteSource.Util.bytes("007"),getName());

需要给自定义Realm添加密码匹配器

//创建默认安全管理器DefaultSecurityManager securityManager = new DefaultSecurityManager();//安全管理器配置自定义RealmMyRealm realm = new MyRealm();//创建密码匹配器HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");//设置迭代次数md5.setHashIterations(10);//配置匹配器realm.setCredentialsMatcher(md5);securityManager.setRealm(realm);//SecurityUtils配置安全管理器SecurityUtils.setSecurityManager(securityManager);//获得Subject对象Subject subject = SecurityUtils.getSubject();//创建账号密码tokenUsernamePasswordToken user = new UsernamePasswordToken("zhang", "123456");//登录验证subject.login(user);//权限判断System.out.println("是否登录成功:" + subject.isAuthenticated());System.out.println("是否拥有role1角色:" + subject.hasRole("role1"));System.out.println("是否拥有delete权限:" + subject.isPermitted("user:delete"));

SpringBoot+MyBatis+Shiro整合

1、表设计

s_user 用户表

s_role 角色表

s_menu 菜单表(权限表)

s_user_menu 用户角色中间表

s_role_menu 角色菜单中间表

2、添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.3.2</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.7.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency>

2、SpringBoot配置

# jdbc配置spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/erp_db?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8spring.datasource.username=rootspring.datasource.password=123456# mybatis配置mybatis-plus.type-aliases-package=com.blb.blb_erp.entitymybatis-plus.mapper-locations=classpath:mapper/*.xml# shiro配置# 登录页面shiro.loginUrl=/pages/login.html# 登录失败跳转页面shiro.unauthorizedUrl=/pages/failed.html# 登录成功跳转页面shiro.successUrl=/pages/index.html

3、编写Mapper接口

需要三个方法:

按用户名查找用户按用户id查询所有菜单按用户id查询所有角色

/*** 用户接口*/public interface SUserMapper extends BaseMapper<SMenu>{/*** 通过用户名查询用户* @param username* @return*/SUser selectUserByUsername(String username);}映射文件:<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.blb.blb_erp.mapper.SUserMapper"><select id="selectUserByUsername" resultType="SUser">select * from s_user where user_name = #{username}</select></mapper>

/*** 菜单接口*/public interface SMenuMapper extends BaseMapper<SMenu>{/*** 根据userId查询所有权限* @param userId* @return*/List<SMenu> selectMenusByUserId(String userId);}映射文件:<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.blb.blb_erp.mapper.SMenuMapper"><select id="selectMenusByUserId" resultType="SMenu">select m.* from s_user u,s_role r,s_user_role ur,s_menu m,s_role_menu rmwhere ur.role_id = r.id and ur.user_id = u.id and rm.role_id = r.id and rm.menu_id = m.idand u.id = #{userId}</select></mapper>

/*** 角色接口*/public interface SRoleMapper extends BaseMapper<SMenu>{/*** 根据用户id查询所有角色* @param userId* @return*/List<SRole> selectRolesByUserId(String userId);}映射文件:<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.blb.blb_erp.mapper.SRoleMapper"><select id="selectRolesByUserId" resultType="SRole">select r.* from s_user_role urjoin s_user u on ur.user_id = u.idjoin s_role r on ur.role_id = r.idwhere ur.user_id = #{userId}</select></mapper>

4、自定义Realm

/*** 用户Realm*/public class UserRealm extends AuthorizingRealm {@Autowiredprivate SUserMapper userMapper;@Autowiredprivate SRoleMapper roleMapper;@Autowiredprivate SMenuMapper menuMapper;@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {//获得用户对象SUser user = (SUser) principalCollection.getPrimaryPrincipal();//查询权限和角色List<SMenu> menus = menuMapper.selectMenusByUserId(user.getId());List<SRole> roles = roleMapper.selectRolesByUserId(user.getId());//保存权限和角色名称的集合List<String> strRoles = new ArrayList<>();roles.forEach(r -> strRoles.add(r.getRoleName()));List<String> strMenus = new ArrayList<>();menus.forEach(m -> strMenus.add(m.getMenuName()));//返回带有角色和权限名称的授权信息SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addRoles(strRoles);info.addStringPermissions(strMenus);return info;}@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {//获得账号String username = authenticationToken.getPrincipal().toString();//通过账号查询用户SUser user = userMapper.selectUserByUsername(username);if(user == null){throw new UnknownAccountException("此用户不存在");}//返回验证信息 参数:1、用户对象 2、正确密码 3、盐 4、realm名称return new SimpleAuthenticationInfo(user,user.getPassWord(), ByteSource.Util.bytes(user.getSalt()),getName());}}

5、Shiro配置类

/*** Shiro配置*/@Configurationpublic class ShiroConfig {//返回Realm@Beanpublic UserRealm myRealm(){UserRealm myRealm = new UserRealm();//设置密码匹配器HashedCredentialsMatcher md5 = new HashedCredentialsMatcher("md5");md5.setHashIterations(10);myRealm.setCredentialsMatcher(md5);//关闭缓存myRealm.setCachingEnabled(false);return myRealm;}//返回面向Web开发的安全管理器@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(){DefaultWebSecurityManager sm = new DefaultWebSecurityManager();//设置自定义Realmsm.setRealm(myRealm());return sm;}//返回Shiro过滤器链定义@Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition() {DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();//定义过滤器链key为url,value为anon不验证,authc验证//anon在前,authc在后,需要使用LinkedHashMap保留顺序LinkedHashMap<String, String> map = new LinkedHashMap<>();map.put("/pages/login.html","anon");map.put("/user/login","anon");map.put("/**","authc");chainDefinition.addPathDefinitions(map);return chainDefinition;}//启动thymeleaf的shiro标签@Beanpublic ShiroDialect shiroDialect(){return new ShiroDialect();}}

6、启动类

@MapperScan("com.blb.blb_erp.mapper")@SpringBootApplicationpublic class BlbErpApplication {public static void main(String[] args) {SpringApplication.run(BlbErpApplication.class, args);}}

7、控制器

@Data@AllArgsConstructor@NoArgsConstructorpublic class JsonResult {private int code;private Object data;}

@RestController@RequestMapping("/user")public class UserController {@PostMapping("/login")public JsonResult login(String username,String password){//创建TokenUsernamePasswordToken token = new UsernamePasswordToken(username, password);//获得subjectSubject subject = SecurityUtils.getSubject();try {subject.login(token);return new JsonResult(1,"登录成功");}catch (AuthenticationException ex){ex.printStackTrace();}return new JsonResult(0,"账号或密码错误");}@RequiresRoles("管理员")@GetMapping("/role-admin")public String testRole(){return "有管理员角色";}@RequiresPermissions("部门管理")@GetMapping("/menu-dept")public String testMenu(){return "有部门管理权限";}}

@RequiresRoles、@RequiresPermissions写在控制器的方法上,登录用户有对应的角色和权限才能访问。

RememberMe

可以在登录页面上添加记住我功能,勾选后下次不用登录直接进去系统了

1、页面上添加RememberMe复选框

<!DOCTYPE html><html lang="en" xmlns:th=""><head><meta charset="UTF-8"><title>登录</title><link rel="stylesheet" href="/elementui/index.css"><style>.box-card{margin:200px auto;width: 480px;}.clearfix{text-align: center;color:#303133;font-size: 18px;}.login-form{width: 400px;}</style></head><body><div id="app"><el-card class="box-card"><div slot="header" class="clearfix"><span>系统登录</span></div><el-form class="login-form" ref="form" :model="form" label-width="80px"><el-form-item label="账号"><el-input v-model="form.username"></el-input></el-form-item><el-form-item label="密码"><el-input type="password" v-model="form.password"></el-input></el-form-item><el-form-item ><el-checkbox v-model="form.rememberMe">记住我</el-checkbox></el-form-item><el-form-item><el-button type="primary" @click="login">登 录</el-button><el-button>取 消</el-button></el-form-item></el-form></el-card></div><script src="/vue/vue.js"></script><script src="/elementui/index.js"></script><script src="/axios/axios.min.js"></script><script src="/qs/qs.min.js"></script><script>new Vue({el:"#app",data:{form:{username:"",password:"",rememberMe:false}},methods:{login:function () {//Qs.stringify(this.form) 将form由{xx:值} 转为 xx=值&xx=值axios.post("/user/login",Qs.stringify(this.form)).then(res=>{if(res.data.code == 1){location.href = "/pages/index.html";}});}}});</script></body></html>

2、修改登录方法

@PostMapping("/login")public JsonResult login(String username,String password,Boolean rememberMe){//创建TokenUsernamePasswordToken token = new UsernamePasswordToken(username, password);//设置记住我token.setRememberMe(rememberMe);//获得subjectSubject subject = SecurityUtils.getSubject();try {subject.login(token);return new JsonResult(1,"登录成功");}catch (AuthenticationException ex){ex.printStackTrace();}return new JsonResult(0,"账号或密码错误");}

3、修改Shiro配置类

//创建RememberMe管理器public CookieRememberMeManager rememberMeManager(){CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();SimpleCookie rememberMe = new SimpleCookie("rememberMe");//单位是秒 过期时间rememberMe.setMaxAge(60 * 10);rememberMeManager.setCookie(rememberMe);return rememberMeManager;}//返回面向Web开发的安全管理器@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager(){DefaultWebSecurityManager sm = new DefaultWebSecurityManager();//设置自定义Realmsm.setRealm(myRealm());//设置RememberMesm.setRememberMeManager(rememberMeManager());return sm;}//返回Shiro过滤器链定义@Beanpublic ShiroFilterChainDefinition shiroFilterChainDefinition() {...//这是使用user,和authc的区别是:authc必须经过验证,user经过验证或RememberMe都可以登录map.put("/**","user");chainDefinition.addPathDefinitions(map);return chainDefinition;}

选择记住我登录系统,关闭浏览器再次打开系统页面后,不需要登录直接进入系统。

结束

本文如果对你有帮助,请点个赞哦: )

大家如果需要学习其他Java知识点,戳这里 超详细的Java知识点汇总

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。