Springboot 整合shiro实现权限控制的方法
Author:jeffrey
Date:2019-04-08
一、开发环境:
1、mysql-5.7
2、navicat(mysql客户端管理工具)
3、idea2017.2
4、jdk8
5、tomcat8.5
6、springboot2.1.3
7、mybatis3
8、shiro1.4
9、maven3.3.9
二、数据库设计
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CB46ByC1-1604249108144)(img/shiro01.png)]
三、创建springboot项目
3.1添加组件
添加web、lombok、thymeleaf、jdbc、mysql、mybatis等模块;
3.2pom.xml
org.springframework.boot spring-boot-starter-parent 2.1.4.RELEASE org.springframework.boot spring-boot-starter-jdbc org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web org.mybatis.spring.boot mybatis-spring-boot-starter 2.0.1 org.apache.shiro shiro-spring 1.4.0 com.alibaba druid 1.0.26 mysql mysql-connector-java 5.1.32 org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin
3.3创建项目包结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NNuns5Pt-1604249108145)(img/shiro02.png)]
3.4配置初始化文件application.yml
#配置服务端口号 server: port:8080 #配置数据源 spring: datasource: type:com.alibaba.druid.pool.DruidDataSource driver-class-name:com.mysql.jdbc.Driver url:jdbc:mysql://localhost:3306/my_shiro?useUnicode=true&characterEncoding=utf-8 username:root password:59852369 #配置mybatis mybatis: mapper-locations:classpath:mapper/*.xml type-aliases-package:com.qf.domain.pojo
四、程序设计开发
4.1实体类开发
SysUser.java
packagecom.qf.domain; importlombok.Data; importjava.io.Serializable; importjava.util.Date; /** *Createdby54110on2019-07-05. */ @Data publicclassSysUserimplementsSerializable{ privateintuserId;//用户id privateStringloginName;//登录名 privateStringpassword;// privateIntegerstate; privateDatecreateTime; privateStringrealname; }
SysPermission.java
packagecom.qf.domain; importlombok.Data; importjava.io.Serializable; /** *Createdby54110on2019-07-05. */ @Data publicclassSysPermissionimplementsSerializable{ privateintpermId; privateStringpermName;//权限名称 privateStringpermUrl;//权限操作地址(路径) privateStringmenuName;//菜单名 privateStringmenuLevel;//菜单级别(11:一级;12:二级。。。) privateStringmenuCode;//菜单编码(每级两位数字) privateintifValid; privateStringparentCode; }
4.2数据访问层接口开发
SysUserMapper.java
packagecom.qf.mapper; importcom.qf.domain.SysUser; importorg.apache.ibatis.annotations.Mapper; /** *Createdby54110on2019-07-05. */ @Mapper publicinterfaceSysUserMapper{ publicSysUserfindUserByUsername(Stringusername); }
SysPermissionMapper
packagecom.qf.mapper; importcom.qf.domain.SysPermission; importorg.apache.ibatis.annotations.Mapper; importjava.util.List; /** *Createdby54110on2019-07-05. */ @Mapper publicinterfaceSysPermissionMapper{ // 根据用户登录名查询其所拥有的权限 publicListfindPermissionsByLoginName(StringloginName); }
4.3Mybatis映射开发
SysUsersMapper.xml
PASSWORD,LOGIN_NAME,CREATE_TIME,REALNAME,STATE SELECT FROM TB_SYS_USERUS WHERE US.LOGIN_NAME=#{name}
SysPermissionMapper.xml
SELECT p.* FROM TB_SYS_USERus, TB_USER_ROLEur, TB_SYS_ROLEr, TB_ROLE_PERMISSIONrp, TB_SYS_PERMISSIONp WHERE us.USERID=ur.USER_IDANDur.ROLE_ID=r.ROLE_ID ANDr.ROLE_ID=rp.ROLE_IDANDrp.PERMISSION_ID=p.PERMISSION_ID ANDtrim(us.LOGIN_NAME)=#{loginName} ORDERBYp.MENU_CODE
4.4业务层开发
SysUsersServiceImpl.java
packagecom.qf.service.impl; importcom.qf.domain.SysUser; importcom.qf.mapper.SysUserMapper; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Service; /** *Createdby54110on2019-07-05. */ @Service publicclassSysUsersServiceImpl{ @Autowired privateSysUserMapperuserMapper; publicSysUserqueryUserByLoginName(StringloginName){ SysUsertbUsers=userMapper.findUserByUsername(loginName); returntbUsers; } }
SysPermissionServiceImpl.java
packagecom.qf.service.impl; importcom.qf.domain.SysPermission; importcom.qf.mapper.SysPermissionMapper; importcom.qf.service.SysPermissionService; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Service; importjava.util.List; /** *Createdby54110on2019-07-05. */ @Service publicclassSysPermissionServiceImplimplementsSysPermissionService{ @Autowired privateSysPermissionMapperpermMapper; @Override publicListqueryPermissionsByLoginName(StringloginName){ List list=permMapper.findPermissionsByLoginName(loginName); returnlist; } }
4.5控制层接口开发
UserController.java
packagecom.qf.controller; importcom.qf.service.SysUserService; importorg.apache.shiro.SecurityUtils; importorg.apache.shiro.authc.AuthenticationException; importorg.apache.shiro.authc.UsernamePasswordToken; importorg.apache.shiro.subject.Subject; importorg.springframework.beans.factory.annotation.Autowired; importorg.springframework.stereotype.Controller; importorg.springframework.web.bind.annotation.RequestMapping; importorg.springframework.web.bind.annotation.RequestMethod; importorg.springframework.web.bind.annotation.RequestParam; importjava.util.Map; /** *Createdby54110on2019-07-05. */ @Controller publicclassUserController{ @Autowired privateSysUserServiceuserService; //登录页(view)展示 @RequestMapping("/login") publicStringshowlogin(){ return"login"; } /** *登录处理 *@parammap用户登录表单数据 *@return逻辑视图 */ @RequestMapping(value="dealLogin",method=RequestMethod.POST) publicStringdealLogin(@RequestParamMapmap){ System.out.println(map.values().toString()); try{ Subjectsubject=SecurityUtils.getSubject();//从安全管理器中获取主体对象 UsernamePasswordTokentoken=newUsernamePasswordToken();//构建令牌对象 token.setUsername(map.get("name").toString());//赋身份信息 token.setPassword(map.get("password").toString().toCharArray());//赋凭证信息 subject.login(token);//使用主体的login方法判定用户的权限 if(subject.isAuthenticated()){ //已登陆 //用户信息及权限信息的存储(session||redis) return"main"; } }catch(AuthenticationExceptione){ e.printStackTrace(); System.out.println("登录失败"); } return"login"; } //登录且拥有user: @RequestMapping("/one") publicStringshowCaseOne(){ return"one"; } @RequestMapping("/two") publicStringshowCaseTwo(){ return"two"; } //权限不足时,响应的页面 @RequestMapping("/unauth") publicStringshowPermission(){ return"unauth"; } //用户注销操作 @RequestMapping("/logout") publicStringlogout(){ Subjectsubject=SecurityUtils.getSubject(); subject.logout();//登出 return"redirect:login"; } }
4.6关于shiro的开发
a、自定义安全策略
MyShiroRealm.java
packagecom.qf.shiro; importcom.qf.domain.SysPermission; importcom.qf.domain.SysUser; importcom.qf.service.SysPermissionService; importcom.qf.service.SysUserService; importorg.apache.shiro.SecurityUtils; importorg.apache.shiro.authc.AuthenticationException; importorg.apache.shiro.authc.AuthenticationInfo; importorg.apache.shiro.authc.AuthenticationToken; importorg.apache.shiro.authc.SimpleAuthenticationInfo; importorg.apache.shiro.authz.AuthorizationInfo; importorg.apache.shiro.authz.SimpleAuthorizationInfo; importorg.apache.shiro.realm.AuthorizingRealm; importorg.apache.shiro.subject.PrincipalCollection; importorg.apache.shiro.subject.Subject; importorg.springframework.beans.factory.annotation.Autowired; importjava.util.Collection; importjava.util.HashSet; importjava.util.List; /** *Createdby54110on2019-07-05. */ publicclassMyShiroRealmextendsAuthorizingRealm{ @Autowired privateSysUserServicesysUserServiceImpl; @Autowired privateSysPermissionServicesysPermissionServiceImpl; privateStringusername; //系统授权 @Override protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){ Subjectsubject=SecurityUtils.getSubject();//获取主体对象 Stringusername=(String)subject.getPrincipal();//获取用户身份信息 Listpermissions=sysPermissionService.queryPermissionsByLoginName(username);//根据用户名获取用户的权限信息 //权限去重 Collection perms=newHashSet<>(); for(SysPermissionperm:permissions){ perms.add(perm.getPermName()); } SimpleAuthorizationInfosimpleAuthorizationInfo=newSimpleAuthorizationInfo(); simpleAuthorizationInfo.addStringPermissions(perms);//授权 returnsimpleAuthorizationInfo; } //用户认证 @Override protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{ Stringusername=(String)token.getPrincipal();//获取用户信息 //根据用户信息查询数据库获取后端的用户身份,转交给securityManager判定 SysUseruser1=sysUserService.queryUserByLoginName(username);//从数据库直接取 System.out.println(user1); if(user1!=null){ SimpleAuthenticationInfosimpleAuthenticationInfo=newSimpleAuthenticationInfo(user1.getLoginName(),user1.getPassword(),getName()); returnsimpleAuthenticationInfo; } returnnull; } }
b、自定义Shiro配置管理
ShiroConfig.java
packagecom.qf.config; importcom.qf.shiro.MyShiroRealm; importorg.apache.shiro.spring.web.ShiroFilterFactoryBean; importorg.apache.shiro.web.mgt.DefaultWebSecurityManager; importorg.springframework.beans.factory.annotation.Qualifier; importorg.springframework.context.annotation.Bean; importorg.springframework.context.annotation.Configuration; importjava.util.HashMap; importjava.util.Map; /** *Createdby54110on2019-07-05. */ @Configuration publicclassShiroConfig{ @Bean publicShiroFilterFactoryBeanshiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManagerdefaultWebSecurityManager){ ShiroFilterFactoryBeanshiroFilterFactoryBean=newShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); Mapmap=newHashMap<>(); map.put("/main","authc");//必须登录才可访问 map.put("/one","perms[user_edit]");//只有特定权限(“user_edit”)的用户登录后才可访问 map.put("/two","perms[user_forbidden]");//只有特定权限(“user_forbidden”)的用户登录后才可访问 shiroFilterFactoryBean.setLoginUrl("/login");//设置登录页(匿名) shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");//权限不足的错误提示页 shiroFilterFactoryBean.setFilterChainDefinitionMap(map);//装配拦截策略 returnshiroFilterFactoryBean; } //配置安全管理器(注入Realm对象) @Bean(name="defaultWebSecurityManager") publicDefaultWebSecurityManagerdefaultWebSecurityManager(@Qualifier("myShiroRealm")MyShiroRealmmyShiroRealm){ DefaultWebSecurityManagerdefaultWebSecurityManager=newDefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(myShiroRealm); returndefaultWebSecurityManager; } @Bean(name="myShiroRealm")//使用该注解是的Realm对象由spring容器管理 publicMyShiroRealmmyShiroRealm(){ MyShiroRealmshiroRealm=newMyShiroRealm(); returnshiroRealm; } }
五、功能测试
地址栏输入http://localhost:8080/login
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BdyHhsoT-1604249108147)(img\shiro03.png)]
使用用户:admin密码:admin登录,登录成功后显示页面如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LdoaSOGM-1604249108149)(img/shiro04.png)]
因为admin2用户拥有caseone功能的操作权限,所以当鼠标单击caseone链接时,显示如下成功访问页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Co1rUWJi-1604249108150)(img/shiro05.png)]
因为admin2没有casetwo访问权限,当用户单击casetwo时,会显示无权限访问的页面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geha4PCi-1604249108150)(img/shiro06.png)]
当单击logout链接时,系统重回登录页。此时使用用户test2密码test2再次登录。因test2用户无caseone权限,有casetwo权限,所以当test2用户单击casetwo时会显示如下页面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AWZrOeGj-1604249108151)(img/shiro07.png)]
六、启动shiro注解模式
6.1、在ShiroConfig.java中注释掉原先使用路径过滤的权限拦截语句:
//只有特定权限(“user_edit”)的用户登录后才可访问 //map.put("/one","perms[user_edit]"); //只有特定权限(“user_forbidden”)的用户登录后才可访问 //map.put("/two","perms[user_forbidden]");
6.2、修改ShiroConfig.java类代码添加如下内容:
/** *开启Shiro注解(如@RequiresRoles,@RequiresPermissions), *需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 *配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor) */ @Bean publicDefaultAdvisorAutoProxyCreatoradvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreatoradvisorAutoProxyCreator=newDefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); returnadvisorAutoProxyCreator; } /** *开启aop注解支持 */ @Bean publicAuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(DefaultWebSecurityManagerdefaultWebSecurityManager){ AuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor=newAuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager); returnauthorizationAttributeSourceAdvisor; }
6.3、修改UserController.java控制器接口代码:
//登录且拥有user: @RequiresPermissions(value={"user_edit"}) @RequestMapping("/one") publicStringshowCaseOne(){ return"one"; } @RequiresPermissions(value={"user_forbidden"}) @RequestMapping("/two") publicStringshowCaseTwo(){ return"two"; }
6.4、测试
此事有权限访问的也页面正常,但未授权的页面,无法进入提示页,显示如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pM9lRu2Z-1604249108152)(img/shiro08.png)]
后台亦抛出org.apache.shiro.authz.AuthorizationException异常:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k8KTUuoi-1604249108152)(img/shiro09.png)]
6.5、此时使用aop拦截抛出的异常
packagecom.jeffrey.exception; importorg.apache.shiro.authz.UnauthorizedException; importorg.springframework.web.bind.annotation.ControllerAdvice; importorg.springframework.web.bind.annotation.ExceptionHandler; importjavax.servlet.http.HttpServletRequest; /** *Createdbyjeffreyon2019/4/8. */ @ControllerAdvice publicclassExceptionController{ @ExceptionHandler(value=UnauthorizedException.class)//处理访问方法时权限不足问题 publicStringdefaultErrorHandler(HttpServletRequestreq,Exceptione){ return"unauth"; } }
七、shiro密码的MD5加密处理
7.1.密码的加密
在数据表中存的密码不应该是12345,而应该是12345加密之后的字符串,而且还要求这个加密算法是不可逆的,即由加密后的字符串不能反推回来原来的密码,如果能反推回来那这个加密是没有意义的。
著名的加密算法,比如MD5,SHA1
7.2.MD5加密
1).如何把一个字符串加密为MD5
2).使用MD5加密算法后,前台用户输入的字符串如何使用MD5加密,需要做的是将当前的Realm的credentialsMatcher属性,替换为Md5CredentialsMatcher由于Md5CredentialsMatcher已经过期了,推荐使用HashedCredentialsMatcher并设置加密算法即可。
7.3.使用MD5加密
1).修改ShiroConfig.java文件添加如下内容;
/** *密码校验规则HashedCredentialsMatcher *这个类是为了对密码进行编码的, *防止密码在数据库里明码保存,当然在登陆认证的时候, *这个类也负责对form里输入的密码进行编码 *处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher */ @Bean("hashedCredentialsMatcher") publicHashedCredentialsMatcherhashedCredentialsMatcher(){ HashedCredentialsMatchercredentialsMatcher=newHashedCredentialsMatcher(); //指定加密方式为MD5 credentialsMatcher.setHashAlgorithmName("MD5"); //加密次数 credentialsMatcher.setHashIterations(1024); credentialsMatcher.setStoredCredentialsHexEncoded(true); returncredentialsMatcher; } @Bean("myShiroRealm") publicMyShiroRealmmyShiroRealm(@Qualifier("hashedCredentialsMatcher")HashedCredentialsMatchermatcher){ MyShiroRealmauthRealm=newMyShiroRealm(); authRealm.setAuthorizationCachingEnabled(false); authRealm.setCredentialsMatcher(matcher); returnauthRealm; }
2).修改MyRealm.java的认证逻辑如下:
//用户认证 @Override protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{ Stringusername=(String)token.getPrincipal();//获取用户信息 SysUseruser1=sysUserService.queryUserByLoginName(username);//从数据库直接取 System.out.println(user1); if(user1!=null){ //当前realm对象的name StringrealmName=getName(); //盐值 ByteSourcecredentialsSalt=ByteSource.Util.bytes(username); //封装用户信息,构建AuthenticationInfo对象并返回 AuthenticationInfoauthcInfo=newSimpleAuthenticationInfo(username,user1.getPassword(),credentialsSalt,realmName); returnauthcInfo; } returnnull; }
3).通过 newSimpleHash(hashAlgorithmName,credentials,salt,hashIterations);我们可以得到"12345"经过MD5加密1024后的字符串;
packagecom.jeffrey; importorg.apache.shiro.crypto.hash.SimpleHash; importorg.apache.shiro.util.ByteSource; /** *Createdbyjeffreyon2019/4/8. */ publicclassMD5Salt{ publicstaticvoidmain(String[]args){ StringhashAlgorithName="MD5";//加密算法 Stringpassword="12345";//登陆时的密码 inthashIterations=1024;//加密次数 ByteSourcecredentialsSalt=ByteSource.Util.bytes("admin2");//使用登录名做为salt SimpleHashsimpleHash=newSimpleHash(hashAlgorithName,password,credentialsSalt,hashIterations); System.out.println("ok"+simpleHash); } }
使用密文替换数据库中的明文密码;
7.4测试略。。。。
7.5后记为什么使用MD5盐值加密:希望即使两个原始密码相同,加密得到的两个字符串也不同。
为什么使用MD5盐值加密:
希望即使两个原始密码相同,加密得到的两个字符串也不同。
如何做到:
1).在doGetAuthenticationInfo方法返回值创建SimpleAuthenticationInfo对象的时候,需要使用SimpleAuthenticationInfo(principal,credentials,credentialsSalt,realmName)构造器
2).使用ByteSource.Util.bytes()来计算盐值.
3).盐值需要唯一:一般使用随机字符串或userid
4).使用newSimpleHash(hashAlgorithmName,credentials,salt,hashIterations);来计算盐值加密后的密码的值.
到此这篇关于Springboot整合shiro实现权限控制的文章就介绍到这了,更多相关Springboot权限控制内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!