Shiro - 认证那些事

简介: Shiro - 认证那些事

【1】认证

① 身份认证


身份认证是第一道门户,进去之后才能谈论授权的问题。


身份验证,一般需要提供如身份ID 等一些标识信息来表明登录者的身份,如提供email,用户名/密码来证明。


在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:


principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。

credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principals 和credentials 组合就是用户名/密码了。


② 身份认证流程


1)收集用户身份/凭证,即如用户名/密码;


2)调用Subject.login进行登录,如果失败将得到相应的AuthenticationException异常,根据异常提示用户错误信息;否则登录成功;


第2)步细节如下:


创建自定义的Realm 类,继承org.apache.shiro.realm.AuthorizingRealm类,实现doGetAuthenticationInfo() 方法;

Shiro将会调用自定义的Realm 类的doGetAuthenticationInfo()方法,然后正常返回或者抛出异常;


如下图所示:

如下示例:


③ 认证过程详解

步骤如下:

首先调用Subject.login(token) 进行登录,其会自动委托给SecurityManager


SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;


Authenticator 才是真正的身份验证者,ShiroAPI 中核心的身份认证入口点,此处可以自定义插入自己的实现;


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


Authenticator 会把相应的token 传入Realm(通常是你的自定义CustomRealm.doGetAuthenticationInfo()),从Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。


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

AuthenticationException

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

⑤ Realm


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


Realm接口如下:

String getName();//返回一个唯一的Realm名字
boolean supports(AuthenticationToken token);//判断此reaml是否支持此Token
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws 
AuthenticationException;//根据Token获取认证信息

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

Realm 的继承关系如下图示:


⑥ Authenticator


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


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


如下图示所示:


Authenticator继承关系图如下:


⑦ AuthenticationStrategy


AuthenticationStrategy接口的默认实现:


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

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

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


如下图所示:

ModularRealmAuthenticator默认是AtLeastOneSuccessfulStrategy策略。看不懂没关系,继续往下看。


⑦ 代码示例

登录方法如下:

   @RequestMapping(value="/doLogin" )
    public String doLogin(Model model, HttpSession session, HttpServletRequest request, HttpServletResponse response, String  userName, String password, String randomCode) throws Exception{
        String msg;
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        log.debug("UserNamePasswordToken----:"+JSON.toJSONString(token));
        token.setRememberMe(true);
        request.setAttribute("userName", userName);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            if (subject.isAuthenticated()) {
                return "redirect:/index";
            }
        } catch (UserNameException e) {
          msg = e.getMessage();//Password for account " + token.getPrincipal() + " was incorrect.
          model.addAttribute("message", msg);
        } catch (PasswordException e) {
          msg = e.getMessage();//Password for account " + token.getPrincipal() + " was incorrect.
          model.addAttribute("message", msg);
        } catch (IncorrectCredentialsException e) {
            msg = "登录密码错误. ";//Password for account " + token.getPrincipal() + " was incorrect.
            model.addAttribute("message", msg);
        } catch (ExcessiveAttemptsException e) {
            msg = "登录失败次数过多.";
            model.addAttribute("message", msg);
        } catch (LockedAccountException e) {
            msg = "帐号已被锁定,如有疑问请联系管理员. ";//The account for username " + token.getPrincipal() + " was locked.
            model.addAttribute("message", msg);
        } catch (DisabledAccountException e) {
            msg = "帐号已被禁用. ";//The account for username " + token.getPrincipal() + " was disabled.
            model.addAttribute("message", msg);
        } catch (ExpiredCredentialsException e) {
            msg = "帐号已过期. ";//the account for username " + token.getPrincipal() + "  was expired.
            model.addAttribute("message", msg);
        } catch (UnknownAccountException e) {
            msg = "帐号不存在. ";//There is no user with username of " + token.getPrincipal()
            model.addAttribute("message", msg);
        } catch (UnauthorizedException e) {
            msg = "您没有得到相应的授权!" + e.getMessage();
            model.addAttribute("message", msg);
        }
        return "forward:/login";
    }

自定义CustomRealm.doGetAuthenticationInfo()方法如下:

  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(
      AuthenticationToken authenticationToken) throws AuthenticationException {
      UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
          //获取页面传来的用户账号
      String loginName = token.getUsername();
          //根据登录账号从数据库查询用户信息
          SysUser user = sysUserService.getUserByLoginCode(loginName);
          System.out.println("从数据库查询到的用户信息 : "+user);
          //一些异常新娘西
          if (null == user) {
            throw new UnknownAccountException();//没找到帐号
          }
          if (user.getStatus()==null||user.getStatus()==0) {
            throw new LockedAccountException();//帐号被锁定
          }
          //其他异常...
          //返回AuthenticationInfo的实现类SimpleAuthenticationInfo
          return new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
  }

需要注意的是在登录成功后,注销登录一定要走Shiro过滤器 logout:

<!--请求logout,shrio擦除sssion-->
 /logout=logout

或者自定义注销过滤器,总之要完成如下的功能:任何现有的Session都将会失效,而且任何身份都将会失去关联。在web应用程序中,RememberMe cookie也将被删除。


