东莞旅游网站建设,自助网站免费建站平台,wordpress 用户排序,.net做网站用什么的多目录
1.用户登录认证流程
1.1 生成认证Token
1.2 用户登录认证
1.2.1 SecurityManager login流程解析
1.2.1.1 authenticate方法进行登录认证
1.2.1.1.1 单Realm认证
1.2.1.2 认证通过后创建登录用户对象
1.2.1.2.1 复制SubjectContext
1.2.1.2.2 对subjectContext设…目录
1.用户登录认证流程
1.1 生成认证Token
1.2 用户登录认证
1.2.1 SecurityManager login流程解析
1.2.1.1 authenticate方法进行登录认证
1.2.1.1.1 单Realm认证
1.2.1.2 认证通过后创建登录用户对象
1.2.1.2.1 复制SubjectContext
1.2.1.2.2 对subjectContext设置securityManager
1.2.1.2.3 对subjectContext设置session
1.2.1.2.4 对subjectContext设置Principals
1.2.1.2.5 根据subjectContext创建Subject
1.2.1.2.6 保存Subject Shiro作为一款比较流行的登录认证、访问控制安全框架被广泛应用在程序员社区Shiro登录验证、访问控制、Session管理等流程内部都是委托给SecurityManager安全管理器来完成的SecurityManager安全管理器上篇文章已经进行了详细解析详见Shiro框架Shiro SecurityManager安全管理器解析-CSDN博客在此基础上本篇文章继续对Shiro关联链路处理流程之一---登录认证流程 进行解析 想要深入了解Shiro框架整体原理可移步 Shiro框架ShiroFilterFactoryBean过滤器源码解析-CSDN博客、 Shiro框架Shiro内置过滤器源码解析-CSDN博客 1.用户登录认证流程
在Shiro框架Shiro内置过滤器源码解析-CSDN博客内置过滤器分析中我们知道用户执行登录的认证操作是在过滤器FormAuthenticationFilter中执行的如下 protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {if (isLoginRequest(request, response)) {if (isLoginSubmission(request, response)) {if (log.isTraceEnabled()) {log.trace(Login submission detected. Attempting to execute login.);}return executeLogin(request, response);} else {if (log.isTraceEnabled()) {log.trace(Login page view.);}//allow them to see the login page ;)return true;}} else {if (log.isTraceEnabled()) {log.trace(Attempting to access a path which requires authentication. Forwarding to the Authentication url [ getLoginUrl() ]);}saveRequestAndRedirectToLogin(request, response);return false;}}登录认证操作是在executeLogin方法中完成的 protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {AuthenticationToken token createToken(request, response);if (token null) {String msg createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.;throw new IllegalStateException(msg);}try {Subject subject getSubject(request, response);subject.login(token);return onLoginSuccess(token, subject, request, response);} catch (AuthenticationException e) {return onLoginFailure(token, e, request, response);}}
上述源码的执行过程表示为时序图会更直观时序图如下 该认证过程主要包含以下几个部分
根据用户名密码等生成认证Token获取当前登录用户Subject调用Subject的login方法进行登录认证其它的登录成功、或登录失败的拦截方法
下面主要对生成认证Token和用户登录认证实现进行详细说明
1.1 生成认证Token
createToken的实现在FormAuthenticationFilter内如下
这里用户名和密码是通过request请求对象获取的 protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {String username getUsername(request);String password getPassword(request);return createToken(username, password, request, response);}
在父类AuthenticatingFilter中进一步实现如下这里构造了UsernamePasswordToken类表示采用用户名密码的认证方式 protected AuthenticationToken createToken(String username, String password,ServletRequest request, ServletResponse response) {boolean rememberMe isRememberMe(request);String host getHost(request);return createToken(username, password, rememberMe, host);}protected AuthenticationToken createToken(String username, String password,boolean rememberMe, String host) {return new UsernamePasswordToken(username, password, rememberMe, host);}
图示UsernamePasswordToken的继承结构 1.2 用户登录认证
在Subject的login的具体实现如下
实际的login是委托给SecurityManager完成的登录成功后设置当前登录对象为认证成功 下面主要对SecurityManager的login方法进行具体分析
1.2.1 SecurityManager login流程解析
login方法具体实现如下 public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {AuthenticationInfo info;try {info authenticate(token);} catch (AuthenticationException ae) {try {onFailedLogin(token, ae, subject);} catch (Exception e) {if (log.isInfoEnabled()) {log.info(onFailedLogin method threw an exception. Logging and propagating original AuthenticationException., e);}}throw ae; //propagate}Subject loggedIn createSubject(token, info, subject);onSuccessfulLogin(token, info, loggedIn);return loggedIn;}
上述登录流程主要包含2部分内容 通过authenticate方法进行登录认证认证通过后创建登录用户对象 下面分别进行展开分析
1.2.1.1 authenticate方法进行登录认证
具体的authenticate实现内部又委托给了认证器Authenticator来实现具体的认证器为ModularRealmAuthenticator其继承结构如下 authenticate方法具体实现是在父类AbstractAuthenticator中如下
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {if (token null) {throw new IllegalArgumentException(Method argument (authentication token) cannot be null.);}log.trace(Authentication attempt received for token [{}], token);AuthenticationInfo info;try {info doAuthenticate(token);if (info null) {String msg No account information found for authentication token [ token ] by this Authenticator instance. Please check that it is configured correctly.;throw new AuthenticationException(msg);}} catch (Throwable t) {AuthenticationException ae null;if (t instanceof AuthenticationException) {ae (AuthenticationException) t;}if (ae null) {//Exception thrown was not an expected AuthenticationException. Therefore it is probably a little more//severe or unexpected. So, wrap in an AuthenticationException, log to warn, and propagate:String msg Authentication failed for token submission [ token ]. Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).;ae new AuthenticationException(msg, t);if (log.isWarnEnabled())log.warn(msg, t);}try {notifyFailure(token, ae);} catch (Throwable t2) {if (log.isWarnEnabled()) {String msg Unable to send notification for failed authentication attempt - listener error?. Please check your AuthenticationListener implementation(s). Logging sending exception and propagating original AuthenticationException instead...;log.warn(msg, t2);}}throw ae;}log.debug(Authentication successful for token [{}]. Returned account [{}], token, info);notifySuccess(token, info);return info;}
通过方法doAuthenticate对认证Token进行认证通过notifySuccess、notifyFailure监听认证通过或失败的事件并调用注册的监听器
方法doAuthenticate的具体实现是在子类完成的如下 protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {assertRealmsConfigured();CollectionRealm realms getRealms();if (realms.size() 1) {return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);} else {return doMultiRealmAuthentication(realms, authenticationToken);}}
这里首先获取已注册的安全组件Realm其注册过程是在RealmSecurityManager初始化的过程中完成的对SecurityManager安全管理器的处理过程感兴趣可以参见Shiro框架Shiro SecurityManager安全管理器解析-CSDN博客
然后根据Realm的个数分别执行单Realm认证或多Realm认证这里已单Realm认证为例进行说明多Realm认证类似
1.2.1.1.1 单Realm认证
通过doSingleRealmAuthentication方法完成单Realm认证实现如下 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);}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);}return info;}
这里Reaml接口包含一整套的继承层次实现如下这里不过多展开解析 getAuthenticationInfo是在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);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);} else {log.debug(No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null., token);}return info;} 这里首先获取AuthenticationInfo对象要么从缓存中获取要么通过引入的抽象方法doGetAuthenticationInfo获取交由应用层子类具体实现
获取到AuthenticationInfo后将其余用户录入的登录Token进行比对这部分具体是方法assertCredentialsMatch完成的具体如下 protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {CredentialsMatcher cm getCredentialsMatcher();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.);}}
这里获取了默认的匹配器SimpleCredentialsMatcher并调用doCredentialsMatch方法进行匹配匹配实现如下 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {Object tokenCredentials getCredentials(token);Object accountCredentials getCredentials(info);return equals(tokenCredentials, accountCredentials);}
首先获取Credentials也即用户密码然后调用equals方法进行匹配如下通过字节流的方式进行比对主要是为了安全考虑系统处理时采用非明文形式。 protected boolean equals(Object tokenCredentials, Object accountCredentials) {if (log.isDebugEnabled()) {log.debug(Performing credentials equality check for tokenCredentials of type [ tokenCredentials.getClass().getName() and accountCredentials of type [ accountCredentials.getClass().getName() ]);}if (isByteSource(tokenCredentials) isByteSource(accountCredentials)) {if (log.isDebugEnabled()) {log.debug(Both credentials arguments can be easily converted to byte arrays. Performing array equals comparison);}byte[] tokenBytes toBytes(tokenCredentials);byte[] accountBytes toBytes(accountCredentials);return MessageDigest.isEqual(tokenBytes, accountBytes);} else {return accountCredentials.equals(tokenCredentials);}} 至此用户账号密码匹配完成匹配完成后会重新创建用户登录对象并更新用户状态等下面具体分析
1.2.1.2 认证通过后创建登录用户对象
认证通过后创建Subject的实现如下 protected Subject createSubject(AuthenticationToken token, AuthenticationInfo info, Subject existing) {SubjectContext context createSubjectContext();context.setAuthenticated(true);context.setAuthenticationToken(token);context.setAuthenticationInfo(info);if (existing ! null) {context.setSubject(existing);}return createSubject(context);}Overrideprotected SubjectContext createSubjectContext() {return new DefaultWebSubjectContext();}
这里创建了DefaultWebSubjectContext用户Subject创建其中设置了认证状态、认证Token、已认证信息然后调用createSubject方法继续进行构造如下 public Subject createSubject(SubjectContext subjectContext) {//create a copy so we dont modify the arguments backing map:SubjectContext context copy(subjectContext);//ensure that the context has a SecurityManager instance, and if not, add one:context ensureSecurityManager(context);//Resolve an associated Session (usually based on a referenced session ID), and place it in the context before//sending to the SubjectFactory. The SubjectFactory should not need to know how to acquire sessions as the//process is often environment specific - better to shield the SF from these details:context resolveSession(context);//Similarly, the SubjectFactory should not require any concept of RememberMe - translate that here first//if possible before handing off to the SubjectFactory:context resolvePrincipals(context);Subject subject doCreateSubject(context);//save this subject for future reference if necessary://(this is needed here in case rememberMe principals were resolved and they need to be stored in the//session, so we dont constantly rehydrate the rememberMe PrincipalCollection on every operation).//Added in 1.2:save(subject);return subject;}
如上创建过程包含了以下几部分分别进行分析;
1.2.1.2.1 复制SubjectContext
对subjectContext调用复制构造方法进行复制subjectContext底层通过backingMap保存上下文信息 public MapContext(MapString, Object map) {this();if (!CollectionUtils.isEmpty(map)) {this.backingMap.putAll(map);}}
1.2.1.2.2 对subjectContext设置securityManager
获取securityManager的顺序如下
首先从subjectContext中获取若无则从当前线程上下文中获取否则将当前securityManager设置到subjectContext中 protected SubjectContext ensureSecurityManager(SubjectContext context) {if (context.resolveSecurityManager() ! null) {log.trace(Context already contains a SecurityManager instance. Returning.);return context;}log.trace(No SecurityManager found in context. Adding self reference.);context.setSecurityManager(this);return context;} public SecurityManager resolveSecurityManager() {SecurityManager securityManager getSecurityManager();if (securityManager null) {if (log.isDebugEnabled()) {log.debug(No SecurityManager available in subject context map. Falling back to SecurityUtils.getSecurityManager() lookup.);}try {securityManager SecurityUtils.getSecurityManager();} catch (UnavailableSecurityManagerException e) {if (log.isDebugEnabled()) {log.debug(No SecurityManager available via SecurityUtils. Heuristics exhausted., e);}}}return securityManager;}
1.2.1.2.3 对subjectContext设置session
解析session方法如下其中session的解析顺序为
首先从subjectContext获取session判断是否已设置session见源码2否则通过subjectContext中保存的Subject对象获取关联的session见源码2其次通过sessionManager Session管理器获取见源码3Web服务中具体的Session管理器为ServletContainerSessionManager尝试从request请求中获取Servlet管理的Session见源码4
源码1 protected SubjectContext resolveSession(SubjectContext context) {if (context.resolveSession() ! null) {log.debug(Context already contains a session. Returning.);return context;}try {//Context couldnt resolve it directly, lets see if we can since we have direct access to //the session manager:Session session resolveContextSession(context);if (session ! null) {context.setSession(session);}} catch (InvalidSessionException e) {log.debug(Resolved SubjectContext context session is invalid. Ignoring and creating an anonymous (session-less) Subject instance., e);}return context;}
源码2 public Session resolveSession() {Session session getSession();if (session null) {//try the Subject if it exists:Subject existingSubject getSubject();if (existingSubject ! null) {session existingSubject.getSession(false);}}return session;}
源码3 protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {SessionKey key getSessionKey(context);if (key ! null) {return getSession(key);}return null;}public Session getSession(SessionKey key) throws SessionException {return this.sessionManager.getSession(key);}
源码4 ServletContainerSessionManager获取Session public Session getSession(SessionKey key) throws SessionException {if (!WebUtils.isHttp(key)) {String msg SessionKey must be an HTTP compatible implementation.;throw new IllegalArgumentException(msg);}HttpServletRequest request WebUtils.getHttpRequest(key);Session session null;HttpSession httpSession request.getSession(false);if (httpSession ! null) {session createSession(httpSession, request.getRemoteHost());}return session;}
1.2.1.2.4 对subjectContext设置Principals
resolvePrincipals方法实现如下这里获取Principals的顺序为
先从SubjectContext中直接获取Principals见源码2否则通过SubjectContext中已认证的AuthenticationInfo获取见源码2其次通过SubjectContext中的Subject获取见源码2再次通过SubjectContext中的Session获取见源码2最后通过RememberMeManager中的Cookie中获取
源码1 protected SubjectContext resolvePrincipals(SubjectContext context) {PrincipalCollection principals context.resolvePrincipals();if (isEmpty(principals)) {log.trace(No identity (PrincipalCollection) found in the context. Looking for a remembered identity.);principals getRememberedIdentity(context);if (!isEmpty(principals)) {log.debug(Found remembered PrincipalCollection. Adding to the context to be used for subject construction by the SubjectFactory.);context.setPrincipals(principals);} else {log.trace(No remembered identity found. Returning original context.);}}return context;}
源码2从SubjectContext获取Principals public PrincipalCollection resolvePrincipals() {PrincipalCollection principals getPrincipals();if (isEmpty(principals)) {//check to see if they were just authenticated:AuthenticationInfo info getAuthenticationInfo();if (info ! null) {principals info.getPrincipals();}}if (isEmpty(principals)) {Subject subject getSubject();if (subject ! null) {principals subject.getPrincipals();}}if (isEmpty(principals)) {//try the session:Session session resolveSession();if (session ! null) {principals (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);}}return principals;}
1.2.1.2.5 根据subjectContext创建Subject
如下通过createSubject创建了WebDelegatingSubject对象 public Subject createSubject(SubjectContext context) {if (!(context instanceof WebSubjectContext)) {return super.createSubject(context);}WebSubjectContext wsc (WebSubjectContext) context;SecurityManager securityManager wsc.resolveSecurityManager();Session session wsc.resolveSession();boolean sessionEnabled wsc.isSessionCreationEnabled();PrincipalCollection principals wsc.resolvePrincipals();boolean authenticated wsc.resolveAuthenticated();String host wsc.resolveHost();ServletRequest request wsc.resolveServletRequest();ServletResponse response wsc.resolveServletResponse();return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,request, response, securityManager);}
1.2.1.2.6 保存Subject Subject保存是通过DefaultSubjectDAO完成的
如下通过saveToSession方法保存Principals和认证状态到Session中 protected void saveToSession(Subject subject) {//performs merge logic, only updating the Subjects session if it does not match the current state:mergePrincipals(subject);mergeAuthenticationState(subject);}
这里在保存Principals的过程中如果Principals不为空且非Servlet Session的条件下这里会调用subject.getSession()方法创建shiro管理的native Session对象 subject.getSession()方法实现如下 public Session getSession() {return getSession(true);}public Session getSession(boolean create) {if (log.isTraceEnabled()) {log.trace(attempting to get session; create create ; session is null (this.session null) ; session has id (this.session ! null session.getId() ! null));}if (this.session null create) {//added in 1.2:if (!isSessionCreationEnabled()) {String msg Session creation has been disabled for the current subject. This exception indicates that there is either a programming error (using a session when it should never be used) or that Shiros configuration needs to be adjusted to allow Sessions to be created for the current Subject. See the DisabledSessionException.class.getName() JavaDoc for more.;throw new DisabledSessionException(msg);}log.trace(Starting session for host {}, getHost());SessionContext sessionContext createSessionContext();Session session this.securityManager.start(sessionContext);this.session decorate(session);}return this.session;}
如上通过securityManager的start方法创建Session对象start具体实现为 public Session start(SessionContext context) throws AuthorizationException {return this.sessionManager.start(context);}
这里又委托给了Session管理器完成session创建这里的Session管理器实现为AbstractNativeSessionManagerstart方法实现如下 public Session start(SessionContext context) {Session session createSession(context);applyGlobalSessionTimeout(session);onStart(session, context);notifyStart(session);//Dont expose the EIS-tier Session object to the client-tier:return createExposedSession(session, context);}
上述主要完成了以下几部分功能
1. 通过createSession方法创建SimpleSession对象其中包括了Session创建、Session保存到 sessionDAO中比如保存到内存中的子类MemorySessionDAO或者保存在数据库中的子类 EnterpriseCacheSessionDAO等SessionId生成等操作 图示sessionDAO的整体继承结构 2. Session全局超时时间配置默认超时时间为30min
3. onStart将SessionId存储到Cookie中如下 private void storeSessionId(Serializable currentId, HttpServletRequest request, HttpServletResponse response) {if (currentId null) {String msg sessionId cannot be null when persisting for subsequent requests.;throw new IllegalArgumentException(msg);}Cookie template getSessionIdCookie();Cookie cookie new SimpleCookie(template);String idString currentId.toString();cookie.setValue(idString);cookie.saveTo(request, response);log.trace(Set session ID cookie for session with id {}, idString);}
至此用户登录认证过程完成了生成认证Token、通过Realm从应用中获取用户认证信息、用户认证Token匹配以及认证成功后创建用户Subject对象、保存Subject到Session、SessionId保存到Cookie等操作。