这篇文章我们来学习如何使用SpringBoot集成ApacheShiro。安全应该是互联网公司的一道生命线,几乎任何的公司都会涉及到这方面的需求。在Java领域一般有SpringSecurity、ApacheShiro等安全框架,但是由于SpringSecurity过于庞大和复杂,大多数公司会选择ApacheShiro来使用,这篇文章会先介绍一下ApacheShiro,在结合SpringBoot给出使用案例。
ApacheShiro是一个功能强大、灵活的,开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。
ApacheShiro的首要目标是易于使用和理解。安全通常很复杂,甚至让人感到很痛苦,但是Shiro却不是这样子的。一个好的安全框架应该屏蔽复杂性,向外暴露简单、直观的API,来简化开发人员实现应用程序安全所花费的时间和精力。
Shiro能做什么呢?
验证用户身份
用户访问权限控制,比如:1、判断用户是否分配了一定的安全角色。2、判断用户是否被授予完成某个操作的权限
在非Web或EJB容器的环境下可以任意使用SessionAPI
可以响应认证、访问控制,或者Session生命周期中发生的事件
可将一个或以上用户安全数据源数据组合成一个复合的用户“view”(视图)
支持单点登录(SSO)功能
支持提供“RememberMe”服务,获取用户关联信息而无需登录
…
等等——都集成到一个有凝聚力的易于使用的API。
Shiro致力在所有应用环境下实现上述功能,小到命令行应用程序,大到企业应用中,而且不需要借助第三方框架、容器、应用服务器等。当然Shiro的目的是尽量的融入到这样的应用环境中去,但也可以在它们之外的任何环境下开箱即用。
ApacheShiro是一个全面的、蕴含丰富功能的安全框架。下图为描述Shiro功能的框架图:
Authentication(认证),Authorization(授权),SessionManagement(会话管理),Cryptography(加密)被Shiro框架的开发团队称之为应用安全的四大基石。那么就让我们来看看它们吧:
Authentication(认证):用户身份识别,通常被称为用户“登录”
Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
SessionManagement(会话管理):特定于用户的会话管理,甚至在非web或EJB应用程序。
Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
Web支持:Shiro提供的Web支持api,可以很轻松的保护Web应用程序的安全。
缓存:缓存是ApacheShiro保证安全操作快速、高效的重要手段。
并发:ApacheShiro支持多线程应用程序的并发特性。
测试:支持单元测试和集成测试,确保代码和预想的一样安全。
“RunAs”:这个功能允许用户假设另一个用户的身份(在许可的前提下)。
“RememberMe”:跨session记录用户的身份,只有在强制需要时才需要登录。
在概念层,Shiro架构包含三个主要的理念:Subject,SecurityManager和Realm。下面的图展示了这些组件如何相互作用,我们将在下面依次对其进行描述。
Subject:当前用户,Subject可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
SecurityManager:管理所有Subject,SecurityManager是Shiro架构的核心,配合内部安全组件共同组成安全伞。
Realms:用于进行权限信息的验证,我们自己实现。Realm本质上是一个特定的安全DAO:它封装与数据源连接的细节,得到Shiro所需的相关的数据。在配置Shiro的时候,你必须指定至少一个Realm来实现认证(authentication)和/或授权(authorization)。
pom包依赖
/groupIdartifactIdspring-boot-starter-data-jpa/artifactId//groupIdartifactIdspring-boot-starter-thymeleaf/artifactId//groupIdartifactIdnekohtml//version//groupIdartifactIdspring-boot-starter-web/artifactId//groupIdartifactIdshiro-spring//version/depencydepencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/depency/depencies
重点是shiro-spring包
配置文件
spring:datasource:url:jdbc:mysql://localhost:3306/testusername:rootpassword:rootdriver-class-name::database:mysqlshow-sql:truehibernate:ddl-auto:updatenaming:strategy::hibernate:dialect::cache:falsemode:LEGACYHTML5
thymeleaf的配置是为了去掉html的校验
页面
我们新建了六个页面用来测试:
:首页
:登录页
:用户信息页面
:添加用户页面
:删除用户页面
403.html:没有权限的页面
除过登录页面其它都很简单,大概如下:
!DOCTYPEhtmlhtmllang="en"headmetacharset="UTF-8"titleTitle/title/headbodyh1index/h1/body/htmlRBAC
RBAC是基于角色的访问控制(Role-BasedAccessControl)在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。
采用Jpa技术来自动生成基础表格,对应的实体如下:
用户信息
@EntitypublicclassUserInfoimplementsSerializable{@Id@GeneratedValueprivateIntegeruid;@Column(unique=true)privateStringusername;//帐号privateStringname;//名称(昵称或者真实姓名,不同系统不同定义)privateStringpassword;//密码;privateStringsalt;//加密密码的盐privatebytestate;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户,1:正常状态,2:用户被锁定.@ManyToMany(fetch=)//立即从数据库中进行加载数据;@JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="uid")},inverseJoinColumns={@JoinColumn(name="roleId")})privateListSysRoleroleList;//一个用户具有多个角色//省略getset方法}
角色信息
@EntitypublicclassSysRole{@Id@GeneratedValueprivateIntegerid;//编号privateStringrole;//角色标识程序中判断使用,如"admin",这个是唯一的:privateStringdescription;//角色描述,UI界面显示使用privateBooleanavailable=;//是否可用,如果不可用将不会添加给用户//角色--权限关系:多对多关系;@ManyToMany(fetch=)@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})privateListSysPermissionpermissions;//用户-角色关系定义;@ManyToMany@JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})privateListUserInfouserInfos;//一个角色对应多个用户//省略getset方法}
权限信息
@EntitypublicclassSysPermissionimplementsSerializable{@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=;@ManyToMany@JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})privateListSysRoleroles;//省略getset方法}
根据以上的代码会自动生成user_info(用户信息表)、sys_role(角色表)、sys_permission(权限表)、sys_user_role(用户角色表)、sys_role_permission(角色权限表)这五张表,为了方便测试我们给这五张表插入一些初始化数据:
INSERTINTO`user_info`(`uid`,`username`,`name`,`password`,`salt`,`state`)VALUES('1','admin','管理员','d3c59d25033dbf980d29554025c23a75','8d78869f4709524d4bf4f',0);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`(`id`,`available`,`description`,`role`)VALUES(3,1,'test','test');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(2,1);INSERTINTO`sys_role_permission`(`permission_id`,`role_id`)VALUES(3,2);INSERTINTO`sys_user_role`(`role_id`,`uid`)VALUES(1,1);
Shiro配置首先要配置的是ShiroConfig类,ApacheShiro核心通过Filter来实现,就好像SpringMvc通过DispachServlet来主控制一样。既然是使用Filter一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。
ShiroConfig
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
Shiro内置的FilterChain
anon:所有url都都可以匿名访问
authc:需要认证才能进行访问
user:配置记住我或认证通过可以访问
登录认证实现
在认证、授权内部实现机制中都有提到,最终处理都将交给Real进行处理。因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo(),重写获取用户信息的方法。
doGetAuthenticationInfo的重写
@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){("权限配置--()");SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();UserInfouserInfo=(UserInfo)();for(SysRolerole:()){(());for(SysPermissionp:()){(());}}returnauthorizationInfo;}
当然也可以添加set集合:roles是从数据库查询的当前用户的角色,stringPermissions是从数据库查询的当前用户对应的权限
登录实现
登录过程其实只是处理异常的相关信息,具体的登录验证交给Shiro来处理
@RequestMapping("/login")publicStringlogin(HttpServletRequestrequest,MapString,Objectmap)throwsException{("()");//登录失败从request中获取shiro处理的异常信息。//shiroLoginFailure:就是shiro异常类的全类名.Stringexception=(String)("shiroLoginFailure");("exception="+exception);Stringmsg="";if(exception!=null){if(().equals(exception)){("UnknownAccountException--账号不存在:");msg="UnknownAccountException--账号不存在:";}elseif(().equals(exception)){("IncorrectCredentialsException--密码不正确:");msg="IncorrectCredentialsException--密码不正确:";}elseif("kaptchaValidateFailed".equals(exception)){("kaptchaValidateFailed--验证码错误");msg="kaptchaValidateFailed--验证码错误";}else{msg="else"+exception;("else--"+exception);}}("msg",msg);//此方法不处理登录成功,由shiro进行处理return"/login";}
其它Dao层和Service的代码就不贴出来了大家直接看代码。
1、编写好后就可以启动程序,访问
http://localhost:8080/userInfo/userList页面,由于没有登录就会跳转到
http://localhost:8080/login页面。登录之后就会跳转到index页面,登录后,直接在浏览器中输入
http://localhost:8080/userInfo/userList访问就会看到用户信息。上面这些操作时候触发
()这个方法,也就是登录认证的方法。
2、登录admin账户,访问:
,访问
。上面这些操作时候触发
()这个方面,也就是权限校验的方法。
3、修改admin不同的权限进行测试
Shiro很强大,这仅仅是完成了登录认证和权限管理这两个功能,更多内容以后有时间再做探讨。
版权声明:本站所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流,不声明或保证其内容的正确性,如发现本站有涉嫌抄袭侵权/违法违规的内容。请举报,一经查实,本站将立刻删除。