cryptography:密码管理,提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
- 比如md5散列算法。
五、为什么使用Shiro
我们在使用URL拦截的时候,要将所有的URL都配置起来,繁琐、不易维护
而我们的Shiro实现系统的权限管理,有效提高开发效率,从而降低开发成本。
六、Shiro认证
6.1导入jar包
我们使用的是Maven的坐标就行了
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-quartz</artifactId> <version>1.2.3</version> </dependency>
当然了,我们也可以把Shiro相关的jar包全部导入进去
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency>
6.2Shiro认证流程
这里写图片描述
6.2.1通过配置文件创建工厂
这里写图片描述
// 用户登陆和退出 @Test public void testLoginAndLogout() { // 创建securityManager工厂,通过ini配置文件创建securityManager工厂 Factory<SecurityManager> factory = new IniSecurityManagerFactory( "classpath:shiro-first.ini"); // 创建SecurityManager SecurityManager securityManager = factory.getInstance(); // 将securityManager设置当前的运行环境中 SecurityUtils.setSecurityManager(securityManager); // 从SecurityUtils里边创建一个subject Subject subject = SecurityUtils.getSubject(); // 在认证提交前准备token(令牌) // 这里的账号和密码 将来是由用户输入进去 UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "111111"); try { // 执行认证提交 subject.login(token); } catch (AuthenticationException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 是否认证通过 boolean isAuthenticated = subject.isAuthenticated(); System.out.println("是否认证通过:" + isAuthenticated); // 退出操作 subject.logout(); // 是否认证通过 isAuthenticated = subject.isAuthenticated(); System.out.println("是否认证通过:" + isAuthenticated); }
这里写图片描述
6.3小结
ModularRealmAuthenticator作用进行认证,需要调用realm查询用户信息(在数据库中存在用户信息)
ModularRealmAuthenticator进行密码对比(认证过程)。
realm:需要根据token中的身份信息去查询数据库(入门程序使用ini配置文件),如果查到用户返回认证信息,如果查询不到返回null。
6.4自定义realm
从第一个认证程序我们可以看见,我们所说的流程,是认证器去找realm去查询我们相对应的数据。而默认的realm是直接去与配置文件来比对的,一般地,我们在开发中都是让realm去数据库中比对。
因此,我们需要自定义realm
这里写图片描述
public class CustomRealm extends AuthorizingRealm { // 设置realm的名称 @Override public void setName(String name) { super.setName("customRealm"); } // 用于认证 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { // token是用户输入的 // 第一步从token中取出身份信息 String userCode = (String) token.getPrincipal(); // 第二步:根据用户输入的userCode从数据库查询 // .... // 如果查询不到返回null //数据库中用户账号是zhangsansan /*if(!userCode.equals("zhangsansan")){// return null; }*/ // 模拟从数据库查询到密码 String password = "111112"; // 如果查询到返回认证信息AuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( userCode, password, this.getName()); return simpleAuthenticationInfo; } // 用于授权 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { // TODO Auto-generated method stub return null; } }
6.5配置realm
需要在shiro-realm.ini配置realm注入到securityManager中。
这里写图片描述
6.6测试自定义realm
同上边的入门程序,需要更改ini配置文件路径:
同上边的入门程序,需要更改ini配置文件路径: Factory<SecurityManager> factory = new IniSecurityManagerFactory( "classpath:shiro-realm.ini");
6.7散列算法
我们如果知道md5,我们就会知道md5是不可逆的,但是如果设置了一些安全性比较低的密码:111111…即时是不可逆的,但还是可以通过暴力算法来得到md5对应的明文…
建议对md5进行散列时加salt(盐),进行加密相当 于对原始密码+盐进行散列。\
正常使用时散列方法:
- 在程序中对原始密码+盐进行散列,将散列值存储到数据库中,并且还要将盐也要存储在数据库中。
测试:
public class MD5Test { public static void main(String[] args) { //原始 密码 String source = "111111"; //盐 String salt = "qwerty"; //散列次数 int hashIterations = 2; //上边散列1次:f3694f162729b7d0254c6e40260bf15c //上边散列2次:36f2dfa24d0a9fa97276abbe13e596fc //构造方法中: //第一个参数:明文,原始密码 //第二个参数:盐,通过使用随机数 //第三个参数:散列的次数,比如散列两次,相当 于md5(md5('')) Md5Hash md5Hash = new Md5Hash(source, salt, hashIterations); String password_md5 = md5Hash.toString(); System.out.println(password_md5); //第一个参数:散列算法 SimpleHash simpleHash = new SimpleHash("md5", source, salt, hashIterations); System.out.println(simpleHash.toString()); } }
6.8自定义realm支持md5
自定义realm
public class CustomRealmMd5 extends AuthorizingRealm { // 设置realm的名称 @Override public void setName(String name) { super.setName("customRealmMd5"); } // 用于认证 @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { // token是用户输入的 // 第一步从token中取出身份信息 String userCode = (String) token.getPrincipal(); // 第二步:根据用户输入的userCode从数据库查询 // .... // 如果查询不到返回null // 数据库中用户账号是zhangsansan /* * if(!userCode.equals("zhangsansan")){// return null; } */ // 模拟从数据库查询到密码,散列值 String password = "f3694f162729b7d0254c6e40260bf15c"; // 从数据库获取salt String salt = "qwerty"; //上边散列值和盐对应的明文:111111 // 如果查询到返回认证信息AuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo( userCode, password, ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; } // 用于授权 @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { // TODO Auto-generated method stub return null; } }
配置文件:
这里写图片描述
测试:
// 自定义realm实现散列值匹配 @Test public void testCustomRealmMd5() { // 创建securityManager工厂,通过ini配置文件创建securityManager工厂 Factory<SecurityManager> factory = new IniSecurityManagerFactory( "classpath:shiro-realm-md5.ini"); // 创建SecurityManager SecurityManager securityManager = factory.getInstance(); // 将securityManager设置当前的运行环境中 SecurityUtils.setSecurityManager(securityManager); // 从SecurityUtils里边创建一个subject Subject subject = SecurityUtils.getSubject(); // 在认证提交前准备token(令牌) // 这里的账号和密码 将来是由用户输入进去 UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "222222"); try { // 执行认证提交 subject.login(token); } catch (AuthenticationException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 是否认证通过 boolean isAuthenticated = subject.isAuthenticated(); System.out.println("是否认证通过:" + isAuthenticated); }
七、总结
- 用户认证和用户授权是Shiro的基础,用户认证其实上就是登陆操作、用户授权实际上就是对资源拦截的操作。
- 权限管理的模型一般我们都将资源放在权限表中进行管理起来。
- 我们可以基于角色拦截,也可以基于资源拦截。要是基于角色拦截的话,那么如果角色的权限发生变化了,那就需要修改代码了。推荐使用基于资源进行拦截
- 这次URL拦截,我们使用一个JavaBean来封装所有的认证信息。当用户登陆了之后,我们就把用户对菜单栏的访问、对资源的访问权限都封装到该JavaBean中
- 当使用拦截器进行用户认证的时候,我们只要判断Session域有没有JavaBen对象即可了。
- 当时候拦截器进行用户授权的时候,我们要判断JavaBean中的权限是否能够访问该资源。
- 以前URL拦截的方式需要把所有的URL都在数据库进行管理。非常麻烦,不易维护。
- 我们希望Shiro去认证的时候是通过realm去数据库查询数据的。而我们reaml默认是查询配置文件的数据的。
- 因此,我们需要自定义reaml,使得它是去数据库查询数据。只要继承AuthorizingRealm类就行了。
- 当然了,自定义后的reaml也需要在配置文件中写上我们的自定义reaml的位置的。
- 散列算法就是为了让密码不被别人给破解。我们可对原始的密码加盐再进行散列,这就加大了破解的难度了。
- 自定义的reaml也是支持散列算法的,相同的,还是需要我们在配置文件中配置一下就好了。