您的位置:首页 > 其它

Shiro-学习总结-认证之自定义realm

2017-02-22 22:37 381 查看
用户自定义认证需要实现Realm,其实现类如下图所示:



推荐去继续org.apache.shiro.realm.AuthorizingRealm

public class ShiroRealm extends AuthorizingRealm{//AuthenticatingRealm {//implements Realm {

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 获取用户用户身份验证信息
return null;
}

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取用户身份获取授权信息
return null;
}
}

简单举例:

pom.xml文件配置:

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>

<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>


创建MyRealm继承AuthorizingRealm

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

return null;
}

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//从token中取出用户身份信息
String userName = (String) token.getPrincipal();

//根据userName去数据库中查询
//....
//模拟从数据库中查询的密码
String password = "123456";

//查询不到返回Null

//查询到返回认证信息AuthorizationInfo
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName,
password, getName());

return simpleAuthenticationInfo;

}


创建shiroRealm.ini配置文件:

[main]
myRealm = Lee.realm.MyRealm
securityManager.realms = $myRealm


创建测试程序:

public class Quickstart {

private static final Logger log = LoggerFactory.getLogger(Quickstart.class);

public static void main(String[] args) {
//构造SecurityManager
//Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiroRealm.ini");
SecurityManager securityManager = factory.getInstance();

SecurityUtils.setSecurityManager(securityManager);
//获取当前subject
Subject currentUser = SecurityUtils.getSubject();

//测试当前用户是否已经登录即执行认证
if (!currentUser.isAuthenticated()) {
//用户名、密码封装为UsernamePasswordToken
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "123456");
//记住我
token.setRememberMe(true);
try {
//登录即执行认证
currentUser.login(token);
} catch (UnknownAccountException uae) {//没有此账户
log.info("There is no user with username of " + token.getPrincipal());
System.out.println("==================="+uae+"===================");
} catch (IncorrectCredentialsException ice) {//密码错误
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
System.out.println("==================="+ice+"===================");
} catch (LockedAccountException lae) {//账户被锁定
log.info("The account for username " + token.getPrincipal() + " is locked.  " +
"Please contact your administrator to unlock it.");
System.out.println("==================="+lae+"===================");
}catch (AuthenticationException ae) {
ae.printStackTrace();
}
}
System.out.println("是否认证通过"+currentUser.isAuthenticated());
//退出登录
currentUser.logout();
System.exit(0);
}
}


密码认证过程中没有加入盐值,只是简单的认证。认证流程大体如下:

进入org.apache.shiro.authc.pam.ModularRealmAuthenticator类的下面方法:

protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);//单个realm
} else {
return doMultiRealmAuthentication(realms, authenticationToken);//多个realm
}
}


然后调用org.apache.shiro.realm.AuthenticatingRealm类的下面方法:

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

AuthenticationInfo info = getCachedAuthenticationInfo(token);
if (info == null) {
//otherwise not cached, perform the lookup:
info = doGetAuthenticationInfo(token);//获取用户身份信息,该方法由我们自定义的MyRealm覆盖
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}

if (info != null) {
assertCredentialsMatch(token, info);//获取用户身份信息后,进行密码的比对。token为用户输入的信息,info为我们从数据源中查询的信息
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
}

return info;
}


之后调用本类中的assertCredentialsMatch方法:

protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();//默认为SimpleCredentialsMatcher
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {//进行密码比对
//not successful - throw an exception to indicate this:
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
"credentials during authentication.  If you do not wish for credentials to be examined, you " +
"can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}


密码比对会调用org.apache.shiro.authc.credential.SimpleCredentialsMatcher类的如下方法:

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenCredentials = getCredentials(token);
Object accountCredentials = getCredentials(info);
return equals(tokenCredentials, accountCredentials);//进行简单的equals比较
}


上面的举例中,密码比对为明文。开发中肯定不会这样使用。一般数据源中存储的都是加密后的密码及盐值,下面我们对上面的例子进行更改,以md5为例:



需要修改ini配置文件,及realm
md5Shiro.ini配置文件:
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
#设置散列算法
credentialsMatcher.hashAlgorithmName=MD5
#散列次数
credentialsMatcher.hashIterations=1024
#是否加盐
credentialsMatcher.hashSalted=true
#将凭证匹配器设置到realm中
md5Realm = Lee.realm.Md5Realm
md5Realm.credentialsMatcher=$credentialsMatcher

securityManager.realms = $md5Realm


Md5Realm代码如下:
public class Md5Realm extends AuthorizingRealm{

@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//根据用户输入的账户模拟查询数据库
//数据库中保存的用户密码
String dbaPassword="b8d63fc060e2b5651e8cb4e71ba61e6f";
//数据库中保存的盐值
ByteSource credentialsSalt=ByteSource.Util.bytes("aaa");

//用户输入的登录密码
Object principals=token.getPrincipal();

SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principals, dbaPassword, credentialsSalt,getName());

return authenticationInfo;
}

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// TODO Auto-generated method stub
return null;
}


提供小程序用于生成数据库中加密后的密码:
public static void main(String[] args) {
//加密方式
String hashAlgorithmName = "MD5";
//用户输入的密码
Object credentials = "123456";
//盐值
ByteSource credentialsSalt=ByteSource.Util.bytes("aaa");
//参数一:散列算法	参数二:原始密码   参数三:盐值    参数四:散列次数
System.out.println(new SimpleHash(hashAlgorithmName, credentials, credentialsSalt, 1024));

//参数一:原始密码   参数二:盐值    参数三:散列次数
Md5Hash md5Hash = new Md5Hash("123456", "aaa", 1024);
System.out.println(md5Hash.toString());
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  shiro