springboot(十四):springboot整合shiro-登录认证和权限管理
本文内容纲要:
-ApacheShiro
-WhatisApacheShiro?
-ApacheShiroFeatures特性
-High-LevelOverview高级概述
-快速上手
-基础信息
-RBAC
-Shiro配置
-测试
这篇文章我们来学习如何使用SpringBoot集成ApacheShiro。安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求。在Java领域一般有SpringSecurity、ApacheShiro等安全框架,但是由于SpringSecurity过于庞大和复杂,大多数公司会选择ApacheShiro来使用,这篇文章会先介绍一下ApacheShiro,在结合SpringBoot给出使用案例。
ApacheShiro
WhatisApacheShiro?
ApacheShiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。
ApacheShiro的首要目标是易于使用和理解。安全通常很复杂,甚至让人感到很痛苦,但是Shiro却不是这样子的。一个好的安全框架应该屏蔽复杂性,向外暴露简单、直观的API,来简化开发人员实现应用程序安全所花费的时间和精力。
Shiro能做什么呢?
- 验证用户身份
- 用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
- 在非web或EJB容器的环境下可以任意使用SessionAPI
- 可以响应认证、访问控制,或者Session生命周期中发生的事件
- 可将一个或以上用户安全数据源数据组合成一个复合的用户“view”(视图)
- 支持单点登录(SSO)功能
- 支持提供“RememberMe”服务,获取用户关联信息而无需登录
…
等等——都集成到一个有凝聚力的易于使用的API。
Shiro致力在所有应用环境下实现上述功能,小到命令行应用程序,大到企业应用中,而且不需要借助第三方框架、容器、应用服务器等。当然Shiro的目的是尽量的融入到这样的应用环境中去,但也可以在它们之外的任何环境下开箱即用。
ApacheShiroFeatures特性
ApacheShiro是一个全面的、蕴含丰富功能的安全框架。下图为描述Shiro功能的框架图:
Authentication(认证),Authorization(授权),SessionManagement(会话管理),Cryptography(加密)被Shiro框架的开发团队称之为应用安全的四大基石。那么就让我们来看看它们吧:
- **Authentication(认证):**用户身份识别,通常被称为用户“登录”
- **Authorization(授权):**访问控制。比如某个用户是否具有某个操作的使用权限。
- **SessionManagement(会话管理):**特定于用户的会话管理,甚至在非web或EJB应用程序。
- **Cryptography(加密):**在对数据源使用加密算法加密的同时,保证易于使用。
还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:
- Web支持:Shiro提供的web支持api,可以很轻松的保护web应用程序的安全。
- 缓存:缓存是ApacheShiro保证安全操作快速、高效的重要手段。
- 并发:ApacheShiro支持多线程应用程序的并发特性。
- 测试:支持单元测试和集成测试,确保代码和预想的一样安全。
- “RunAs”:这个功能允许用户假设另一个用户的身份(在许可的前提下)。
- “RememberMe”:跨session记录用户的身份,只有在强制需要时才需要登录。
注意:Shiro不会去维护用户、维护权限,这些需要我们自己去设计/提供,然后通过相应的接口注入给Shiro
High-LevelOverview高级概述
在概念层,Shiro架构包含三个主要的理念:Subject,SecurityManager和Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。
- Subject:当前用户,Subject可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
- SecurityManager:管理所有Subject,SecurityManager是Shiro架构的核心,配合内部安全组件共同组成安全伞。
- Realms:用于进行权限信息的验证,我们自己实现。Realm本质上是一个特定的安全DAO:它封装与数据源连接的细节,得到Shiro所需的相关的数据。在配置Shiro的时候,你必须指定至少一个Realm来实现认证(authentication)和/或授权(authorization)。
我们需要实现Realms的Authentication和Authorization。其中Authentication是用来验证用户身份,Authorization是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
快速上手
基础信息
pom包依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>net.sourceforge.nekohtml</groupId><artifactId>nekohtml</artifactId><version>1.9.22</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency></dependencies>
重点是shiro-spring包
配置文件
spring:
datasource:
url:jdbc:mysql://localhost:3306/test
username:root
password:root
driver-class-name:com.mysql.jdbc.Driver
jpa:
database:mysql
show-sql:true
hibernate:
ddl-auto:update
naming:
strategy:org.hibernate.cfg.DefaultComponentSafeNamingStrategy
properties:
hibernate:
dialect:org.hibernate.dialect.MySQL5Dialect
thymeleaf:
cache:false
mode:LEGACYHTML5
thymeleaf的配置是为了去掉html的校验
页面
我们新建了六个页面用来测试:
- index.html:首页
- login.html:登录页
- userInfo.html:用户信息页面
- userInfoAdd.html:添加用户页面
- userInfoDel.html:删除用户页面
- 403.html:没有权限的页面
除过登录页面其它都很简单,大概如下:
<!DOCTYPEhtml>
<htmllang="en"><head><metacharset="UTF-8"><title>Title</title></head><body><h1>index</h1></body></html>
RBAC
RBAC是基于角色的访问控制(Role-BasedAccessControl)在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
采用jpa技术来自动生成基础表格,对应的entity如下:
用户信息
@Entity
publicclassUserInfoimplementsSerializable{@Id@GeneratedValueprivateIntegeruid;@Column(unique=true)privateStringusername;//帐号privateStringname;//名称(昵称或者真实姓名,不同系统不同定义)privateStringpassword;//密码;privateStringsalt;//加密密码的盐privatebytestate;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户,1:正常状态,2:用户被锁定.@ManyToMany(fetch=FetchType.EAGER)//立即从数据库中进行加载数据;@JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="uid")},inverseJoinColumns={@JoinColumn(name="roleId")})privateList<SysRole>roleList;//一个用户具有多个角色//省略getset方法}
角色信息
@Entity
publicclassSysRole{@Id@GeneratedValueprivateIntegerid;//编号privateStringrole;//角色标识程序中判断使用,如"admin",这个是唯一的:privateStringdescription;//角色描述,UI界面显示使用privateBooleanavailable=Boolean.FALSE;//是否可用,如果不可用将不会添加给用户//角色--权限关系:多对多关系;@ManyToMany(fetch=FetchType.EAGER)@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})privateList<SysPermission>permissions;//用户-角色关系定义;@ManyToMany@JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})privateList<UserInfo>userInfos;//一个角色对应多个用户//省略getset方法}
权限信息
@Entity
publicclassSysPermissionimplementsSerializable{@Id@GeneratedValueprivateIntegerid;//主键.privateStringname;//名称.@Column(columnDefinition="enum('menu','button')")privateStringresourceType;//资源类型,[menu|button]privateStringurl;//资源路径.privateStringpermission;//权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:viewprivateLongparentId;//父编号privateStringparentIds;//父编号列表privateBooleanavailable=Boolean.FALSE;@ManyToMany@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})privateList<SysRole>roles;//省略getset方法}
根据以上的代码会自动生成user_info(用户信息表)、sys_role(角色表)、sys_permission(权限表)、sys_user_role(用户角色表)、sys_role_permission(角色权限表)这五张表,为了方便测试我们给这五张表插入一些初始化数据:
INSERTINTO`sys_permission`(`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`)VALUES(1,0,'用户管理',0,'0/','userInfo:view','menu','userInfo/userList');INSERTINTO`sys_permission`(`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`)VALUES(2,0,'用户添加',1,'0/1','userInfo:add','button','userInfo/userAdd');INSERTINTO`sys_permission`(`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`)VALUES(3,0,'用户删除',1,'0/1','userInfo:del','button','userInfo/userDel');INSERTINTO`sys_role`(`id`,`available`,`description`,`role`)VALUES(1,'0','管理员','admin');INSERTINTO`sys_role`(`id`,`available`,`description`,`role`)VALUES(2,'0','VIP会员','vip');INSERTINTO`sys_role_permission`VALUES('1','1');INSERTINTO`sys_role_permission`(`permission_id`,`role_id`)VALUES(1,1);INSERTINTO`sys_role_permission`(`permission_id`,`role_id`)VALUES(1,2);INSERTINTO`sys_role_permission`(`permission_id`,`role_id`)VALUES(1,3);INSERTINTO`sys_user_role`(`role_id`,`uid`)VALUES(1,1);INSERTINTO`user_info`(`uid`,`username`,`name`,`password`,`salt`,`state`)VALUES('1','admin','管理员','d3c59d25033dbf980d29554025c23a75','8d78869f470951332959580424d4bf4f',0);
Shiro配置
首先要配置的是ShiroConfig类,ApacheShiro核心通过Filter来实现,就好像SpringMvc通过DispachServlet来主控制一样。既然是使用Filter一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
ShiroConfig
@Configuration
publicclassShiroConfig{@BeanpublicShiroFilterFactoryBeanshirFilter(SecurityManagersecurityManager){System.out.println("ShiroConfiguration.shirFilter()");ShiroFilterFactoryBeanshiroFilterFactoryBean=newShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);//拦截器.Map<String,String>filterChainDefinitionMap=newLinkedHashMap<String,String>();//配置不会被拦截的链接顺序判断filterChainDefinitionMap.put("/static/**","anon");//配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了filterChainDefinitionMap.put("/logout","logout");//<!--过滤链定义,从上向下顺序执行,一般将/**放在最为下边-->:这是一个坑呢,一不小心代码就不好使了;//<!--authc:所有url都必须认证通过才可以访问;anon:所有url都都可以匿名访问-->filterChainDefinitionMap.put("/**","authc");//如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面shiroFilterFactoryBean.setLoginUrl("/login");//登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/index");//未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);returnshiroFilterFactoryBean;}@BeanpublicMyShiroRealmmyShiroRealm(){MyShiroRealmmyShiroRealm=newMyShiroRealm();returnmyShiroRealm;}@BeanpublicSecurityManagersecurityManager(){DefaultWebSecurityManagersecurityManager=newDefaultWebSecurityManager();securityManager.setRealm(myShiroRealm());returnsecurityManager;}}
FilterChain定义说明:
- 1、一个URL可以配置多个Filter,使用逗号分隔
- 2、当设置多个过滤器时,全部验证通过,才视为通过
- 3、部分过滤器可指定参数,如perms,roles
Shiro内置的FilterChain
FilterName | Class |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
- anon:所有url都都可以匿名访问
- authc:需要认证才能进行访问
- user:配置记住我或认证通过可以访问
登录认证实现
在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)
方法。
该方法主要执行以下操作:
- 1、检查提交的进行认证的令牌信息
- 2、根据令牌信息从数据源(通常为数据库)中获取用户信息
- 3、对用户信息进行匹配验证。
- 4、验证通过将返回一个封装了用户信息的
AuthenticationInfo
实例。 - 5、验证失败则抛出
AuthenticationException
异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。
doGetAuthenticationInfo的重写
@Override
protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{System.out.println("MyShiroRealm.doGetAuthenticationInfo()");//获取用户的输入的账号.Stringusername=(String)token.getPrincipal();System.out.println(token.getCredentials());//通过username从数据库中查找User对象,如果找到,没找到.//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法UserInfouserInfo=userInfoService.findByUsername(username);System.out.println("----->>userInfo="+userInfo);if(userInfo==null){returnnull;}SimpleAuthenticationInfoauthenticationInfo=newSimpleAuthenticationInfo(userInfo,//用户名userInfo.getPassword(),//密码ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+saltgetName()//realmname);returnauthenticationInfo;}
链接权限的实现
shiro的权限授权是通过继承AuthorizingRealm
抽象类,重载doGetAuthorizationInfo();
当访问到页面的时候,链接配置了相应的权限或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。在这个方法中主要是使用类:SimpleAuthorizationInfo
进行角色的添加和权限的添加。
@Override
protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();UserInfouserInfo=(UserInfo)principals.getPrimaryPrincipal();for(SysRolerole:userInfo.getRoleList()){authorizationInfo.addRole(role.getRole());for(SysPermissionp:role.getPermissions()){authorizationInfo.addStringPermission(p.getPermission());}}returnauthorizationInfo;}
当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
authorizationInfo.setRoles(roles);authorizationInfo.setStringPermissions(stringPermissions);
就是说如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”,“perms[权限添加]”);
就说明访问/add这个链接必须要有“权限添加”这个权限才可以访问,如果在shiro配置文件中添加了filterChainDefinitionMap.put(“/add”,“roles[100002],perms[权限添加]”);
就说明访问/add
这个链接必须要有“权限添加”这个权限和具有“100002”这个角色才可以访问。
登录实现
登录过程其实只是处理异常的相关信息,具体的登录验证交给shiro来处理
@RequestMapping("/login")
publicStringlogin(HttpServletRequestrequest,Map<String,Object>map)throwsException{System.out.println("HomeController.login()");//登录失败从request中获取shiro处理的异常信息。//shiroLoginFailure:就是shiro异常类的全类名.Stringexception=(String)request.getAttribute("shiroLoginFailure");System.out.println("exception="+exception);Stringmsg="";if(exception!=null){if(UnknownAccountException.class.getName().equals(exception)){System.out.println("UnknownAccountException-->账号不存在:");msg="UnknownAccountException-->账号不存在:";}elseif(IncorrectCredentialsException.class.getName().equals(exception)){System.out.println("IncorrectCredentialsException-->密码不正确:");msg="IncorrectCredentialsException-->密码不正确:";}elseif("kaptchaValidateFailed".equals(exception)){System.out.println("kaptchaValidateFailed-->验证码错误");msg="kaptchaValidateFailed-->验证码错误";}else{msg="else>>"+exception;System.out.println("else-->"+exception);}}map.put("msg",msg);//此方法不处理登录成功,由shiro进行处理return"/login";}
其它dao层和service的代码就不贴出来了大家直接看代码。
测试
1、编写好后就可以启动程序,访问index页面,由于没有登录就会跳转到login页面。登录之后就会跳转到index页面,登录后,有直接在浏览器中输入index页面访问,又会跳转到login页面。上面这些操作时候触发MyShiroRealm.doGetAuthenticationInfo()
这个方法,也就是登录认证的方法。
2、登录admin账户,访问:http://127.0.0.1:8080/userInfo/userAdd
显示用户添加界面
,访问http://127.0.0.1:8080/userInfo/userDel
显示403没有权限
。上面这些操作时候触发MyShiroRealm.doGetAuthorizationInfo()
这个方面,也就是权限校验的方法。
3、修改admin不同的权限进行测试
shiro很强大,这仅仅是完成了登录认证和权限管理这两个功能,更多内容以后有时间再做探讨。
示例代码-github
示例代码-码云
参考:
ApacheShiro中文手册
SpringBootShiro权限管理【从零开始学SpringBoot】
SpringBoot+shiro整合学习之登录认证和权限控制
本文内容总结:ApacheShiro,WhatisApacheShiro?,ApacheShiroFeatures特性,High-LevelOverview高级概述,快速上手,基础信息,RBAC,Shiro配置,测试,
原文链接:https://www.cnblogs.com/liyinfeng/p/8033869.html