首页 > 代码库 > (转) shiro权限框架详解04-shiro认证
(转) shiro权限框架详解04-shiro认证
http://blog.csdn.net/facekbook/article/details/54906635
shiro认证
本文介绍shiro的认证功能
- 认证流程
- 入门程序(用户登录和退出)
- 自定义Realm
- 散列算法
认证流程
入门程序(用户登录和退出)
创建Java项目
jdk版本:1.7.0_67
加入shiro的jar包以及依赖包
log4j.properties日志文件配置
log4j.rootLogger=debug, stdoutlog4j.appender.stdout=org.apache.log4j.ConsoleAppenderlog4j.appender.stdout.layout=org.apache.log4j.PatternLayoutlog4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
eclipse中ini文件打开方式配置
shiro使用ini文件作为配置文件。所以需要修改eclipse中ini文件的打开方式,默认的话ini文件是使用记事本打开。具体如下图:
创建ini配置文件
在classpath路径下创建shiro-first.ini文件,文件内容是测试用户的账号和密码。内容如下:
[users]zhangsan=123lisi=456
认证代码
@Test public void testLoginAndLogOut() { // 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-first.ini"); // 通过工厂创建SecurityManager SecurityManager securityManager = factory.getInstance(); // 将SecurityManager设置到运行环境中 SecurityUtils.setSecurityManager(securityManager); //创建一个Subject实例,该实例认证需要使用上面创建的SecurityManager Subject subject = SecurityUtils.getSubject(); //创建token令牌,账号和密码是ini文件中配置的 AuthenticationToken token = new UsernamePasswordToken("zhangsan", "123"); try { //用户登录 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); } //用户认证状态 Boolean isAuthenticated = subject.isAuthenticated(); System.out.println("用户认证状态:"+isAuthenticated);//输出true //用户退出 subject.logout(); isAuthenticated = subject.isAuthenticated(); System.out.println("用户认证状态:"+isAuthenticated);//输出false }
认证执行流程
1.创建token令牌,token中有用户提交的认证信息即账号和密码。
2.执行subject.login(token),最终由securityManager通过 Authenticator进行认证。
3.Authenticator的实现ModuleRealmAuthenticator调用realm从init文件读取用户真实的账号和密码,这里使用的是IniRealm(Shiro自带)
4.IniRealm先根据token中的账号去ini中找该账号,如果找不到则给ModuleRealmAuthenticator返回null,如果找到则匹配密码,匹配密码成功则认证通过。
常见的异常
- UnknownAccountException
账号不存在异常如下:
org.apache.shiro.authc.UnknownAccountException: Realm [org.apache.shiro.realm.text.IniRealm@9cdc393] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - zhangsan1
- IncorrectCredentialsException
当输入密码错误会抛出此异常,如下:
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.
更多异常信息如下:
DisabledAccountException(帐号被禁用)
LockedAccountException(帐号被锁定)
ExcessiveAttemptsException(登录失败次数过多)
ExpiredCredentialsException(凭证过期)等
类结构如下图
自定义Realm
上面的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息。但是实际情况中大部分情况下是从数据库中获取用户信息,所以需要自定义realm。
Shiro中Realm
最基础的是Realm接口,CachingRealm负责缓存管理,AuthenticatingRealm负责认证,AuthorizingRealm负责授权,通常自定义的Realm继承AuthorizingRealm。
自定义Realm代码
通过继承AuthorizingRealm
类实现
public class CustomRealm extends AuthorizingRealm { /** * 认证方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 从token中获取用户身份信息 String username = (String) token.getPrincipal(); // 正常逻辑应该是通过username查询数据库。 // 如果查询不到返回null if (!"zhangsan".equals(username)) {// 这里模仿查询不到 return null; } // 模拟从数据获取密码 String password = "123"; // 返回认证信息交由父类AuthorizingRealm认证 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, ""); return authenticationInfo; } /** * 授权方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; }}
ini配置文件
新建shiro-realm.ini文件。内容如下:
[main]#自定义realmcustomRealm=com.knight.shiro.realm.CustomRealm#将realm设置到securityManagersecurityManager.realm=$customRealm
这里不需要配置users,是因为我们这里模拟users的获取来自数据库。
测试代码
@Test public void testCustomeRealm() { // 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini"); // 通过工厂创建SecurityManager SecurityManager securityManager = factory.getInstance(); // 将SecurityManager设置到运行环境中 SecurityUtils.setSecurityManager(securityManager); //创建一个Subject实例,该实例认证需要使用上面创建的SecurityManager Subject subject = SecurityUtils.getSubject(); //创建token令牌,账号和密码是ini文件中配置的 //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "123");//账号密码正确token //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "1234");//密码错误异常token AuthenticationToken token = new UsernamePasswordToken("zhangsan1", "123");//账号错误异常token try { //用户登录 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); } //用户认证状态 Boolean isAuthenticated = subject.isAuthenticated(); System.out.println("用户认证状态:"+isAuthenticated);//输出true }
散列算法
散列算法一般用于生成一段文本的摘要信息,散列算法不可逆,也就是将内容生成摘要,但是反过来通过摘要生成内容是不可以的。散列算法常用于对密码进行散列,常用的散列算法有MD5、SHA。一般散列算法需要提供一个salt(盐)与原始内容生成摘要,这样做的目的是为了安全性。
例子
Md5Hash md5Hash = new Md5Hash("111111"); System.out.println("md5加密,不加盐:"+md5Hash.toString()); //md5加密,加盐,一次hash String password_md5_sale_1 = new Md5Hash("11111", "aga23", 1).toString(); System.out.println("md5加密,加盐,一次hash:"+password_md5_sale_1); //md5加密,加盐,两次hash String password_md5_sale_2 = new Md5Hash("11111", "aga23", 2).toString(); System.out.println("md5加密,加盐,两次hash:"+password_md5_sale_2);//相当于md5(md5(‘1111‘)) //使用simpleHash String simpleHash = new SimpleHash("MD5", "11111", "aga23", 1).toString(); System.out.println(simpleHash);
在realm中使用散列算法
实际系统中是将盐和散列后的值存储在数据库中,自定义realm从数据库取出盐和加密后的值由shiro完成密码校验。
自定义支持散列的realm
public class CustomRealmMd5 extends AuthorizingRealm { /** * 认证方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 从token中获取用户身份信息 String username = (String) token.getPrincipal(); // 正常逻辑应该是通过username查询数据库。 // 如果查询不到返回null if (!"zhangsan".equals(username)) {// 这里模仿查询不到 return null; } // 模拟从数据获取密码 String password = "fdf907b0d3f427b9ffa2f86f213d1afd"; // 盐 String salt = "aga23"; // 返回认证信息交由父类AuthorizingRealm认证 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, password, ByteSource.Util.bytes(salt), ""); return authenticationInfo; } /** * 授权方法 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; }}
支持散列的realm配置
[main]#定义凭证匹配器credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher#设置散列算法credentialsMatcher.hashAlgorithmName=md5#设置散列次数credentialsMatcher.hashIterations=1#将凭证匹配器设置到realmcustomRealm=com.knight.shiro.realm.CustomRealmMd5customRealm.credentialsMatcher=$credentialsMatchersecurityManager.realms=$customRealm
测试代码
注意修改配置文件的路径
@Test public void testCustomeRealmMd5() { // 构建SecurityManager工厂,IniSecurityManagerFactory可以从ini文件中初始化SecurityManager环境 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm-md5.ini"); // 通过工厂创建SecurityManager SecurityManager securityManager = factory.getInstance(); // 将SecurityManager设置到运行环境中 SecurityUtils.setSecurityManager(securityManager); //创建一个Subject实例,该实例认证需要使用上面创建的SecurityManager Subject subject = SecurityUtils.getSubject(); //创建token令牌,账号和密码是ini文件中配置的 //AuthenticationToken token = new UsernamePasswordToken("zhangsan", "11111");//账号密码正确token AuthenticationToken token = new UsernamePasswordToken("zhangsan", "1234");//密码错误异常token //AuthenticationToken token = new UsernamePasswordToken("zhangsan1", "11111");//账号错误异常token try { //用户登录 subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); } //用户认证状态 Boolean isAuthenticated = subject.isAuthenticated(); System.out.println("用户认证状态:"+isAuthenticated);//输出true }
该文章涉及的代码
代码地址
(转) shiro权限框架详解04-shiro认证