否则,虽然注销成功了,Shiro缓存中还认为该用户认证成功,用户访问需要认证的连接时,将会直接通过!



【2】多Realm认证与配置

多Realm认证需要将ModularRealmAuthenticator作为securityManager属性注册到容器中。


① 如下配置两个自定义Realm:

  <!-- 自定义Realm -->
    <bean id="customRealm" class="com.web.maven.shiro.CustomRealm">
        <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
        <property name="credentialsMatcher">
           <bean  class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
              <property name="hashAlgorithmName" value="MD5"/>
              <property name="hashIterations" value="1"/>
            </bean>
        </property>
    </bean>
      <!-- 自定义SecondRealm -->
    <bean id="customRealm2" class="com.web.maven.shiro.CustomRealm2">
        <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
        <property name="credentialsMatcher">
           <bean  class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
              <property name="hashAlgorithmName" value="SHA1"/>
              <property name="hashIterations" value="1"/>
            </bean>
        </property>
    </bean>

② 将两个自定义Realm配置为bean-authenticator-class为ModularRealmAuthenticator的属性

<bean id="authenticator" 
   class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
  <property name="realms">
      <list>
      <ref bean="customRealm"/>
      <ref bean="customRealm2"/>
    </list>
   </property>
</bean>

③ 将配置的authenticator注入到securityManager中

  <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 注入缓存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
        <!--注入认证器-->
        <property name="authenticator" ref="authenticator"></property>
    </bean>

④ 多Realm下就需要考虑上面说的认证策略了

在authenticator中配置如下:

 <bean id="authenticator" 
      class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
        <property name="realms">
          <list>
          <ref bean="customRealm"/>
          <ref bean="customRealm2"/>
        </list>
        </property>
        <!--ModularRealmAuthenticator默认认证策略即为AtLeastOneSuccessfulStrategy
    可不用显示配置  -->
        <property name="authenticationStrategy">
        <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
      </property>
    </bean>

【3】多Realm配置方式2

上面是把realms配置给authenticator,当然也可以直接把realms配置给securityManager。


securityManager此时配置如下所示:

<!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 注入缓存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"></property>
        <property name="realms">
          <list>
          <ref bean="jdbcRealm"/>
          <ref bean="secondRealm"/>
        </list>
        </property>
    </bean>

authenticator配置此时如下所示:

<bean id="authenticator" 
      class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
  <property name="authenticationStrategy">
    <bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy"></bean>
  </property>
</bean>

其他同上。

目录
相关文章
|
2月前
|
数据库
shiro认证和授权
shiro认证和授权
29 3
|
5月前
|
Java 数据安全/隐私保护
Shiro - 授权那些事
Shiro - 授权那些事
28 0
|
Java 数据安全/隐私保护
【Shiro】1、Shiro实现登录授权认证功能(下)
之前在 SSM 项目中使用过 shiro,发现 shiro 的权限管理做的真不错,但是在 SSM 项目中的配置太繁杂了,于是这次在 SpringBoot 中使用了 shiro,下面一起看看吧
100 0
|
安全 Java 数据库连接
【Shiro】1、Shiro实现登录授权认证功能(上)
之前在 SSM 项目中使用过 shiro,发现 shiro 的权限管理做的真不错,但是在 SSM 项目中的配置太繁杂了,于是这次在 SpringBoot 中使用了 shiro,下面一起看看吧
264 0
|
Java 数据安全/隐私保护
【Shiro】1、Shiro实现登录授权认证功能(中)
之前在 SSM 项目中使用过 shiro,发现 shiro 的权限管理做的真不错,但是在 SSM 项目中的配置太繁杂了,于是这次在 SpringBoot 中使用了 shiro,下面一起看看吧
111 0
|
存储 缓存 安全
Shiro框架01之什么是shiro+shiro的架构+权限认证
Shiro框架01之什么是shiro+shiro的架构+权限认证
Shiro框架01之什么是shiro+shiro的架构+权限认证
|
安全 数据安全/隐私保护
【Shiro】4、Shiro实现记住登录功能
用户每次在登录系统时需要重新输入账户、密码、验证码等信息,非常麻烦,于是要求加一个记住登录的功能,这对于 Shiro 来说是非常简单,下面就让我们一起来实现记住登录功能
137 0
【Shiro】4、Shiro实现记住登录功能
|
Java Apache 数据安全/隐私保护
【Shiro】Shiro从小白到大神(三)-权限认证(授权)-2
【Shiro】Shiro从小白到大神(三)-权限认证(授权)-2
135 0
|
Java 数据安全/隐私保护
【Shiro】Shiro从小白到大神(三)-权限认证(授权)-1
【Shiro】Shiro从小白到大神(三)-权限认证(授权)-1
248 0
|
存储 算法 安全
【Shiro 系列 06】Shiro 中密码加密
上篇文章和小伙伴们分享了 Realm 的认证策略问题,本文我想和小伙伴们来聊一聊密码的加密问题。
【Shiro 系列 06】Shiro 中密码加密