通过了三个月的实习,在javaWEB开发的过程当中,学习到了一些新的知识,特此记录一下学习到的一种新的WEB开发当中常用的用户认证和授权的安全框架,Shiro。
首先,要先知道shiro这个框架主要在WEB开发当中的定位,其主要的定位是用于做用户登录和权限控制的一个安全框架,可以帮助我们更加安全的完成用户的登录以及授权的相关过程,并且其使用和配置都十分的简便,还可以集成到SpringMVC当中,并且还能配合各类数据库的使用,包括mysql、nosql等,所以可谓十分强大但又很简练的安全框架。
要开始学习shiro的使用,我们要先引入一些在shiro当中的一些核心功能的概念:
1.Authentication(身份验证):简称为“登录”,即验证当前登录的用户是否是我们项目当中的合法用户。
2.Authorization(授权):不同级别的合法用户的访问权限的控制过程,即决定哪些用户拥有哪些权限去访问某些的资源。
3.Session Management(会话管理):管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
4.Cryptography(加密):通过使用加密保持数据安全
shiro由于其定位的因素,本文主要介绍其中的两个核心方法:认证(Authentication)和授权(Authorization)
在介绍功能之前,我们还要简单的介绍一下在shiro框架当中的主要使用的组件的概念:
1.Subject : 正与系统进行交互的对象,在web开发当中,可以理解成每一台想要访问web的用户,都是一个subject。
2.SecurityManager:顾名思义,就是用于安全事务的管理的对象,Shiro 架构的核心,其用来协调内部各安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务。当 Shiro 与一个 Subject 进行交互时,实质上是幕后的 SecurityManager 处理所有繁重的 Subject 安全操作。
3.Realms :本质上是一个特定安全的 DAO,即一个安全的数据源。当配置 Shiro 时,必须指定至少一个 Realm 用来进行身份验证和/或授权。Shiro 提供了多种可用的 Realms 来获取安全相关的数据。如关系(JDBC),INI 及属性文件等。可以定义自己 Realm 实现来代表自定义的数据源。
对以上这些概念有了一些理解之后,就基本掌握了shiro的大体概念了,那么接下来,我们来了解shiro如何使用,二话不说,先上我们的maven依赖文件:
4.0.0 bjtu.wellhold Shiro 0.0.1-SNAPSHOT jar Shiro http://maven.apache.org UTF-8 junit junit 4.9 test org.apache.shiro shiro-core 1.2.3 commons-logging commons-logging 1.2
测试框架我们使用的是junit,shiro的版本我们使用的是1.2.3,这里要额外提一句,版本的不同,对一些功能的实现会有不兼容的现象(报错),所以一定要注意版本的问题。
在上头介绍概念的时候,有提到过Realms 的概念,它是一个要验证的数据源,可以把它理解成一个安全的用于存储程序合法用户信息——用户名、密码、以及该用户的权限的仓库,所有用户提交的账户信息都需要到这里头进行验证,看看用户提交的账户信息是否存在于Realms里头,一判定当前想要登录的用户是否是程序的合法用户,如果是合法用户,那么还可以获取到该用户的一个安全权限,即该用户可以访问什么内容,不可以访问什么内容,可能解释的有点繁琐,也不是很全面,但是这样的解释比较利于理解。在介绍Realms 概念的时候,有提到Realms 的数据源可以是JDBC,也可以是.ini文件,那么接下来我们就分别对两种形式进行介绍。
1.通过.ini作为信息数据源
在shiro1.2.3.版本之后,在我们没有指定相应的Realms的实现类的时候,shiro会自动的为我们生成一个iniRealm实例供程序使用,所以我们只需要指定我们的信息源.ini文件即可,下边是我们的.ini文件内容:
#自定义数据源 格式 用户名=密码[users]Floder=Floder
内容很简单,只是指定了一个用户名为Floder,密码为Floder的用户,因为这里只涉及讲解身份认证,所以并没有对这个用户的权限内容进行编写。接下来看看我们的代码:
//认证过程 public void demo1() { //该demo中没有配置realm,是因为Shiro,默认提供了一个叫IniRealm的实例在进行的 //在1.2.3版本之后才有的 //设置securityManage环境 Factoryfactory=new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager manager=factory.getInstance(); SecurityUtils.setSecurityManager(manager); //模拟subject Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken token =new UsernamePasswordToken("Floder","Floder"); try{ subject.login(token); }catch(AuthenticationException e) { e.printStackTrace(); } boolean isOK=subject.isAuthenticated(); System.out.println("用户是否登录:"+isOK); //模拟用户退出登录 subject.logout(); isOK=subject.isAuthenticated(); System.out.println("用户是否登录:"+isOK); }
代码当中,首先获取到一个SecurityManager的工厂实例,并且为这个实例指定了我们的数据源文件,之后通过工厂实例生成了一个SecurityManager的实例,再为SecurityUtils去配置SecurityManager,这样我们的程序就有一个SecurityManager用于管理所有安全行为的过程了,并对所有的安全行为负责(这里提到的安全行为主要指的是身份认证和授权过程)。之后我们通过模拟一个subject和token(令牌)去模拟用户登录的过程,通过执行subject.login,将令牌信息提交到subject之后,再执行subject.isAuthenticated(),对提交的令牌进行身份认证,即到我们配置的.ini文件当中去查找是否存在这个合法用户,如果存在则返回true,如果不存在则返回false,执行结果如下:
接下来我们再来看看的授权过程,这时候我们要在.ini文件当中配置到合法用户的相关权限:
#用户对应的角色#用户Floder拥有角色role1和role2[users]Floder = Floder,role1,role2floder=floder,role2#角色对应的资源权限[roles]#权限标识符符号规则 资源:操作:实例 user:create:01 表示对用户资源实例01进行create操作#user:create 表示对资源进行create操作,相当于user:create:*#user:*:01 表示对用户资源实例01进行所有操作role1=user:create,user:updaterole2 = user:create
在.ini文件当中,我们配置了两个合法用户,并且我们制定了相应的roles,即角色,相信熟悉较为高级的数据库的人对这个概念不陌生,角色的不同,拥有的不同权限,在我们的demo中,角色1有创建用户和更新用户的权限,而角色2则只有创建用户的权限。接下来看我们的授权demo的代码:
@Test //授权过程 public void demo2() { //该demo中没有配置realm,是因为Shiro,默认提供了一个叫IniRealm的实例在进行的 //在1.2.3版本之后才有的 //设置securityManage环境 Factoryfactory = new IniSecurityManagerFactory("classpath:shiro-permission.ini"); SecurityManager manager=factory.getInstance(); SecurityUtils.setSecurityManager(manager); Subject subject = SecurityUtils.getSubject();// Subject subject2 = SecurityUtils.getSubject(); //提交认证是说携带的信息储存在 Token 中 UsernamePasswordToken token = new UsernamePasswordToken("Floder", "Floder");// UsernamePasswordToken token2 = new UsernamePasswordToken("floder", "floder"); //模拟获取登陆 try { subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); } //基于角色判断 //判断认证用户是否有role1角色 boolean isHasRole=subject.hasRole("role1"); System.out.println("判断当前用户是否有role1角色:"+isHasRole); //判断当前用户是否拥有多个角色 boolean isHasAllRoles=subject.hasAllRoles(Arrays.asList("role1","role2")); System.out.println("判断当前用户是否有多个角色:"+isHasAllRoles); //基于资源判断 //判断当前用户是否能对该资源进行单一操作 boolean isPermitted=subject.isPermitted("user:create"); System.out.println("判断当前用户是否能对该资源进行单一操作:"+isPermitted); //判断已认证用户的资源是否拥有对应的多个操作 boolean isPermittedAll = subject.isPermittedAll("user:create","user:update"); System.out.println("判断已认证用户的资源是否拥有对应的多个操作 "+isPermittedAll); }
在代码当中,当前合法用户进行了各种角色和授权的行为判别,即管理当前用户所具有的安全权限。运行结果如下:
将我们的token换成token2进行subject.login,再看运行结果:
符合我们在.ini文件当中指定的内容。到此,我们基于.ini配置文件的身份认证和授权管理过程就讲的差不多了,因为是使用的shiro提供的自动生成的realm,所以所需要编写的东西还是相对简单的,接下来我们来学习通过我们自定义的realm来做身份认证和授权的过程。
2.通过自定义Realms作为信息数据源
首先先看我们自定义的Realms实现类的代码:
package bjtu.wellhold.Shiro;import java.util.ArrayList;import java.util.List;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.IncorrectCredentialsException;import org.apache.shiro.authc.SimpleAuthenticationInfo;import org.apache.shiro.authc.UnknownAccountException;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.subject.PrincipalCollection;public class UserRealm extends AuthorizingRealm { @Override public void setName(String name) { super.setName("userName"); } //授权(依赖于认证信息) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String userId = (String) principals.getPrimaryPrincipal(); //模拟从数据库得来的角色信息 ListlistPermission = new ArrayList (); listPermission.add("user:create"); listPermission.add("user:update"); //将权限信息保存到AuthorizationInfo中 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for(String permission:listPermission){ simpleAuthorizationInfo.addStringPermission(permission); } return simpleAuthorizationInfo; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //得到token的用户信息 // 从token中取出身份信息, 即用户的username String userId = (String) token.getPrincipal(); System.out.println("token传入的用户名:"+userId); String password = new String((char[])token.getCredentials()); System.out.println("token传入的密码:"+password); //可以到数据库当中的某张表查询是否存在该合法用户信息 //模拟数据库返回数据,存在就返回一个credentials String username1 = "Floder";//数据库当中存在的用户名 String password1 = "Floder";//数据库当中存在的密码 if(!username1.equals(userId)) throw new UnknownAccountException("没有这个用户!"); else if(!password1.equals(password)) throw new IncorrectCredentialsException("密码错误!"); else { //实际上在返回SimpleAuthenticationInfo的时候 //subject.login当中的token还是会和SimpleAuthenticationInfo当中的用户名密码进行匹配一次 //不过为了可控的抛出exception信息,我们还是在方法中就进行验证用户名和密码 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userId, password1, this.getName()); return info; } }}
在代码中,我们自定义了一个实现类叫UserRealm,它继承了父类AuthorizingRealm,继承只要,需要实现两个方法,分别是身份认证方法:doGetAuthenticationInfo,以及授权方法:doGetAuthorizationInfo。
我们首先来说身份认证方法的实现逻辑,我们先来看看我们的demo函数的写法,与使用shiro提供的自定义Realm当中的demo函数的逻辑相似:
//采用自定义realm @Test public void demo3() { //设置securityManage环境 IniSecurityManagerFactory factory=new IniSecurityManagerFactory("classpath:shiro-single-Aurealm.ini"); SecurityManager manager=factory.getInstance(); SecurityUtils.setSecurityManager(manager); //模拟subject Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken token =new UsernamePasswordToken("Floder","Floder"); try{ subject.login(token); }catch(AuthenticationException e) { e.printStackTrace(); } boolean isOK=subject.isAuthenticated(); System.out.println("用户是否登录:"+isOK); //模拟用户退出登录 subject.logout(); isOK=subject.isAuthenticated(); System.out.println("用户是否登录:"+isOK); }
demo函数当中,还是以往的套路,首先生成一个工厂的实例,在生成工厂实例的过程当中指定.ini配置文件,但是这一次,不在.ini内写用户信息,而是在.ini内写自定义的Realm信息:
userRealm=bjtu.wellhold.Shiro.UserRealmsecurityManager.realms=$userRealm
在配置完SecurityManage环境之后,我们执行subject.login,这个时候,会自动调用到自定义的Realm类里头的doGetAuthenticationInfo方法,并且把token传过去,这时候我们就可以在doGetAuthenticationInfo方法当中做用户的身份认证,信息源可以是mysql等数据库,并且还可以对传入的token进行用户名密码的分别认证,然后如果不属于合法信息用户还可以分别跑出不同的异常信息进行提示,在代码当中的注释也写的很明白了,这里就不再进行赘述了。
看完自定义类的身份认证,我们再来看自定义类的授权过程,首先看授权的demo代码:
@Test public void demo4() { IniSecurityManagerFactory factory=new IniSecurityManagerFactory("classpath:shiro-single-Aurealm.ini"); SecurityManager manager=factory.getInstance(); SecurityUtils.setSecurityManager(manager); //模拟subject Subject subject=SecurityUtils.getSubject(); UsernamePasswordToken token =new UsernamePasswordToken("Floder","Floder"); try{ subject.login(token); }catch(AuthenticationException e) { e.printStackTrace(); } //基于资源判断 //判断当前用户是否能对该资源进行单一操作 boolean isPermitted=subject.isPermitted("user:create"); System.out.println("判断当前用户是否能对该资源进行单一操作:"+isPermitted); //判断已认证用户的资源是否拥有对应的多个操作 boolean isPermittedAll = subject.isPermittedAll("user:create","user:update"); System.out.println("判断已认证用户的资源是否拥有对应的多个操作 "+isPermittedAll); }
对传入的token进行了身份认证之后,在进行授权确认,在doGetAuthorizationInfo的方法当中,对token当中的信息,进行了授权信息的查询(可以从配置文件当中查询,也可以从数据库当中查询)查询之后通过simpleAuthorizationInfo实例对象返回到demo函数当中,具体的可以通过代码当中的注释很明了的看出,这里就不进行赘述了。
到此为止,基于.ini文件配置以及基于自定义的Realm的方式使用Shiro进行用户登录的身份认证和授权的demo就讲解完毕了,可能总结的比较片面,描述的也比较简单,如有描述不正确的地方,还请联系本人,相互交流一下。