zoukankan      html  css  js  c++  java
  • shiro基础之认证授权

    复习学习建议:
    1.了解整体架构以及一些基本概念词汇(https://www.cnblogs.com/e206842/p/9047263.html 该博文的第一部分)
    2.学习本篇博文,学习shiro基本流程和原理
    3.学习shiro与spring的整合(https://www.cnblogs.com/e206842/p/9047263.html 该博文的第二部分)

    一、从Helloworld探究原理

    public static void main(String[] args) {
    
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
    
        SecurityUtils.setSecurityManager(securityManager);
    
        // 获取当前的 Subject. 调用 SecurityUtils.getSubject();
        Subject currentUser = SecurityUtils.getSubject();
    
        // Do some stuff with a Session (no need for a web or EJB container!!!)
        // 测试使用 Session 
        // 获取 Session: Subject#getSession()
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("---> Retrieved the correct value! [" + value + "]");
        }
    
        // let's login the current user so we can check against roles and permissions:
        // 测试当前的用户是否已经被认证. 即是否已经登录. 
        // 调动 Subject 的 isAuthenticated() 
        if (!currentUser.isAuthenticated()) {
        	// 把用户名和密码封装为 UsernamePasswordToken 对象
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            // rememberme
            token.setRememberMe(true);
            try {
            	// 执行登录. 
                currentUser.login(token);
            } 
            // 若没有指定的账户, 则 shiro 将会抛出 UnknownAccountException 异常. 
            catch (UnknownAccountException uae) {
                log.info("----> There is no user with username of " + token.getPrincipal());
                return; 
            } 
            // 若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。 
            catch (IncorrectCredentialsException ice) {
                log.info("----> Password for account " + token.getPrincipal() + " was incorrect!");
                return; 
            } 
            // 用户被锁定的异常 LockedAccountException
            catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                        "Please contact your administrator to unlock it.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            // 所有认证时异常的父类. 
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }
    
        //认证之后授权
        //test a role:
        // 测试是否有某一个角色. 调用 Subject 的 hasRole 方法. 
        if (currentUser.hasRole("schwartz")) {
            log.info("----> May the Schwartz be with you!");
        } else {
            log.info("----> Hello, mere mortal.");
            return; 
        }
    
        //a (very powerful) Instance Level permission:
        // 测试用户是否具备某一个行为. 
        if (currentUser.isPermitted("user:delete:zhangsan")) {
            log.info("----> You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }
    }
    

    二、执行流程

    1、通过ini配置文件创建securityManager
    helloWord使用的读取ini(含有用户、角色和权限配置)文件的形式,使用的是IniRealm ,正式使用时ini文件中的信息来自于通过自定义realm(继承AuthorizingRealm即可)查询的数据库

    2、调用subject.login方法主体提交认证,提交的token
    3、securityManager进行认证,securityManager最终由ModularRealmAuthenticator进行认证。

    Authenticator 可能会委托给相应的 AuthenticationStrategy
    行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用
    AuthenticationStrategy 进行多 Realm 身份验证

    4、ModularRealmAuthenticator调用IniRealm(给realm传入token) 去ini配置文件中查询用户信息
    5、IniRealm根据输入的token(UsernamePasswordToken)从 shiro-first.ini查询用户信息,根据账号查询用户信息(账号和密码)
    如果查询到用户信息,就给ModularRealmAuthenticator返回用户信息(账号和密码)AuthenticationInfo
    正式使用时:在自定义的realm重写父类的doGetAuthenticationInfo的方法中,构建用户信息

    //自定义relam
    public class ShiroRealm extends AuthorizingRealm {
    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
    		AuthenticationToken token) throws AuthenticationException {
    	System.out.println("[FirstRealm] doGetAuthenticationInfo");
    	
    	//1. 把 AuthenticationToken 转换为 UsernamePasswordToken 
    	UsernamePasswordToken upToken = (UsernamePasswordToken) token;
    	
    	//2. 从 UsernamePasswordToken 中来获取 username
    	String username = upToken.getUsername();
    	
    	//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
    	System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");
    	
    	//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
    	if("unknown".equals(username)){
    		throw new UnknownAccountException("用户不存在!");
    	}
    	
    	//5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常. 
    	if("monster".equals(username)){
    		throw new LockedAccountException("用户被锁定");
    	}
    	
    	//6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
    	//以下信息是从数据库中获取的.
    	//1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. 
    	Object principal = username;
    	//2). credentials: 密码. 
    	Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
    	if("admin".equals(username)){
    		credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
    	}else if("user".equals(username)){
    		credentials = "098d2c478e9c11555ce2823231e02ec1";
    	}
    	
    	//3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
    	String realmName = getName();
    	//4). 盐值. 
    	ByteSource credentialsSalt = ByteSource.Util.bytes(username);
    	
    	SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
    	info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
    	return info;
    }
    }
    

    如果查询不到,就给ModularRealmAuthenticator返回null
    6、ModularRealmAuthenticator接收IniRealm返回Authentication认证信息
    如果返回的认证信息是null,ModularRealmAuthenticator抛出异常 (org.apache.shiro.authc.UnknownAccountException)
    如果返回的认证信息不是null(说明inirealm找到了用户),对IniRealm返回用户密码 (在ini文件中存在)和 token中的密码 进行对比,如果不一致抛出异常(org.apache.shiro.authc.IncorrectCredentialsException)

    7、内部密码比对示意图

    AuthenticationStrategy
    • AuthenticationStrategy 接口的默认实现:
    • FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第
    一个 Realm 身份验证成功的认证信息,其他的忽略;
    • AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和
    FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信
    息;
    • AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有
    Realm身份验证成功的认证信息,如果有一个失败就失败了。
    • ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy
    策略

    三、授权执行流程

    1、首先调用 Subject.isPermitted/hasRole 接口,其会委托给SecurityManager,而 SecurityManager 接着会委托给 Authorizer;
    2、Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过PermissionResolver 把字符串转换成相应的 Permission 实例;
    3、在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限信息AuthorizationInfo用于匹配传入的角色/权限;
    4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个Realm,会委托给 ModularRealmAuthorizer 进行循环判断,如果匹配如 isPermitted/hasRole 会返回true,否则返回false表示授权失败。
    5、授权内部示意图

  • 相关阅读:
    2014-写给明年现在的自己
    DDD 领域驱动设计-Value Object(值对象)如何使用 EF 进行正确映射
    DDD 领域驱动设计-在动手之前,先把你的脑袋清理干净
    醍醐灌顶:领域驱动设计实现之路
    拨乱反正:DDD 回归具体的业务场景,Domain Model 再再重新设计
    设计窘境:来自 Repository 的一丝线索,Domain Model 再重新设计
    No zuo no die:DDD 应对具体业务场景,Domain Model 重新设计
    拨开迷雾,找回自我:DDD 应对具体业务场景,Domain Model 到底如何设计?
    【记录】GitHub/TortoiseGit 修改邮箱/提交者
    死去活来,而不变质:Domain Model(领域模型) 和 EntityFramework 如何正确进行对象关系映射?
  • 原文地址:https://www.cnblogs.com/laoyin666/p/10625135.html
Copyright © 2011-2022 走看看