在使用自定义Realme之后subject如何进行认证
securityManager配置
@Bean
public DefaultWebSecurityManager securityManager(AdminRealm adminRealm, WxRealm wxRealm,
DefaultWebSessionManager sessionManager,
CustomAuthenticator authenticator) {
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//添加realm
ArrayList<Realm> realms = new ArrayList<>();
realms.add(adminRealm);
realms.add(wxRealm);
defaultWebSecurityManager.setRealms(realms);
defaultWebSecurityManager.setSessionManager(sessionManager);
//此处设定的是自定义authenticator
defaultWebSecurityManager.setAuthenticator(authenticator);
return defaultWebSecurityManager;
}
@Bean
public CustomAuthenticator authenticator(AdminRealm adminRealm, WxRealm wxRealm) {
CustomAuthenticator customAuthenticator = new CustomAuthenticator();
ArrayList<Realm> realms = new ArrayList<>();
realms.add(adminRealm);
realms.add(wxRealm);
customAuthenticator.setRealms(realms);
return customAuthenticator;
}
@Component
public class CustomAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) {
this.assertRealmsConfigured();
Collection<Realm> originRealms = this.getRealms();
MallToken mallToken = (MallToken) authenticationToken;
String type = mallToken.getType();
ArrayList<Realm> realms = new ArrayList<>();
for (Realm originRealm : originRealms) {
if (originRealm.getName().toLowerCase().contains(type)) {
realms.add(originRealm);
}
}
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
}
首先在controller中使用subject.login方法,传入token
@CountTime
@RequestMapping("login")
public Result login(@RequestBody LoginBo loginBo) {
MallToken adminToken = new MallToken(loginBo.getUsername(), loginBo.getPassword(), "admin");
Subject subject = SecurityUtils.getSubject();
subject.login(adminToken);
return Result.ok(subject.getSession().getId());
}
而Subject是一个接口,由DelegatingSubject类实现接口来具体实现login方法,而在第3行中又由this.securityManager调用login , 并传入token
public void login(AuthenticationToken token) throws AuthenticationException {
this.clearRunAsIdentitiesInternal();
Subject subject = this.securityManager.login(this, token);
String host = null;
PrincipalCollection principals;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject)subject;
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
if (principals != null && !principals.isEmpty()) {
this.principals = principals;
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken)token).getHost();
}
if (host != null) {
this.host = host;
}
Session session = subject.getSession(false);
if (session != null) {
this.session = this.decorate(session);
} else {
this.session = null;
}
} else {
String msg = "Principals returned from securityManager.login( token ) returned a null or empty value. This value must be non null and populated with one or more elements.";
throw new IllegalStateException(msg);
}
}
此处this.securityManager为DelegatingSubject类中
protected transient SecurityManager securityManager;
此SecurityManager是一个接口
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
Subject login(Subject var1, AuthenticationToken var2) throws AuthenticationException;
void logout(Subject var1);
Subject createSubject(SubjectContext var1);
}
由DefaultSecurityManager类对login方法做了具体实现 , 其中第4行又调用了authenticate方法 , 传入token。但是DefaultSecurityManager本身并没有实现authenticate(token)方法,此处调用方法时调用的是其父类(AuthenticatingSecurityManager)方法
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = this.authenticate(token);
} catch (AuthenticationException var7) {
AuthenticationException ae = var7;
try {
this.onFailedLogin(token, ae, subject);
} catch (Exception var6) {
if (log.isInfoEnabled()) {
log.info("onFailedLogin method threw an exception. Logging and propagating original AuthenticationException.", var6);
}
}
throw var7;
}
Subject loggedIn = this.createSubject(token, info, subject);
this.onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
}
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
this.authenticator再次调用authenticate 。因为之前的securityManager配置,所以此处的this.authenticator为CustomeAuthenticator类
private Authenticator authenticator = new ModularRealmAuthenticator();
public void setAuthenticator(Authenticator authenticator) throws IllegalArgumentException {
if (authenticator == null) {
String msg = "Authenticator argument cannot be null.";
throw new IllegalArgumentException(msg);
} else {
this.authenticator = authenticator;
}
}
}
public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
return this.authenticator.authenticate(token);
}
而CustomeAuthenticator又重写了authenticate方法,直到最后执行this.doSingleRealmAuthentication((Realm)realms.iterator().next(),authenticationToken)由父类ModularRealmAuthenticator执行
public class CustomAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) {
this.assertRealmsConfigured();
Collection<Realm> originRealms = this.getRealms();
MallToken mallToken = (MallToken) authenticationToken;
String type = mallToken.getType();
ArrayList<Realm> realms = new ArrayList<>();
for (Realm originRealm : originRealms) {
if (originRealm.getName().toLowerCase().contains(type)) {
realms.add(originRealm);
}
}
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
}
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm [" + realm + "] does not support authentication token [" + token + "]. Please ensure that the appropriate Realm implementation is " + "configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
} else {
AuthenticationInfo info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm + "] was unable to find account data for the " + "submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
} else {
return info;
}
}
}
执行到realm.getAuthenticationInfo(token),Realm是一个接口,此方法由AuthenticatingRealm实现,且被final关键字修饰,所以不能被重写。因为这个项目并没有加入缓存机制,所以会执行第5行info = this.doGetAuthenticationInfo(token),如有缓存机制,则执行this.assertCredentialsMatch(token, info);
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
this.assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
这是一个抽象方法,因为是在adminRealm上调用,所以会执行AdminRealm的doGetAuthenticationInfo方法,最后返回SimpleAuthenticationInfo
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
MallToken adminToken = (MallToken) authenticationToken;
String username = adminToken.getUsername();
//从数据库中取出数据
Admin admin = adminMapper.selectAdminByUsername(username);
String credential = admin.getPassword();
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, credential, this.getName());
return authenticationInfo;
}
不断返回之后,在这里进行加密匹配,也可以自定义明文匹配
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
this.assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
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.");
}
}
此处doCredentialsMatch(token, info)有四种密码器匹配模式,具体调用哪种还有待验证。
AllowAllCredentialsMatcher为有用户名就通过,SimpleCredentialsMatcher为简单的相等,剩余两种采用加密算法进行密码验证。
至此,认证的基本流程就结束了。
在使用自定义Realme之后subject如何进行授权
授权时调用doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法,是在SecurityUtils.getSubject.isPermitted时调用 , 而我们平常用注解@RequirePermissions()时则会调用SecurityUtils.getSubject().isPermitted() , 最终可以调用到自定义Realm中重写的doGetAuthenticationInfo(AuthenticationToken authenticationToken)方法