经过了三个月的实习,在javaWEB开发的过程中,学习到了一些新的知识,特此记录一下学习到的一种新的WEB开发当中经常使用的用户认证和受权的安全框架,Shiro。java
首先,要先知道shiro这个框架主要在WEB开发当中的定位,其主要的定位是用于作用户登陆和权限控制的一个安全框架,能够帮助咱们更加安全的完成用户的登陆以及受权的相关过程,而且其使用和配置都十分的简便,还能够集成到SpringMVC当中,而且还能配合各种数据库的使用,包括mysql、nosql等,因此可谓十分强大但又很简练的安全框架。mysql
要开始学习shiro的使用,咱们要先引入一些在shiro当中的一些核心功能的概念:web
1.Authentication(身份验证):简称为“登陆”,即验证当前登陆的用户是不是咱们项目当中的合法用户。算法
2.Authorization(受权):不一样级别的合法用户的访问权限的控制过程,即决定哪些用户拥有哪些权限去访问某些的资源。sql
3.Session Management(会话管理):管理用户特定的会话,即便在非 Web 或 EJB 应用程序。数据库
4.Cryptography(加密):经过使用加密算法保持数据安全apache
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依赖文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>bjtu.wellhold</groupId> <artifactId>Shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>Shiro</name> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> </dependencies> </project>
测试框架咱们使用的是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环境 Factory<SecurityManager> factory=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,role2
floder=floder,role2
#角色对应的资源权限
[roles]
#权限标识符符号规则 资源:操做:实例 user:create:01 表示对用户资源实例01进行create操做
#user:create 表示对资源进行create操做,至关于user:create:*
#user:*:01 表示对用户资源实例01进行全部操做
role1=user:create,user:update
role2 = user:create
在.ini文件当中,咱们配置了两个合法用户,而且咱们制定了相应的roles,即角色,相信熟悉较为高级的数据库的人对这个概念不陌生,角色的不一样,拥有的不一样权限,在咱们的demo中,角色1有建立用户和更新用户的权限,而角色2则只有建立用户的权限。接下来看咱们的受权demo的代码:
@Test //受权过程 public void demo2() { //该demo中没有配置realm,是由于Shiro,默认提供了一个叫IniRealm的实例在进行的 //在1.2.3版本以后才有的 //设置securityManage环境 Factory<SecurityManager> factory = 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(); //模拟从数据库得来的角色信息 List<String> listPermission = new ArrayList<String>(); 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.UserRealm
securityManager.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就讲解完毕了,可能总结的比较片面,描述的也比较简单,若有描述不正确的地方,还请联系本人,相互交流一下。