shiro入门学习四
2017-11-29 10:56
344 查看
INI配置解析
从之前的shiro架构图可以看出,shiro是从根对象SecurityManager进行身份验证和授权的,这个对象是线程安全且整个应用只需要一个即可,因此Shiro提供了SecurityUtils让我们绑定它为全局的,shiro的类都是POJO的,很容易放到任何IOC容器管理,shiro提供的INI配置类似于Spring之类的IOC/DI容器,shiro支持的依赖注入:public空参构造器对象的创建、setter依赖注入。纯java代码写法
public void pureJava(){ DefaultSecurityManager securityManager = new DefaultSecurityManager(); //设置authenticator ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator(); authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy()); securityManager.setAuthenticator(authenticator); //设置authorizer ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer(); authorizer.setPermissionResolver(new WildcardPermissionResolver()); securityManager.setAuthorizer(authorizer); //设置Realm DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName("com.mysql.jdbc.Driver"); ds.setUrl("jdbc:mysql://localhost:3306/shiro"); ds.setName("root"); ds.setPassword(""); JdbcRealm jdbcRealm = new JdbcRealm(); jdbcRealm.setDataSource(ds); jdbcRealm.setPermissionsLookupEnabled(true); securityManager.setRealms(Arrays.asList((Realm)jdbcRealm)); //将SecurityManager设置到SecurityUtils中 SecurityUtils.setSecurityManager(securityManager); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang","123"); subject.login(token); Assert.assertTrue(subject.isAuthenticated()); subject.logout(); }
INI配置
[main] #authenticator配置 authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy authenticator.authenticationStrategy=$authenticationStrategy securityManager.authenticator=$authenticator #authorizer配置 authorizer=org.apache.shiro.authz.ModularRealmAuthorizer permissionResolver=org.apache.shiro.authz.permission.Wildcar fb21 dPermissionResolver authorizer.permissionResolver=$permissionResolver securityManager.authorizer=$authorizer #realm配置 dataSource=com.alibaba.druid.pool.DruidDataSource dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql://localhost:3306/shiro dataSource.username=root dataSource.password= jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm jdbcRealm.dataSource=$dataSource jdbcRealm.permissionsLookupEnabled=true securityManager.realms=$jdbcRealm
java代码同前面学习的一样,获取*.ini配置文件创建SecurityManager工厂类;如果接触过IOC容器,如上配置很容易理解:
1. 对象名 = 全限定类名 相当于调用 public 无参构造器创建对象
2. 对象名. 属性名 = 值 相当于调用 setter 方法设置常量值
3. 对象名. 属性名 =$对象引用 相当于调用 setter 方法设置对象引用
INI配置分类
ini配置文件类似于Java中的properties(key=value),不过提供了将 key/value 分类的特性,key 是每个部分不重复即可,而不是整个配置文件,后边的注入会覆盖前面的注入。如下是 INI 配置分类:[main] #提供了对根对象securityManager及其依赖的配置 securityManager=org.apache.shiro.mgt.DefaultSecurityManager ………… securityManager.realms=$jdbcRealm [users] #提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2 username=password,role1,role2 [roles] #提供了角色及权限之间关系的配置,角色=权限1,权限2 role1=permission1,permission2 [urls] #用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器 /index.html = anon /admin/** = authc, roles[admin], perms["permission1"]
编码/加密
进制编码
实际项目中,密码的存储不是明文存储,而应该是加密存储。shiro提供了64和16进制字符串编码/解码的API支持。/* * 64进制编码解码 */ @Test public void testBase64() { String str = "hello"; String base64Encoded = Base64.encodeToString(str.getBytes()); String str1 = Base64.decodeToString(base64Encoded); Assert.assertEquals(str, str1); } /* * 16进制编码解码 */ @Test public void hex(){ String str = "hello"; String base16Encoded = Hex.encodeToString(str.getBytes()); String str2 = new String(Hex.decode(base16Encoded.getBytes())); Assert.assertEquals(str, str2); }
散列算法
散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如 MD5、SHA 等。一般进行散列时最好提供一个 salt(盐),比如加密密码 “admin”,产生的散列值是 “21232f297a57a5a743894a0e4a801fc3”,可以到一些 md5 解密网站很容易的通过散列值得到密码 “admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如用户名和 ID(即盐);这样散列的对象是 “密码 + 用户名 +ID”,这样生成的散列值相对来说更难破解。shiro提供了Md2、Md5、Sha1、Sha256、Sha384、Sha512散列算法,同时提供了通用的散列支持SimpleHash,其内部使用了 Java 的 MessageDigest 实现。
SimpleHash simpleHash = new SimpleHash(String algorithmName,Object source,Object salt,int hashIterations); algorithName:加密方式,如"MD5" source:被加密数据 slat:盐值 hashIterations:加密次数
为了方便使用,Shiro提供了HashService接口,默认提供了DefaultHashService实现。
DefaultHashService hashService = new DefaultHashService(); hashService.setHashAlgorithmName("MD5"); hashService.setPrivateSalt(new SimpleByteSource("123")); hashService.setGeneratePublicSalt(true); hashService.setRandomNumberGenerator(new SecureRandomNumberGenerator());//用于生成公盐,默认就这个 hashService.setHashIterations(1); HashRequest request = new HashRequest.Builder() .setAlgorithmName("MD5").setSource(ByteSource.Util.bytes("hello")) .setSalt(ByteSource.Util.bytes("123")).setIterations(2).build(); String hex = hashService.computeHash(request).toHex(); /* 首先创建一个 DefaultHashService,默认使用 SHA-512 算法; * 以通过 hashAlgorithmName 属性修改算法,默认1; * 可以通过 privateSalt 设置一个私盐,其在散列时自动与用户传入的公盐混合产生一个新盐; * 可以通过 generatePublicSalt 属性在用户没有传入公盐的情况下是否生成公盐; * 可以设置 randomNumberGenerator 用于生成公盐; * 可以设置 hashIterations 属性来修改默认加密迭代次数; * 需要构建一个 HashRequest,传入算法、数据、公盐、迭代次数。 */
PasswordService/CredentialsMatcher
Shiro 提供了 PasswordService 及 CredentialsMatcher 用于提供加密密码及验证密码服务。Shiro 默认提供了 PasswordService 实现 DefaultPasswordService;CredentialsMatcher 实现 PasswordMatcher 及 HashedCredentialsMatcher(更强大)。DefaultPasswordService 配合 PasswordMatcher 实现简单的密码加密与验证服务
自定义Realm
public class MyRealm extends AuthorizingRealm { private PasswordService passwordService; public void setPasswordService(PasswordService passwordService) { this.passwordService = passwordService; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { return new SimpleAuthenticationInfo( "wu", passwordService.encryptPassword(new String((char[])token.getCredentials())), getName()); } }
ini配置
[main] passwordService=org.apache.shiro.authc.credential.DefaultPasswordService hashService=org.apache.shiro.crypto.hash.DefaultHashService passwordService.hashService=$hashService hashFormat=org.apache.shiro.crypto.hash.format.Shiro1CryptFormat passwordService.hashFormat=$hashFormat hashFormatFactory=org.apache.shiro.crypto.hash.format.DefaultHashFormatFactory passwordService.hashFormatFactory=$hashFormatFactory passwordMatcher=org.apache.shiro.authc.credential.PasswordMatcher passwordMatcher.passwordService=$passwordService myRealm=com.shiro.realm.MyRealm myRealm.passwordService=$passwordService myRealm.credentialsMatcher=$passwordMatcher securityManager.realms=$myRealm
passwordService 使用 DefaultPasswordService,如果有必要也可以自定义;
hashService 定义散列密码使用的 HashService,默认使用 DefaultHashService(默认 SHA-256 算法);
hashFormat 用于对散列出的值进行格式化,默认使用 Shiro1CryptFormat,另外提供了 Base64Format 和 HexFormat,对于有 salt 的密码请自定义实现 ParsableHashFormat 然后把 salt 格式化到散列值中;
hashFormatFactory 用于根据散列值得到散列的密码和 salt;因为如果使用如 SHA 算法,那么会生成一个 salt,此 salt 需要保存到散列后的值中以便之后与传入的密码比较时使用;默认使用 DefaultHashFormatFactory;
passwordMatcher 使用 PasswordMatcher,其是一个 CredentialsMatcher 实现;
将 credentialsMatcher 赋值给 myRealm,myRealm 间接继承了 AuthenticatingRealm,其在调用 getAuthenticationInfo 方法获取到 AuthenticationInfo 信息后,会使用 credentialsMatcher 来验证凭据是否匹配,如果不匹配将抛出 IncorrectCredentialsException 异常。
HashedCredentialsMatcher 实现密码验证服务
HashedCredentialsMatcher,和之前的 PasswordMatcher 不同的是,它只用于密码验证,且可以提供自己的盐,而不是随机生成盐,且生成密码散列值的算法需要自己写,因为能提供自己的盐。
public class MyRealm2 extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String algorithmName = "MD5"; String username = String.valueOf(token.getPrincipal()); String password = String.valueOf(token.getCredentials()); String salt1 = username; String salt2 = new SecureRandomNumberGenerator().nextBytes().toHex(); //随机数 int hashIterations = 2; SimpleHash hash = new SimpleHash(algorithmName,password,salt1+salt2,hashIterations); String encodedPassword = hash.toHex(); //ByteSource.Util.bytes(salt1+salt2)为生成的新盐 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,password,ByteSource.Util.bytes(salt1+salt2),getName()); return info; } }
ini配置
[main] credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher credentialsMatcher.hashAlgorithmName=md5 credentialsMatcher.hashIterations=2 #表示是否存储散列后的密码为 16 进制,需要和生成密码时的一样,默认是 base64; credentialsMatcher.storedCredentialsHexEncoded=true myRealm=com.shiro.realm.MyRealm2 myRealm.credentialsMatcher=$credentialsMatcher securityManager.realms=$myRealm
密码重试次数限制
如在 1 个小时内密码最多重试 5 次,如果尝试次数超过 5 次就锁定 1 小时,1 小时后可再次重试,如果还是重试失败,可以锁定如 1 天,以此类推,防止密码被暴力破解。我们通过继承 HashedCredentialsMatcher,且使用 Ehcache 记录重试次数和超时时间。
public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher { private Ehcache passwordRetryCache; public RetryLimitHashedCredentialsMatcher() { CacheManager cacheManager = CacheManager.newInstance(CacheManager.class.getClassLoader().getResource("ehcache.xml")); passwordRetryCache = cacheManager.getCache("passwordRetryCache"); } @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { String username = (String)token.getPrincipal(); //retry count + 1 Element element = passwordRetryCache.get(username); if(element == null) { element = new Element(username , new AtomicInteger(0)); passwordRetryCache.put(element); } AtomicInteger retryCount = (AtomicInteger)element.getObjectValue(); if(retryCount.incrementAndGet() > 5) { //if retry count > 5 throw throw new ExcessiveAttemptsException(); } boolean matches = super.doCredentialsMatch(token, info); if(matches) { //clear retry count passwordRetryCache.remove(username); } return matches; } }
ehcache.xml配置
<ehcache name="es"> <diskStore path="java.io.tmpdir"/> <!-- 登录记录缓存 锁定1小时 --> <cache name="passwordRetryCache" maxEntriesLocalHeap="2000" eternal="false" timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false" statistics="true"> </cache> </ehcache>
相关文章推荐
- 4000 Shiro入门学习二
- Shiro入门学习三
- Shiro学习总结(二)--Shiro的入门小例子
- 权限学习--Shiro入门学习
- Shiro系列学习 -- 入门篇
- Shiro 学习记录 Shiro 入门程序
- Shiro入门学习六
- Shiro入门学习三
- shiro学习(一)---认证入门程序
- Shiro入门学习五
- Shiro入门学习一
- java安全框架-Shiro学习笔记(一)-入门小案例
- Shiro入门学习二
- Shiro入门学习四
- Shiro入门学习一
- 18.03.09,web学习第七十天,bos第十天,shiro框架入门
- apache shiro学习笔记--02(入门案例)
- shiro入门学习
- 深入学习:Windows下Git入门教程(上)
- 机器人操作系统ROS Indigo 入门学习(19)——接下来做什么