一、Realm简介
1.什么是Realm
Realm 是可以访问程序特定的安全数据如用户、角色、权限等的一个组件。Realm会将这些程序特定的安全数据转换成一种shiro可以理解的形式,shiro就可以依次提供容易理解的Subject程序API而不管有多少数据源或者程序中你的数据如何组织。
Realm 通常和数据源如数据库、LDAP目录、文件系统或者其它类似的数据源是一对一的关系,所以,可以用数据源相应的API如JDBC、File IO、 hibernate 或者JPA以及其它的API来实现Realm接口,从而获取授权的相关数据(角色、权限等)。
realm本质上就是一个指定安全的DAO。
因为大部分这类数据源通常都会同时存储认证数据(如密码)和授权数据(如角色和权限),所以每一个Shiro Realm都可以同时执行认证和授权操作。
总结: shiro要进行身份验证,就要从realm中获取相应的身份信息来进行验证,简单来说,我们可以自行定义realm,在realm中,从数据库获取身份信息,然后和 用户输入的身份信息进行匹配。这一切都由我们自己来定义。
2.为什么要用Realm
在前面的例子中,我们将身份信息(用户名/密码/角色/权限)写在配置文件中,但是实际开发中,这些身份信息应该保存在数据中,因此我们需要自定义Realm来从数据中获取身份信息,进行验证。
3.Realm 配置
如果使用Shiro的INI配置文件,你能够自定义及引用Realm,就像在[main]项中的任何其他对象一样,但它们在securityManager中采用两种方法之一进行配置:显式或隐式。
显式赋值:
这是一个显式的配置方法,在定义一个或多个Realm后,将它们作为securityManager对象的集合属性。
例如:
fooRealm = com.turing.foo.Realm
barRealm = com.turing.another.Realm
bazRealm = com.turing.baz.Realm
securityManager.realms = $fooRealm, $barRealm,$bazRealm
隐式赋值:
如果因为某些原因(可能是定义的Realm太多?)不想为securityManager.realms指定,我们也可以使用隐式方式。
也就是说,把上面的配置改成如下形式就是隐式方式了:
fooRealm = com.company.foo.Realm
barRealm = com.company.another.Realm
bazRealm = com.company.baz.Realm
隐式方式其实就是不指定,只定义(define),Shiro会搜索配置中所有的Realm并将它们一一指定给securityManager。
使用隐式方式时只要稍微改一下Realm的定义,Shiro就可能会给我们来个惊喜。
4.Realms的认证实现
Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法通常会在org.apache.shiro.realm.AuthenticatingRealm中实现,当然,这个方法中会调用到具体realm实现的方法。
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
这是对所有Realm getAuthenticationInfo 实现的最高级别的工作流,验证通过后,就返回一个非空的AuthenticationInfo 实例来代表来自于该数据源的Subject 帐户信息。
5.身份验证流程
身份验证: 即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。
5.1、基本概念
Principals(身份):Subject 的 identifying attributes(标识属性)。比如我们登录提交的用户名。
Credentials(凭证):用来作为一种起支持作用的证据,此证据包含身份证明。比如我们登录提供的密码。
5.2、认证的基本步骤
收集Subjects 提交的Principals(身份)和Credentials(凭证);
提交Principals(身份)和Credentials(凭证)进行身份验证;
如果提交成功,则允许访问,否则重新进行身份验证或者阻止访问。
AuthenticationToken:Shiro 中代表提交的 Principals(身份) 和 Credentials (凭证) 的身份验证系统的最基本接口。
UsernamePasswordToken :AuthenticationToken 的接口的实现类,支持最常见的用户名/密码的身份验证
提交用户名/密码进行认证
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
5.4、认证顺序
5.5、认证过程
Step 1:应用程序代码调用 Subject.login 方法,传递创建好的包含终端用户的 Principals(身份)和 Credentials(凭证)的 AuthenticationToken 实例
Step 2:Subject 实例,通常为 DelegatingSubject(或子类)委托应用程序的 SecurityManager 通过调用 securityManager.login(token) 开始真正的验证。
Step 3:SubjectManager 接收 token,调用内部的 Authenticator 实例调用 authenticator.authenticate(token)。 Authenticator 通常是一个 ModularRealmAuthenticator 实例,支持在身份验证中协调一个或多个Realm 实例。
Step 4:如果应用程序中配置了一个以上的 Realm,ModularRealmAuthenticator 实例将利用配置好的AuthenticationStrategy 来启动 Multi-Realm 认证尝试。在Realms 被身份验证调用之前,期间和以后,AuthenticationStrategy 被调用使其能够对每个Realm 的结果作出反应。
Step 5:每个配置的 Realm 用来帮助看它是否支持提交的AuthenticationToken。如果支持,那么支持 Realm 的 getAuthenticationInfo 方法将会伴随着提交的 token 被调用。getAuthenticationInfo 方法有效地代表一
个特定 Realm 的单一的身份验证尝试。
二、使用Realm
首先自定义一个MyRealm类,继承AuthorizingRealm,父类AuthorizingRealm将获取Subject相关信息分成两步:获取身份验证信息(doGetAuthenticationInfo)及授权信息(doGetAuthorizationInfo);
1、doGetAuthenticationInfo 获取身份验证相关信息:首先根据传入的用户名获取User信息;然后如果user为空,那么抛出没找到帐号异常UnknownAccountException;如果user找到但锁定了抛出锁定异常LockedAccountException;最后生成AuthenticationInfo信息,交给间接父类AuthenticatingRealm使用CredentialsMatcher进行判断密码是否匹配,如果不匹配将抛出密码错误异常IncorrectCredentialsException;另外如果密码重试此处太多将抛出超出重试次数异常ExcessiveAttemptsException;
2、doGetAuthorizationInfo 获取授权信息:PrincipalCollection是一个身份集合,因为我们现在就一个Realm,所以直接调用getPrimaryPrincipal得到之前传入的用户名即可;然后根据用户名调用UserService接口获取角色及权限信息。
2.1、支持单个Realm
自定义一个Realm继承AuthorizingRealm实现其中的doGetAuthenticationInfo和doGetAuthorizationInfo方法:
package com.fendo.temp;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyRealm1 extends AuthorizingRealm {
private static final transient Logger log = LoggerFactory.getLogger(MyRealm1.class);
/**
* 获取身份信息,我们可以在这个方法中,从数据库获取该用户的权限和角色信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("----------doGetAuthorizationInfo方法被调用----------");
String username = (String) getAvailablePrincipal(principals);
//我们可以通过用户名从数据库获取权限/角色信息
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//权限
Set s = new HashSet();
s.add("printer:print");
s.add("printer:query");
info.setStringPermissions(s);
//角色
Set r = new HashSet();
r.add("role1");
info.setRoles(r);
return info;
}
/**
* 在这个方法中,进行身份验证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//用户名
String username = (String) token.getPrincipal();
log.info("username:"+username);
//密码
String password = new String((char[])token.getCredentials());
log.info("password:"+password);
//从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作
if(!"admin".equals(username)){
throw new UnknownAccountException();
}
if(!"123".equals(password)){
throw new IncorrectCredentialsException();
}
//身份验证通过,返回一个身份信息
AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName());
return aInfo;
}
}
package com.fendo.temp;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Main {
private static final transient Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
//获取SecurityManager的实例
Factory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currenUser = SecurityUtils.getSubject();
//如果还未认证
if(!currenUser.isAuthenticated()){
UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
token.setRememberMe(true);
try {
currenUser.login(token);
} catch (UnknownAccountException uae) {
log.info("没有该用户: " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info( token.getPrincipal() + " 的密码不正确!");
} catch (LockedAccountException lae) {
log.info( token.getPrincipal() + " 被锁定 ,请联系管理员");
}catch (AuthenticationException ae) {
//其他未知的异常
}
}
if(currenUser.isAuthenticated())
log.info("用户 "+currenUser.getPrincipal() +" 登录成功");
//是否有role1这个角色
if(currenUser.hasRole("role1")){
log.info("有角色role1");
}else{
log.info("没有角色role1");
}
//是否有对打印机进行打印操作的权限
if(currenUser.isPermitted("printer:print")){
log.info("可以对打印机进行打印操作");
}else {
log.info("不可以对打印机进行打印操作");
}
}
}
#声明一个realm
MyRealm1=com.fendo.temp.MyRealm1
#指定securityManager的realms实现
securityManager.realms=$MyRealm1
2.2、支持多个Realm
有时候不单单只有一个Realm而是由多个,Shiro支持多个Realm验证,新建一个MyRealm2
package com.fendo.temp;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyRealm2 extends AuthorizingRealm{
private static final transient Logger log = LoggerFactory.getLogger(Main.class);
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) getAvailablePrincipal(principals);
//通过用户名从数据库获取权限字符串
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//权限
Set s = new HashSet();
s.add("printer:print");
s.add("printer:query");
info.setStringPermissions(s);
//角色
Set r = new HashSet();
r.add("role1");
info.setRoles(r);
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
log.info("MyRealm2开始认证。。。。。。");
//用户名
String username = (String) token.getPrincipal();
log.info("username:"+username);
//密码
String password = new String((char[])token.getCredentials());
log.info("password:"+password);
//从数据库获取用户名密码进行匹配,这里为了方面,省略数据库操作
if(!"admin".equals(username)){
throw new UnknownAccountException();
}
if(!"123".equals(password)){
throw new IncorrectCredentialsException();
}
//身份验证通过
AuthenticationInfo aInfo = new SimpleAuthenticationInfo(username,password,getName());
return aInfo;
}
}
#声明一个realm
MyRealm1=com.fendo.temp.MyRealm1
MyRealm2=com.fendo.temp.MyRealm2
#ָ配置验证器
securityManager.realms=$MyRealm1
#配置验证器
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
#配置策略
# AllSuccessfulStrategy 表示 MyRealm1和MyRealm2 认证都通过才算通过
authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
#将验证器和策略关联起来
authenticator.authenticationStrategy = $authcStrategy
#配置验证器所使用的Realm
authenticator.realms=$MyRealm2,$MyRealm1
#把Authenticator设置给securityManager
securityManager.authenticator = $authenticator
##########################################################################
# 1. AtLeastOneSuccessfulStrategy :如果一个(或更多)Realm 验证成功,则整体的尝试被认
# 为是成功的。如果没有一个验证成功,则整体尝试失败。
# 2. FirstSuccessfulStrategy 只有第一个成功地验证的Realm 返回的信息将被使用。所有进一步
# 的Realm 将被忽略。如果没有一个验证成功,则整体尝试失败
# 3. AllSucessfulStrategy 为了整体的尝试成功,所有配置的Realm 必须验证成功。如果没有一
# 个验证成功,则整体尝试失败。
# ModularRealmAuthenticator 默认的是AtLeastOneSuccessfulStrategy
###########################################################################
Authenticator的职责是验证用户帐号,是Shiro API中身份验证核心的入口点:
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken) throws AuthenticationException;
SecurityManager接口继承了Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm进行验证,验证规则通过AuthenticationStrategy接口指定,默认提供的实现:
FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;
AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;
AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略。
新建测试类mains:
package com.fendo.temp;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
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.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Mains {
private static final transient Logger log = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
//获取SecurityManager的实例
Factory factory = new IniSecurityManagerFactory("classpath:shiro-mutil-realm.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject currenUser = SecurityUtils.getSubject();
//如果还未认证
if(!currenUser.isAuthenticated()){
UsernamePasswordToken token = new UsernamePasswordToken("admin","123");
token.setRememberMe(true);
try {
currenUser.login(token);
} catch (UnknownAccountException uae) {
log.info("没有该用户: " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) {
log.info( token.getPrincipal() + " 的密码不正确!");
} catch (LockedAccountException lae) {
log.info( token.getPrincipal() + " 被锁定 ,请联系管理员");
}catch (AuthenticationException ae) {
//其他未知的异常
}
}
if(currenUser.isAuthenticated())
log.info("用户 "+currenUser.getPrincipal() +" 登录成功");
//得到一个身份集合
PrincipalCollection principalCollection = currenUser.getPrincipals();
}
}
2017-07-15 15:31:53,809 INFO [com.fendo.temp.Main] - MyRealm2开始认证。。。。。。
2017-07-15 15:31:53,809 INFO [com.fendo.temp.Main] - username:admin
2017-07-15 15:31:53,809 INFO [com.fendo.temp.Main] - password:123
2017-07-15 15:31:53,814 INFO [com.fendo.temp.MyRealm1] - MyRealm1开始认证。。。。。。
2017-07-15 15:31:53,814 INFO [com.fendo.temp.MyRealm1] - username:admin
2017-07-15 15:31:53,814 INFO [com.fendo.temp.MyRealm1] - password:123
2.3、自定义AuthenticationStrategy(验证策略)
我们也可以自定义AuthenticationStrategy,首先看其API:
//在所有Realm验证之前调用
AuthenticationInfo beforeAllAttempts(Collection extends Realm> realms, AuthenticationToken token) throws AuthenticationException;
//在每个Realm之前调用
AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException;
//在每个Realm之后调用
AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException;
//在所有Realm之后调用
AuthenticationInfo afterAllAttempts(AuthenticationToken token, AuthenticationInfo aggregate) throws AuthenticationException;
package com.fendo.temp;
import java.util.Collection;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.AbstractAuthenticationStrategy;
import org.apache.shiro.realm.Realm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyAuthenticationStrategy extends AbstractAuthenticationStrategy{
private static final transient Logger log = LoggerFactory.getLogger(MyAuthenticationStrategy.class);
/**
* 所有Realm验证之前调用
*/
@Override
public AuthenticationInfo beforeAllAttempts(
Collection extends Realm> realms, AuthenticationToken token)
throws AuthenticationException {
log.info("===============beforeAllAttempts方法被调用==================");
return super.beforeAllAttempts(realms, token);
}
/**
* 每一个Realm验证之前调用
*/
@Override
public AuthenticationInfo beforeAttempt(Realm realm,
AuthenticationToken token, AuthenticationInfo aggregate)
throws AuthenticationException {
log.info("===============beforeAttempt方法被调用==================");
return super.beforeAttempt(realm, token, aggregate);
}
/**
* 每一个Realm验证之后调用
*/
@Override
public AuthenticationInfo afterAttempt(Realm realm,
AuthenticationToken token, AuthenticationInfo singleRealmInfo,
AuthenticationInfo aggregateInfo, Throwable t)
throws AuthenticationException {
log.info("===============afterAttempt方法被调用==================");
return super.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, t);
}
/**
* 所有Realm验证之后调用
*/
@Override
public AuthenticationInfo afterAllAttempts(AuthenticationToken token,
AuthenticationInfo aggregate) throws AuthenticationException {
log.info("===============afterAllAttempts方法被调用==================");
return super.afterAllAttempts(token, aggregate);
}
}
2017-07-15 15:42:37,464 INFO [com.fendo.temp.MyAuthenticationStrategy] - ===============beforeAllAttempts方法被调用==================
2017-07-15 15:42:37,466 TRACE [org.apache.shiro.authc.pam.ModularRealmAuthenticator] - Iterating through 2 realms for PAM authentication
2017-07-15 15:42:37,466 INFO [com.fendo.temp.MyAuthenticationStrategy] - ===============beforeAttempt方法被调用==================
2017-07-15 15:42:37,466 TRACE [org.apache.shiro.authc.pam.ModularRealmAuthenticator] - Attempting to authenticate token [org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=true] using realm [com.fendo.temp.MyRealm2@17d99928]
2017-07-15 15:42:37,466 INFO [com.fendo.temp.Main] - MyRealm2开始认证。。。。。。
2017-07-15 15:42:37,466 INFO [com.fendo.temp.Main] - username:admin
2017-07-15 15:42:37,466 INFO [com.fendo.temp.Main] - password:123
2017-07-15 15:42:37,469 DEBUG [org.apache.shiro.realm.AuthenticatingRealm] - Looked up AuthenticationInfo [admin] from doGetAuthenticationInfo
2017-07-15 15:42:37,469 DEBUG [org.apache.shiro.realm.AuthenticatingRealm] - AuthenticationInfo caching is disabled for info [admin]. Submitted token: [org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=true].
2017-07-15 15:42:37,469 DEBUG [org.apache.shiro.authc.credential.SimpleCredentialsMatcher] - Performing credentials equality check for tokenCredentials of type [[C and accountCredentials of type [java.lang.String]
2017-07-15 15:42:37,469 DEBUG [org.apache.shiro.authc.credential.SimpleCredentialsMatcher] - Both credentials arguments can be easily converted to byte arrays. Performing array equals comparison
2017-07-15 15:42:37,470 INFO [com.fendo.temp.MyAuthenticationStrategy] - ===============afterAttempt方法被调用==================
2017-07-15 15:42:37,470 INFO [com.fendo.temp.MyAuthenticationStrategy] - ===============beforeAttempt方法被调用==================
2017-07-15 15:42:37,471 TRACE [org.apache.shiro.authc.pam.ModularRealmAuthenticator] - Attempting to authenticate token [org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=true] using realm [com.fendo.temp.MyRealm1@2fc14f68]
2017-07-15 15:42:37,471 INFO [com.fendo.temp.MyRealm1] - MyRealm1开始认证。。。。。。
2017-07-15 15:42:37,471 INFO [com.fendo.temp.MyRealm1] - username:admin
2017-07-15 15:42:37,471 INFO [com.fendo.temp.MyRealm1] - password:123
2017-07-15 15:42:37,471 DEBUG [org.apache.shiro.realm.AuthenticatingRealm] - Looked up AuthenticationInfo [admin] from doGetAuthenticationInfo
2017-07-15 15:42:37,471 DEBUG [org.apache.shiro.realm.AuthenticatingRealm] - AuthenticationInfo caching is disabled for info [admin]. Submitted token: [org.apache.shiro.authc.UsernamePasswordToken - admin, rememberMe=true].
2017-07-15 15:42:37,471 DEBUG [org.apache.shiro.authc.credential.SimpleCredentialsMatcher] - Performing credentials equality check for tokenCredentials of type [[C and accountCredentials of type [java.lang.String]
2017-07-15 15:42:37,471 DEBUG [org.apache.shiro.authc.credential.SimpleCredentialsMatcher] - Both credentials arguments can be easily converted to byte arrays. Performing array equals comparison
2017-07-15 15:42:37,471 INFO [com.fendo.temp.MyAuthenticationStrategy] - ===============afterAttempt方法被调用==================
2017-07-15 15:42:37,472 INFO [com.fendo.temp.MyAuthenticationStrategy] - ===============afterAllAttempts方法被调用==================
2017-07-15 15:42:38,043 INFO [com.fendo.temp.Main] - 用户 admin 登录成功