shiro(4)-认证

1 Shiro 架构 (Shiro外部来看)

• 从外部来看Shiro ,即从应用程序角度的来观察如何使用 Shiro 完成工作:


image

2 身份验证

身份验证:一般需要提供如身份 ID 等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。
• 在 shiro 中,用户需要提供 principals (身份)credentials(证明)给 shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。
credentials证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
• 最常见的 principals 和 credentials 组合就是用户名/密码

3 身份验证基本流程

• 1、收集用户身份/凭证,即如用户名/密码
• 2、调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功
• 3、创建自定义的 Realm 类,继承org.apache.shiro.realm.AuthorizingRealm 类,实现doGetAuthenticationInfo() 方法

3.1 详细登录认证流程

  1. 获取当前的 Subject, 调用 SecurityUtils.getSubject();
  2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用Subject de isAuthenticated();
  3. 若没有被认证, 则把 用户名密码 封装为 UsernamePasswordToken 对象 ;
  • 1 创建一个表单页面
  • 2 把请求提交到 SpringMVC 的 Handler
  • 3 获取用户名和密码
  1. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法;
  2. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给Shiro.
  • 1 实际上需要继承 org.apache.shche.shiro.realm.AuthenticationRealm
  • 2 实现 doGetAuthenticationInfo(AuthenticationToken) 方法
  1. 由 Shiro 完成对密码的比对

4 身份验证示例

image

5 AuthenticationException

• 如果身份验证失败请捕获 AuthenticationException 或其子类
• 最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;


image

6 认证流程

image

7 身份认证流程

• 1、首先调用 Subject.login(token) 进行登录,其会自动委托给SecurityManager
• 2、SecurityManager 负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;
• 3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
• 4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用AuthenticationStrategy 进行多 Realm 身份验证;
• 5、Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

8 Realm

• Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),即 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作

• Realm接口如下:


image.png

一般继承 AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。

• Realm 的继承关系:


image.png

9 Authenticator

• Authenticator 的职责是验证用户帐号,是 Shiro API 中身份验证核心的入口点:如果验证成功,将返回AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 异常

• SecurityManager 接口继承了 Authenticator,另外还有一个ModularRealmAuthenticator实现,其委托给多个Realm 进行验证,验证规则通过 AuthenticationStrategy 接口指定

10 AuthenticationStrategy

• AuthenticationStrategy 接口的默认实现:

FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;

AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy 不同,将返回所有Realm身份验证成功的认证信息;

AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

• ModularRealmAuthenticator 默认是 AtLeastOneSuccessfulStrategy策略

11 代码

11.1) 实现认证流程

1. login.jsp

username:

password:

2. UserAction 增加登录方法(login)

    @PostMapping(value = "login")
    public String login(String username, String password){
        // 1. 获取当前的 `Subject`, 调用 `SecurityUtils.getSubject()`;
        Subject subject = SecurityUtils.getSubject();
        //2. 测试当前的用户是否已经被认证. 即是否已经登录. 调用`Subject de isAuthenticated()`;
        if (!subject.isAuthenticated()){
            //3. 若没有被认证, 则把 `用户名` 和 `密码` 封装为 `UsernamePasswordToken 对象` ;
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            // rememberme
//            token.setRememberMe(true);
            //4. 执行登录: 调用 Subject 的 login(AuthenticationToken) 方法;
            System.out.println("token:"+token.hashCode());
            try {
                subject.login(token);
            } catch (AuthenticationException e) {
                System.out.println("登录失败: " + e.getMessage());
            }
            //5. 自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给Shiro.
            //  - 1 实际上需要继承 `org.apache.shche.shiro.realm.AuthenticationRealm` 类
            //  - 2 实现 doGetAuthenticationInfo(AuthenticationToken) 方法
            //6. 由 Shiro 完成对密码的比对
        }

        return "redirect:/list";
    }

3. ShiroRealm 修改realm

public class ShiroRealm extends AuthorizingRealm {

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("doGetAuthenticationInfo:"+authenticationToken.hashCode());
        return null;
    }
}

4. 测试

11.2) 实现认证(Realm)

