shiro讲解之 Authentication
本章节将详细讲解Shiro的 Authentication 流程。
概念
官方定义:
- Authentication: Sometimes referred to as ‘login’, this is the act of proving a user is who they say they are.
通常而言:
- Shiro 认证更大程度上特指用户登录并对用户信息进行校验(这里的信息通常特指principal信息,常见的为用户名/密码组合),如果信息验证成功则顺序执行代码,否则抛出异常。
认证流程(Authentication Flow)
身份验证
身份验证
- 一般需要提供如身份 ID 等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。在 shiro 中,用户需要提供 principals (身份)和 credentials(证明)给 shiro,从而应用能验证用户身份。
principals
- 身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个 principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。
credentials
- 证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
最常见的 principals 和 credentials 组合就是用户名/密码了。
身份验证基本流程
收集用户身份/凭证,即如用户名/密码
核心代码
``` UsernamePasswordToken token = new UsernamePasswordToken(username, password); ```
其中 username 和 password 为用户登录的用户名和密码。
调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功
核心代码
try { Subject subject = SecurityUtils.getSubject(); subject.login(token); } catch (UnknownAccountException ue) { // 没有该用户 logger.error("This is no such user with name of " + token.getPrincipal()); return "erro"; } catch (IncorrectCredentialsException Ie) { // 密码错误 logger.error("This password for account " + token.getPrincipal() + " is incorrect"); return "erro"; } catch (LockedAccountException Le) { logger.error("This account was locked!"); return "erro"; } catch (AuthenticationException e) { //认证过程所有异常的父类异常。也可以直接捕获父类异常。 logger.error("Authentication Failed"); return "erro"; }
创建自定义的 Realm 类,继承 org.apache.shiro.realm.AuthorizingRealm 类,实现
doGetAuthenticationInfo() 方法核心代码示例
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 对应的用户记录,此处用SubjectEntity模拟从数据库中查询出来的数据。 SubjectEntity principals = new SubjectEntity("123456", "Dustyone"); // 4. 若用户不存在, 则可以抛出 UnknownAccountException 异常,亦可在Controller中捕获 if ("unknown".equals(username)) { throw new UnknownAccountException("用户不存在!"); } // 5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常.同上 if ("monster".equals(username)) { throw new LockedAccountException("用户被锁定"); } // 6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: // SimpleAuthenticationInfo // 以下信息是从数据库中获取的. // 1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. Object principal = principals.getUsername(); // 2). credentials: 密码. Object credentials = principals.getPassword(); // 3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可 String realmName = getName(); //Shiro封装的校验Principals 和 credentials 的具体实现方法。在后续的学习中我们在深入学习认证策略和加密时将会着重学习这块。 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName); return info; }
认证异常(AuthenticationException)
AuthenticationException 是在认证过程中所有Shiro 定义的异常的父类,一般直接捕获该父类异常即可,但若想获得 关于 AuthenticationException 的详细信息时(一般我们都是这么做的),我们可以挨个捕获。下面我们列举下AuthenticationException的所有异常。
AuthenticationException(所有认证异常父类)
AccountException(Account类异常)
ConcurrentAccessException(用户重复登录)
DisabledAccountException(当前用户不可用)
- LockedAccountException(当前用户被锁定,禁止登录)
ExcessiveAttemptsException(登录重试次数,超限。只允许在一段时间内允许有一定数量的认证尝试)
UnknownAccountException(未知账户/没找到帐号,登录失败)
CredentialsException(Credentials类异常)
ExpiredCredentialsException(口令已过期)
IncorrectCredentialsException(口令不匹配错误)
UnSupportedTokenException(身份令牌异常,不支持的身份令牌)
小结
认证流程
身份认证流程
首先调用 Subject.login(token) 进行登录,其会自动委托给SecurityManager
SecurityManager 负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证
Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现
Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用AuthenticationStrategy 进行多 Realm 身份验证
Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问