99网
您的当前位置:首页【JavaWeb】shiro认证、授权源码流程

【JavaWeb】shiro认证、授权源码流程

来源:99网

概述前提(代码不全,可略过此篇博客)

1、自定义UsernamePasswordTokenUsenamePasswordToken一个简单的用户名/密码身份验证令牌,可支持最广泛的身份验证机制,所以我希望的认证能够围绕token进行

@Data
@AllArgsConstructor
public class CustomUsernamePasswordToken extends UsernamePasswordToken{
	private String token;
}

2、自定义AccessControlFilter:任何资源的过滤器,这里需要覆写isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o)方法以及onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse)方法

public class CustomAccessControllerFilter extends AccessControlFilter

3、自定义Realm:此实现将自动执行所有角色和权限检查,需要覆写授权方法doGetAuthorizationInfo(PrincipalCollection principals)和认证方法doGetAuthenticationInfo(AuthenticationToken token)

public class CustomRealm extends AuthorizingRealm

4、自定义HashedCredentialsMatcher:简单验证匹配器

public class CustomHashedCredentialsMatcher extends HashedCredentialsMatcher

认证过程

  假设一登录用户希望查看自身的详细信息,在用户向服务器发送请求的过程中,首先,这次请求会被我们自定义的CustomAccessControllerFilter所拦截,其次,会访问方法:isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o),如果该方法返回true,则这次请求会被本次放过,被下一个所捕获或直接通过(过滤器的工作方式是链式的);如果返回false,则这次继续访问自定义CustomAccessControllerFilter的方法onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse),并通过getSubject(servletRequest,servletResponse).login(customUsernamePasswordToken)传入customUsernamePasswordToken表示最终用户的主体和凭据的构造实例

  再之后会流转到ModularRealmAuthenticator进行认证:

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;
    }

  由于我们之前只自定义了一个realm,所以会跳转至doSingleRealmAuthentication(realms.iterator().next(), authenticationToken)方法内部,这里AuthenticationInfo info = realm.getAuthenticationInfo(token);则是真正开始认证:

  这里首先根据getCachedAuthenticationInfo(token)方法从缓存中获取info信息,但由于我们之前没有配置,所以这里的info是空的,那么在info = doGetAuthenticationInfo(token)方法中就从我们自定义的realm里面的doGetAuthenticationInfo方法获取:

 @Override
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
     CustomUsernamePasswordToken sessionId = (CustomUsernamePasswordToken)token;
     //getPrincipal()、getCredentials()返回的都是token
     SimpleAuthenticationInfo info =
             new SimpleAuthenticationInfo(sessionId.getPrincipal(),sessionId.getCredentials(),getName());
     return info;
 }

  当info!=null时,将拿着用户信息的进入到用户的核心匹配器进行认证匹配,assertCredentialsMatch(token, info)方法或捕获我们自定义的CustomHashedCredentialsMatcher匹配器(这里登陆时我把用户信息缓存到了redis里),匹配成功后返回true,通过方法栈向下完成认证

@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    CustomUsernamePasswordToken customUsernamePasswordToken = (CustomUsernamePasswordToken)token;

    String sessionId = (String)customUsernamePasswordToken.getPrincipal();
    if(!redisService.hasKey(sessionId)){
        throw new BusinessException(4001002, "凭证过期,请重新登录");
    }
    return true;
}

总结: 首先通过过滤器拦截请求从请求头中获取token信息,随后由主体提交并构造实例传递到SecurityManager委托完成authenticate,在此过程中首先验证Realm的个数,由个数选择启用方法,随后在Realm域中真正获取用户信息并进行匹配

授权过程

  授权过程需要启用shiro的注解:

/**
 * 开启shiro aop注解支持.
 * 使用代理方式;所以需要开启代码支持;
 * @UpdateUser:
 * @Version:     0.0.1
 * @param securityManager
 * @return       org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor
 * @throws
 */
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
}
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
    defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
    return defaultAdvisorAutoProxyCreator;
}

  主体授权(subject.isPermitted()、subject.checkRole(“xxx角色”)、shiro前端标签授权、后端授权注解
(@RequiresPermissions(“sys:log:list”))),会跳转至AuthorizingSecurityManager执行授权

/**
     * 自定义授权方法
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String token = (String) principals.getPrimaryPrincipal();
        String userId = (String) redisService.get(token);	//通过在redis中缓存的键值对查找用户ID
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //查询添加过程
        info.addRoles(getRoleByUserId(userId));
        info.addStringPermissions(getPermissionByUserId(userId));
        return info;
    }

  最后再将用户的权限信息封装并循环匹配注解中的权限信息,匹配成功返回true说明拥有权限,匹配不成功则返回false并抛出:Subject does not have permission[xxxx]异常

因篇幅问题不能全部显示,请点此查看更多更全内容