1. 修改application-shiro.xml


            
                /login = anon
                /logout = logout

                
                # everything else requires authentication:
                /** = authc
            
        

2. ShiroRealm(修改Realm)

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1. 把 AuthenticationToken 转换为 UsernamePasswordToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //2. 从 UsernamePasswordToken 中获取 Username
        String username = token.getUsername();
        //3. 调用数据库的方法, 从数据库中查询 Username的用户信息
        System.out.println("用户名:"+username);
        //4. 若用户不存在, 可以抛出UnknownAccountException 异常
        if ("unknown".equals(username)) {
            throw new UnknownAccountException("用户不存在");
        }
        //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常
        if ("master".equals(username)) {
            throw new AuthenticationException("用户被锁定");
        }
        //6. 根据用户的情况, 来构建 AuthenicationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
        //1) principal: 认证的实体信息, 可以是username, 也可以是数据表对应的用户的实体类对象
        Object principal = username;
        //2) credentials: 密码
        Object credentials = "123456";
        //3) realName: 当前 realm 对象的name, 调用父类的 getname() 方法即可
        String realName = getName();
        SimpleAuthenticationInfo info
                = new SimpleAuthenticationInfo(principal, credentials, realName);
        return info;
    }

3. 增加退出 修改list.jsp

Logout

11.3) MD5加密

密码的比对:
通过 AuthenticatingRealm 的 credentialsMatcher 属性来进行的密码的比对!

1. 查看<123456>加密后的结果

    public static void main(String[] args) {
        //加密方式
        String hashAlgorithmName = "MD5";
        //要加密的密码
        Object credentials = "123456";
        //盐值
        Object salt = null;
        //加密次数
        int hashIterations = 1024;

        Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
        System.out.println(result);
    }

2. 修改realm的配置文件(application-shiro.xml)


        
            
                
                
            
        
    

11.4) MD5盐值加密

  1. 为什么使用 MD5 盐值加密:
    为了防止不同用户使用同一密码加密后密码一样
  2. 如何做到:
    1). 在 doGetAuthenticationInfo 方法返回值创建 SimpleAuthenticationInfo 对象的时候, 需要使用
    SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName) 构造器
    2). 使用 ByteSource.Util.bytes() 来计算盐值.
    3). 盐值需要唯一: 一般使用随机字符串或 user id
    4). 使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值.

1. 修改main方法

        String hashAlgorithmName = "MD5";
        Object credentials = "123456";
        Object salt = ByteSource.Util.bytes("admin");
        int hashIterations = 1024;

        Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
        System.out.println(result);

2. 修改doget

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1. 把 AuthenticationToken 转换为 UsernamePasswordToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //2. 从 UsernamePasswordToken 中获取 Username
        String username = token.getUsername();
        //3. 调用数据库的方法, 从数据库中查询 Username的用户信息
        System.out.println("用户名:"+username);
        //4. 若用户不存在, 可以抛出UnknownAccountException 异常
        if ("unknown".equals(username)) {
            throw new UnknownAccountException("用户不存在");
        }
        //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常
        if ("master".equals(username)) {
            throw new AuthenticationException("用户被锁定");
        }
        //6. 根据用户的情况, 来构建 AuthenicationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
        //1) principal: 认证的实体信息, 可以是username, 也可以是数据表对应的用户的实体类对象
        Object principal = username;

//-----------------------------修改位置(以下都是)-------------------------
        //2) credentials: 密码
//        Object credentials = "fc1709d0a95a6be30bc5926fdb7f22f4";
        Object credentials = null;
        if ("user".equals(username)) {
            credentials = "098d2c478e9c11555ce2823231e02ec1";
        } else if ("admin".equals(username)) {
            credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
        }
        //3) realName: 当前 realm 对象的name, 调用父类的 getname() 方法即可
        String realName = getName();
        //4) 盐值
        ByteSource credentialsSalt = ByteSource.Util.bytes(username);
        SimpleAuthenticationInfo info
//                = new SimpleAuthenticationInfo(principal, credentials, realName);
                = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realName);
        return info;
    }

11.5) 多 realm 验证

1. 创建Realm(SecondRealm)

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("SecondRealm:"+authenticationToken.hashCode());
        //1. 把 AuthenticationToken 转换为 UsernamePasswordToken
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //2. 从 UsernamePasswordToken 中获取 Username
        String username = token.getUsername();
        //3. 调用数据库的方法, 从数据库中查询 Username的用户信息
        System.out.println("用户名:"+username);
        //4. 若用户不存在, 可以抛出UnknownAccountException 异常
        if ("unknown".equals(username)) {
            throw new UnknownAccountException("用户不存在");
        }
        //5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常
        if ("master".equals(username)) {
            throw new AuthenticationException("用户被锁定");
        }
        //6. 根据用户的情况, 来构建 AuthenicationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
        //1) principal: 认证的实体信息, 可以是username, 也可以是数据表对应的用户的实体类对象
        Object principal = username;
        //2) credentials: 密码
//        Object credentials = "fc1709d0a95a6be30bc5926fdb7f22f4";
        Object credentials = null;
        if ("user".equals(username)) {
            credentials = "073d4c3ae812935f23cb3f2a71943f49e082a718";
        } else if ("admin".equals(username)) {
            credentials = "ce2f6417c7e1d32c1d81a797ee0b499f87c5de06";
        } 
        //3) realName: 当前 realm 对象的name, 调用父类的 getname() 方法即可
        String realName = getName();
        //4) 盐值
        ByteSource credentialsSalt = ByteSource.Util.bytes(username);
        SimpleAuthenticationInfo info
//                = new SimpleAuthenticationInfo(principal, credentials, realName);
                = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realName);
        return info;
    }

    public static void main(String[] args) {
        String hashAlgorithmName = "SHA1";
        Object credentials = "123456";
        Object salt = ByteSource.Util.bytes("user");
        int hashIterations = 1024;

        Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
        System.out.println(result);
    }

2. application-shiro.xml


    
        
        

    


        
            
                
                
            
        
    


        
            
                
                
            
        
    

你可能感兴趣的:(shiro(4)-认证)