shiro web 身份、权限验证流程

一、身份验证流程(左),权限验证流程(右)
请求
是否登录请求
登录
跳转到登录页
获取用户名密码
是否匹配
创建session和凭证
返回失败信息
将登陆信息写入cookie
请求
验证权限
获取请求需要的权限
是否匹配
调用方法
返回失败信息
二、原理
  1. 组件
组件功能
shiro Filter各种验证流程的入口
Subject代表了登录人,记录session,凭证等相关信息。定义了验证流程所需要的基本的验证方法,每次请求都会创建新的Subject
SecurityManager管理Subject,session等信息,辅助subject实现功能
session类似servlet session,每个登录用户都有唯一一个session相对应
Realm需要开发者实现的部分,通过realm获取权限,角色信息,身份信息
AuthenticationInfo代表了通过realm获取的用于身份验证的信息
AuthorizationInfo代表了通过realm获取的用于权限验证的信息
AuthenticationToken代表了请求的凭证信息
CredentialsMatcher主要用于密码验证
PasswordService用于生成密码和辅助CredentialsMatcher密码验证
AuthenticationStrategy身份验证策略,多个Realm时需要策略来决定身份验证是否通过。类似投票表决
Authenticator身份验证
  1. 组件间关系
    在这里插入图片描述

  2. 身份验证流程相关对象和方法

AbstractShiroFilter.doFilterInternal
FormAuthenticationFilter.onAccessDenied
AuthenticatingFilter.executeLogin
DelegatingSubject.login
DefaultSecurityManager.login
ModularRealmAuthenticator.authenticate
AuthenticatingRealm.getAuthenticationInfo
SimpleCredentialsMatcher.equals
  • AbstractShiroFilter.doFilterInternal)
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
		    throws ServletException, IOException {

		Throwable t = null;

		try {
		    final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
		    final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
           //每次请求都会创建新的Subject
		    final Subject subject = createSubject(request, response);

		    //将创建的subject绑定到当前线程,并调用FormAuthenticationFilter过滤器
		    subject.execute(new Callable() {
			public Object call() throws Exception {
			    updateSessionLastAccessTime(request, response);
			    executeChain(request, response, chain);
			    return null;
			}
		    });
		} catch (ExecutionException ex) {
		    t = ex.getCause();
		} catch (Throwable throwable) {
		    t = throwable;
		}
 }
  • FormAuthenticationFilter.onAccessDenied)
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.");
                }
                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;
        }
}
  • AuthenticatingFilter.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 subject = getSubject(request, response);
            //登录
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
}
  • DelegatingSubject.login
public void login(AuthenticationToken token) throws AuthenticationException {
        //清除当前的身份证明信息
        clearRunAsIdentitiesInternal();
        //登录,返回的subject只是临时的,作用只是把数据传递给当前subject
        Subject subject = securityManager.login(this, token);

        PrincipalCollection principals;

        String host = null;

        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            //获取身份证明信息
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            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);
        }
        //为当前subject设置身份证明信息
        this.principals = principals;
        //为当前subject设置授权
        this.authenticated = true;
        if (token instanceof HostAuthenticationToken) {
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
           //为当前subject设置host
            this.host = host;
        }
        //获取session信息
        Session session = subject.getSession(false);
        if (session != null) {
             //为当前subject设置session
            this.session = decorate(session);
        } else {
            this.session = null;
        }
}
  • DefaultSecurityManager.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;
}

  • ModularRealmAuthenticator.authenticate
//验证策略选择
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            //一般的用户名、密码验证
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
           //多种策略,默认提供了三种
           //1.全成功则成功 2.至少一个成功则成功 3.首个成功则成功
            return 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);
        }
        //用户名、密码验证
        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;
}
  • AuthenticatingRealm.getAuthenticationInfo
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //调用用户realm获取用户信息,这里也可以进行用户名、密码的验证
            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) {
            //通过CredentialsMatcher来验证密码
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
}
  • SimpleCredentialsMatcher.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. 权限验证流程相关对象和方法
    用到了两个过滤器
    • UserFilter:判断用户是否登录
    • PermissionsAuthorizationFilter:判断用户权限是否匹配
AbstractShiroFilter.doFilterInternal
UserFilter.isAccessAllowed
PermissionsAuthorizationFilter.isAccessAllowed
DelegatingSubject.isPermitted
DefaultSecurityManager.isPermitted
ModularRealmAuthorizer.isPermitted
AuthorizingRealm.isPermitted
  • AbstractShiroFilter.doFilterInternal)
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
		    throws ServletException, IOException {

		Throwable t = null;

		try {
		    final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
		    final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
           //每次请求都会创建新的Subject
		    final Subject subject = createSubject(request, response);

		    //将创建的subject绑定到当前线程,并调用FormAuthenticationFilter过滤器
		    subject.execute(new Callable() {
			public Object call() throws Exception {
			    updateSessionLastAccessTime(request, response);
			    executeChain(request, response, chain);
			    return null;
			}
		    });
		} catch (ExecutionException ex) {
		    t = ex.getCause();
		} catch (Throwable throwable) {
		    t = throwable;
		}
 }
  • UserFilter.isAccessAllowed
public class UserFilter extends AccessControlFilter {

    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        //不拦截登录流程
        if (isLoginRequest(request, response)) {
            return true;
        } else {
            //是否登录
            Subject subject = getSubject(request, response);
            return subject.getPrincipal() != null;
        }
    }
    
}
  • PermissionsAuthorizationFilter.isAccessAllowed
 public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {

        Subject subject = getSubject(request, response);
        //配置的权限信息
        String[] perms = (String[]) mappedValue;

        boolean isPermitted = true;
        if (perms != null && perms.length > 0) {
             //验证权限
            if (perms.length == 1) {
                if (!subject.isPermitted(perms[0])) {
                    isPermitted = false;
                }
            } else {
                if (!subject.isPermittedAll(perms)) {
                    isPermitted = false;
                }
            }
        }

        return isPermitted;
}
  • DelegatingSubject.isPermitted
public boolean isPermitted(String permission) {
        //是否有权限信息,有则验证
        return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}
  • DefaultSecurityManager.isPermitted
public boolean isPermitted(PrincipalCollection principals, String permissionString) {
        //代理给authorizer验证
        return this.authorizer.isPermitted(principals, permissionString);
}
  • ModularRealmAuthorizer.isPermitted
public boolean isPermitted(PrincipalCollection principals, String permission) {
        assertRealmsConfigured();
        for (Realm realm : getRealms()) {
            if (!(realm instanceof Authorizer)) continue;
            //调用realm验证
            if (((Authorizer) realm).isPermitted(principals, permission)) {
                return true;
            }
        }
        return false;
}
  • AuthorizingRealm.isPermitted
protected boolean isPermitted(Permission permission, AuthorizationInfo info) {
        Collection<Permission> perms = getPermissions(info);
        if (perms != null && !perms.isEmpty()) {
            for (Permission perm : perms) {
                //获取用户的权限和配置的权限进行比较
                if (perm.implies(permission)) {
                    return true;
                }
            }
        }
        return false;
}
三、总结
  1. 权限验证更灵活的方式是使用aop和注解方式
  2. 除了权限验证还有一个角色验证,流程和权限验证类似,使用了UserFilter,
    RolesAuthorizationFilter
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值