Shiro认证授权业务软件实现流程

shiro简介

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能:

1) 认证 - 用户身份识别,常被称为用户“登录”;

2) 授权 - 访问控制;

3) 密码加密 - 保护或隐藏数据防止被偷窥。

4) 会话管理 - 每用户相关的时间敏感的状态。

对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro要简单的多。下面重点分析认证、授权和密码加密的过程。

shiro整体框架

Subject可以理解成当前操作用户。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。

Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。

Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

一定程度上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。

Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。

Authenticator认证就是核实用户身份的过程。这个过程登录验证的用户密码组合。多数用户在登录软件系统时,通常提供自己的用户名和密码。如果存储在系统中的密码与用户提供的匹配,他们就被认为通过认证。

Authorizer授权实质上就是访问控制,即控制用户能够访问应用中的哪些内容,比如资源、Web页面等等。

SessionManager。Shiro为任何应用提供了一个会话编程范式 ,可在任何应用或架构层一致地使用Session API,从小型后台独立应用到大型集群Web应用。这意味着,那些希望使用会话的应用开发者,不必被迫使用Servlet或EJB容器了。

CacheManager 对Shiro的其他组件提供缓存支持。(源代码里看支持ECache。

业务流程实现流程

Authentication认证

1.用户根据表单信息填写用户名和密码,然后调用登陆login函数。

内部执行如下:

//1.得到Subject
Subject subject = SecurityUtils.getSubject();
//2.调用登录方法
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);//当这一代码执行时,就会自动跳入到AuthRealm中认证方法

2.代理DelegatingSubject继承Subject执行login

public void login(AuthenticationToken token) throws AuthenticationException {
    clearRunAsIdentitiesInternal();
    Subject subject = securityManager.login(this, token);

    PrincipalCollection principals;

    String host = null;

    if (subject instanceof DelegatingSubject) {
        DelegatingSubject delegating = (DelegatingSubject) subject;
        //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
        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);
    }
    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 = decorate(session);
    } else {
        this.session = null;
    }
}

3.调用DefaultSecurityManager继承SessionsSecurityManager执行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;
}

4.认证管理器AuthenticatingSecurityManager继承RealmSecurityManager执行authenticate方法

public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
    return this.authenticator.authenticate(token);
}

5.抽象认证管理器AbstractAuthenticator继承Authenticator, LogoutAware 执行authenticate方法

public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {

    if (token == null) {
        throw new IllegalArgumentException("Method argumet (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);
        }
        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;
}

6.ModularRealmAuthenticator继承AbstractAuthenticator执行doAuthenticate方法

 protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
    assertRealmsConfigured();
    Collection<Realm> realms = getRealms();
    if (realms.size() == 1) {
        return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
    } else {
        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;
}

7.AuthenticatingRealm继承CachingRealm执行getAuthenticationInfo方法

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

    AuthenticationInfo info = getCachedAuthenticationInfo(token); //从缓存中读取
    if (info == null) {
        //otherwise not cached, perform the lookup:
        info = doGetAuthenticationInfo(token);  //缓存中读不到,则到数据库或者ldap或者jndi等去读
        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;
}

从缓存中读取的方法

private Cache<Object, AuthenticationInfo> getAuthenticationCacheLazy() {

    if (this.authenticationCache == null) {

        log.trace("No authenticationCache instance set.  Checking for a cacheManager...");

        CacheManager cacheManager = getCacheManager();

        if (cacheManager != null) {
            String cacheName = getAuthenticationCacheName();
            log.debug("CacheManager [{}] configured.  Building authentication cache '{}'", cacheManager, cacheName);
            this.authenticationCache = cacheManager.getCache(cacheName);
        }
    }

    return this.authenticationCache;
}

从数据库中读取的方法

执行自定义的AuthRealm的doGetAuthenticationInfo方法,继承于AuthorizingRealm

//认证   token 代表用户在界面输入的用户名和密码
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("认证");

    //1.向下转型
    UsernamePasswordToken upToken  = (UsernamePasswordToken) token;

    //2.调用业务方法,实现根据用户名查询
    String hql = "from User where userName=?";
    List<User> list = userService.find(hql, User.class, new String[]{upToken.getUsername()});
    if(list!=null && list.size()>0){
        User user = list.get(0);
        AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
        return info;   //此处如果返回,就会立即进入到密码比较器
    }

    return null;//就会出现异常
}

认证流程图

这里写图片描述

Authorization授权

1、shiro标签执行haspermission的时候执行ispermitted函数,调用的isPermitted是Subject的实现类DelegatingSubject执行的。

public boolean isPermitted(String permission) {
    return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
}

2、实现类实际是自定义的AuthRealm,继承抽象类是AuthorizingRealm。调用的securityManager.isPermitted函数,最终的执行函数是是基于AuthorizingRealm该抽象类实现的。层层调用都是在该实现类内完成。

public boolean isPermitted(PrincipalCollection principals, String permission) {
    Permission p = getPermissionResolver().resolvePermission(permission);
    return isPermitted(principals, p);
}

3、AuthorizingRealm执行getAuthenticationInfo方法

主要做了两个操作:

  • 缓存操作
  • 从自定义Ream中调用实现接口函数获取数据

Cache<Object, AuthorizationInfo> cache = getAvailableAuthorizationCache();
    if (cache != null) {
        if (log.isTraceEnabled()) {
            log.trace("Attempting to retrieve the AuthorizationInfo from cache.");
        }
        Object key = getAuthorizationCacheKey(principals);
        info = cache.get(key);
        if (log.isTraceEnabled()) {
            if (info == null) {
                log.trace("No AuthorizationInfo found in cache for principals [" + principals + "]");
            } else {
                log.trace("AuthorizationInfo found in cache for principals [" + principals + "]");
            }
        }
    }


    if (info == null) {
        // Call template method if the info was not found in a cache
        info = doGetAuthorizationInfo(principals);
        // If the info is not null and the cache has been created, then cache the authorization info.
        if (info != null && cache != null) {
            if (log.isTraceEnabled()) {
                log.trace("Caching authorization info for principals: [" + principals + "].");
            }
            Object key = getAuthorizationCacheKey(principals);
            cache.put(key, info);
        }
    }

4、自定义Ream实现doGetAuthorizationInfo接口函数

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
    System.out.println("授权");
    User user = (User) pc.fromRealm(this.getName()).iterator().next();//根据realm的名字去找对应的realm

    Set<Role > roles = user.getRoles();//对象导航
    List<String> permissions = new ArrayList<String>();
    for(Role role :roles){
        //遍历每个角色 
        Set<Module> modules = role.getModules();//得到每个角色下的模块列表
        for(Module m :modules){
            permissions.add(m.getName());
        }
    }

    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addStringPermissions(permissions);//添加用户的模块(权限)
    return info;
}

5、根据回调的Info值判断是否有该permission值,有则返回true。

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

授权业务流程图:

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值