官网
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、受权、密码和会话管理。使用Shiro的易于理解API,你能够快速、轻松地获取任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序html
官网架构说明前端
应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subjectjava
api | 说明 |
---|---|
Subject | 主体,表明当前‘用户’ 。这个用户不必定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;全部Subject都绑定到SecurityManager,与Subject的全部交互都会委派给SecurityManager;能够把Subject认为是一个门面;SecurityManager才是实际的执行者 |
Shiro SecurityManager | 安全管理器;即全部与安全有关的操做都会与SecurityManager交互且它管理者全部Subject;能够看出它是Shiro的核心,它负责与后面介绍的其它组件进行交互,能够把它当作DispathcherServlet前端控制器 |
Realm | 域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它须要从Realm获取相应的用户进行比较以肯定用户身份是否合法;也须要从Realm获得用户相应的角色/权限进行验证用户是否能进行操做;能够把Realm当作DataSource,即安全数据源。 |
提供了对根对象securityManager及其依赖对象的配置web
#建立对象 securityManager=org.apache.shiro.mgt.DefaultSecurityManager
其构造器必须是public空参构造器,经过反射建立相应的实例。
1.对象名=全限定类名 至关于调用public无参构造器建立对象
2.对象名.属性名=值 至关于调用于setter方法设置常量值
3.对象名.属性名=$对象引用 至关于调用setter方法设置对象引用算法
提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
username=password,role1,role2
例如:配置用户名/密码及其角色,格式:“用户名=密码,角色1,角色2”,角色部分可省略。如:数据库
[users] zhang=123,role1,role2 wang=123
提供了角色及权限之间关系的配置,角色=权限1,权限2 role1 = permission1 , permission2
例如:配置角色及权限之间的关系,格式:“角色=权限1,权限2”;如:apache
[roles] role1=user:create,user:update role2=*
用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器api
/index.html = anon /admin/** = authc, roles[admin],perms["permission1"]
<dependencies> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.1.0</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.6.1</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> </dependencies>
[users] root=123456 # 帐号为root 密码是123456
package com.sxt.test; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; /** * @author Administrator *shiro的第一个入门案例 */ public class HelloTest { public static void main(String[] args) { //1.加载配置文件获取Factory对象 Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro.ini"); //2.获取SecurityManager对象 SecurityManager securityManager =factory.getInstance(); //3.将SecurityManager添加到系统 SecurityUtils.setSecurityManager(securityManager); //4.经过SecurityManager获取Subject对象 Subject subject=SecurityUtils.getSubject(); //帐号密码是客户端提交的数据 AuthenticationToken token=new UsernamePasswordToken("root","123456"); //5.实现认证操做 try{ subject.login(token); System.out.println("认证成功"); }catch(UnknownAccountException e){ System.out.println("帐号输入错误。,,,"); }catch (IncorrectCredentialsException e) { System.out.println("密码输入错误。。。"); } } }
经过上面咱们发现仅仅将数据信息定义在ini文件中咱们实际开发环境有很大不兼容,因此咱们但愿可以本身定义Realm。安全
建立一个java文件继承AuthorizingRealm类,重写两个抽象方法:网络
package com.sxt.realm; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; /** * @author Administrator * 定义的Realm */ public class SecurityRealm extends AuthorizingRealm{ /** * 认证的方法 * 就是咱们在测试代码中 定义的UserPassWoldToken对象 * 有咱们保存的须要验证的帐号密码信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken t=(UsernamePasswordToken) token; //获取登陆的帐号 String username=t.getUsername(); System.out.println("登陆的帐号:"+username); //经过jdbc去数据库中查询该帐号对应的记录 if(!"root".equals(username)){ //帐号不存在 return null; } //数据库中查询的密码是123456 String password="123456"; //身份信息(能够是帐号也能够是对象) 密码 realmName(自定义) return new SimpleAuthenticationInfo(username,password,"tang"); } /** * 受权方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { // TODO Auto-generated method stub return null; } }
方法名 | 说明 |
---|---|
doGetAuthentictionInfo | 完成帐号认证的方法 |
doGetAuthorizationInfo | 完成用户受权的方法 |
[main] #自定义 realm customRealm=com.sxt.realm.SecurityRealm #将realm设置到securityManager securityManager.realms=$customRealm
package com.sxt.test; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; /** * @author Administrator *shiro的第一个入门案例 */ public class HelloTest { public static void main(String[] args) { //1.加载配置文件获取Factory对象 Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro.ini"); //2.获取SecurityManager对象 SecurityManager securityManager =factory.getInstance(); //3.将SecurityManager添加到系统 SecurityUtils.setSecurityManager(securityManager); //4.经过SecurityManager获取Subject对象 Subject subject=SecurityUtils.getSubject(); //帐号密码是客户端提交的数据 AuthenticationToken token=new UsernamePasswordToken("root","123456"); //5.实现认证操做 try{ subject.login(token); System.out.println("认证成功"); }catch(UnknownAccountException e){ System.out.println("帐号输入错误。,,,"); }catch (IncorrectCredentialsException e) { System.out.println("密码输入错误。。。"); } } }
经过分析认证的流程,咱们发如今认证的过程当中核心代码是:
核心方法是doGetAuthenticationInfo(token)
在Realm的结构中
AuthorizingRealm和AuthenticatingRealm都提供的有doGetAuthenticationInfo(token)的抽象方法。可是AuthenticatingRealm中要重写的抽象方法太多而AuthorizingRealm只须要重写两个方法,且这两个方法都是咱们须要使用的。故选择继承AuthorizingRealm
注意:自定义Realm中只完成了帐号的认证。密码认证仍是在AuthenticatingRealm中完成的,只是咱们在自定义Realm中完成了密码的设置。
加密,是以某种特殊的算法改变原有的信息数据,使得未受权的用户即便得到了已加密的信息,但因不知解密的方法,任然没法了解信息的内容
数据加密的基本过程就是对原来为明文的文件或数据按照某种算法进行处理,使其成为不可读的一段代码,一般称为“密文”,使其只能在输入相应的密匙以后才能显示出原本内容,经过这样的途径来达到保护数据不被非法人窃取、阅读的目的。该过程的逆过程为解密,即将该编码信息转换为其原来数据的过程。
双方使用的同一个密匙,既能够加密又能够解密,这种加密方法称为对称加密,也称单密匙加密。
一对密匙由公钥和私钥组成(可使用不少对密匙)。私钥解密公钥加密数据,公钥解密私钥加密数据(私钥公钥能够互相加密解密)。
单项加密是不可逆的,也就是只能加密,不能解密。一般用来传输类型用户名和密码,直接将加密后的数据提交到后台,由于后台不须要知道用户名和密码,能够直接将接收到的加密后的数据存储到数据库
一般分为对称性加密算法和非对称性加密算法,对于对称性加密算法,信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,以后即是对数据进行 加解密了。非对称算法与之不一样,发送双方A,B事先均生成一堆密匙,而后A将本身的公有密匙发送给B,B将本身的公有密匙发送给A,若是A要给B发送消 息,则先须要用B的公有密匙进行消息加密,而后发送给B端,此时B端再用本身的私有密匙进行消息解密,B向A发送消息时为一样的道理。
package com.sxt.test; import org.apache.shiro.crypto.hash.Md5Hash; public class Md5HashTest { public static void main(String[] args) { // 对单个信息加密 Md5Hash md5 = new Md5Hash("123456"); System.out.println(md5.toString()); // 加密添加盐值 增大解密难度 md5 = new Md5Hash("123456","aaa"); System.out.println(md5.toString()); // 加密添加盐值 增大解密难度 迭代1024次 md5 = new Md5Hash("123456","aaa",1024); System.out.println(md5); } }
输出的结果:
使用MD5存在一个问题,相同的password生成的hash值是相同的,若是两个用户设置了相同的密码,那么数据库中会存储两个相同的值,这是极不安全的,加Salt能够在必定程度上解决这一问题,所谓的加Salt方法,就是加点‘佐料’。其基本想法是这样的,当用户首次提供密码时(一般是注册时)由系统自动往这个密码里撒一些‘佐料’,而后在散列,而当用户登陆时,系统为用户提供的代码上撒上相同的‘佐料’,而后散列,再比较散列值,来肯定密码是否正确。
加盐的原理:
给原文加入随机数生成新的MD5的值
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 获取帐号信息 String principal = (String) token.getPrincipal(); // 正常逻辑此处应该根据帐号去数据库中查询,此处咱们默认帐号为 root 密码123456 // 验证帐号 if(!"root".equals(principal)){ // 帐号错误 return null; } //String pwd = "123456"; // 12345 根据 盐值 aaa 加密获取的密文 //88316675d7882e3fdbe066000273842c 1次迭代的密文 //a7cf41c6537065fe724cc9980f8b5635 2次迭代的密文 String pwd = "88316675d7882e3fdbe066000273842c"; // 验证密码 AuthenticationInfo info = new SimpleAuthenticationInfo( principal, pwd,new SimpleByteSource("aaa"),"myrealm"); return info; }
[main] #定义凭证匹配器 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher #散列算法 credentialsMatcher.hashAlgorithmName=md5 #散列次数 credentialsMatcher.hashIterations=1 #将凭证匹配器设置到realm customRealm=com.dpb.realm.MyRealm customRealm.credentialsMatcher=$credentialsMatcher securityManager.realms=$customRealm
@Test public void test() { // 1.获取SecurityManager工厂对象 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); // 2.经过Factory对象获取SecurityManager对象 SecurityManager securityManager = factory.getInstance(); // 3.将SecurityManager对象添加到当前运行环境中 SecurityUtils.setSecurityManager(securityManager); // 4.获取Subject对象 Subject subject = SecurityUtils.getSubject(); AuthenticationToken token = new UsernamePasswordToken("root", "123456"); // 登陆操做 try { subject.login(token); } catch (UnknownAccountException e) { System.out.println("帐号出错..."); } catch(IncorrectCredentialsException e){ System.out.println("密码出错..."); } // 获取登陆的状态 System.out.println(subject.isAuthenticated()); }
完成。