多realm 的 shiro 使用

一、通过用户名、密码登陆
二、通过短信验证码登陆

一、通过用户名、密码登陆

@PostMapping("/login")
@ApiOperation(value = "移动助手登陆用户")
public Object login(@ApiParam(required = true, value = "登录帐号和密码") @Validated @RequestBody VisitLoginReqDTO request,
                    HttpServletRequest httpServletRequest) {
    String clientIp = WebUtil.getHost(httpServletRequest);
    if (null != SecurityUtils.getSubject() && SecurityUtils.getSubject().isAuthenticated()) {
        try {
            SecurityUtils.getSubject().logout();
        } catch (Exception e) {
            // 无需处理

        }
    }
    try {
        if (login(request.getLoginId(), SecurityUtil.encryptPassword(request.getPassword()), clientIp)) {
            // 更新系统用户表 登陆信息
            request.setLastLoginIp(clientIp);
            request.setLastLoginTime(LocalDateTime.now());
            LoginReqDTO loginReqDTO = request.convertTo(LoginReqDTO.class);
            BaseResponse<Long> resp = systemUserFacade.updateUserLoginInfo(loginReqDTO);
            if (!resp.isSuccess()) {
                return resp;
            }
            BaseResponse<Object> response = new BaseResponse<>();
            UserDTO user = (UserDTO) SecurityUtils.getSubject().getPrincipal();
            // 修改日志
            loginLogUtil.saveLog(httpServletRequest, request, LoginType.LOGIN.getCode(), user);
            response.setContent(user);
            return response;
        }

    }catch (Exception e) {
        throw new ApplicationException("登录失败:" + e.getMessage(), e);
    }
    throw new ApplicationException("登录失败");
}

private static final Boolean login(String account, String password, String host) throws Exception {
        UsernamePasswordToken token = new UsernamePasswordToken(account, password, host);
        token.setRememberMe(true);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);    // **会调用 realm中的认证功能**
            return subject.isAuthenticated();
        } catch (LockedAccountException e) {
            throw new LoginException(Resources.getMessage("ACCOUNT_LOCKED", token.getPrincipal()));
        } catch (DisabledAccountException e) {
            throw new LoginException(Resources.getMessage("ACCOUNT_DISABLED", token.getPrincipal()));
        } catch (ExpiredCredentialsException e) {
            throw new LoginException(Resources.getMessage("ACCOUNT_EXPIRED", token.getPrincipal()));
        } catch (Exception e) {
            throw new ApplicationException(e.getMessage(), e);
        }
    }

Realm.java

@Component
@Slf4j
public class Realm extends BaseRealm {

  @Reference(version = "${sys.service.version}")
  private SystemUserFacade systemUserFacade;
  @Reference(version = "${sys.service.version}")
  private SystemSetsOfBooksUserFacade systemSetsOfBooksUserFacade;

// 会先进入这个方法,判断reaml是否适用于登陆的token
  @Override
  public boolean supports(AuthenticationToken token) {
      return token != null && token instanceof UsernamePasswordToken;
  }

  /**
   * 授权
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
      if (null == principals || null == principals.getPrimaryPrincipal()) {
          return null;
      }
      // 从缓存获取
      SimpleAuthorizationInfo info = ShiroUtil.getAuthorizationInfo(principals, this.sessionDAO);
      if (info == null) {
          throw new AccountException("授权失败!");
      }
      return info;
  }

  /**
   * 认证
   *
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
          throws AuthenticationException {
      log.info("调用Realm ,doGetAuthenticationInfo:");
      LoginIdReqDTO mdpSecUserSearchParam = new LoginIdReqDTO();
      String loginId = (String) authcToken.getPrincipal();  // 返回前面UsernamePasswordToken封装的 loginId
      // 查询数据库
      mdpSecUserSearchParam.setLoginId(loginId);
      log.info("登录loginId:" + loginId);
      BaseResponse<SystemUserPwdResponseDTO> userResponse = systemUserFacade.findUserPwdByLoginId(mdpSecUserSearchParam);
      log.info("登录返回:" + userResponse.toString());
      SystemUserPwdResponseDTO user = userResponse.getContent();
      if (null == user) {
          throw new AccountException("无法通过账号获取到用户数据!");
      }
      log.debug("密码:" + user.getPassword() + "_" + String.valueOf((char[]) authcToken.getCredentials()));
      if (null != user && TrueOrFalseEnum.FALSE.getValue().equals(user.getUsable())) {
          throw new AccountException("用户已被禁用!");
      } else if (null != user && !user.getPassword().equals(String.valueOf((char[]) authcToken.getCredentials()))) {
          throw new AccountException("用户名或密码错误!");
      } else {
          log.info(user.getName() + ",用户登录成功:" + user.getLoginId());
      }

      UserDTO userDTO = new UserDTO();
      userDTO.setUserId(user.getUserId());
      userDTO.setSetsOfBooksId(-1L);
      userDTO.setName(user.getName());
      userDTO.setLoginId(user.getLoginId());
      userDTO.setTenantId(user.getTenantId());
      userDTO.setMobileTelephone(user.getMobileTelephone());
      Subject currentUser = SecurityUtils.getSubject();
      if (null != currentUser) {
          super.clearCache(currentUser.getPrincipals());

          Session session = currentUser.getSession();
          userDTO.setSessionId(session.getId().toString());
      }
  	//  SecurityUtils.getSubject().getPrincipal() 获取userDTO对象
      return new SimpleAuthenticationInfo(userDTO, user.getPassword(), getName());
  }

  @Override
  protected void assertCredentialsMatch(AuthenticationToken authcToken,
                                        AuthenticationInfo info) throws AuthenticationException {
      return;
  }
}

二、短信验证码登陆
如果有另外一种登陆方式,需要重新定义 realm,token
MyShiroConfig,项目启动时调用

@Configuration
@Slf4j
public class MyShiroConfig {

    @Autowired
    private Realm realm;
    @Autowired
    private BaseRealm baseRealm;
    @Autowired
    private PhoneMessageRealm phoneMessageRealm;

    @Bean
    public Authenticator authenticator() {
        // 自定义ModularRealmAuthenticator
        ModularRealmAuthenticator authenticator = new CustomModularRealmAuthenticator();
        //设置多个个Realm
        authenticator.setRealms(Arrays.asList(realm, baseRealm,phoneMessageRealm));
        //设置多个realm认证策略,一个成功即跳过其它的
        authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        // 设置自定义authenticator
        DefaultWebSecurityManager manager = SpringContextUtil.getBean(DefaultWebSecurityManager.class);
        manager.setAuthenticator(authenticator);
        return authenticator;
    }
}

解决多个realm覆盖问题
自定义ModularRealmAuthenticator,解决多个realm之后异常覆盖的问题

@Slf4j
public class CustomModularRealmAuthenticator extends ModularRealmAuthenticator {

    @Override
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
        AuthenticationStrategy strategy = getAuthenticationStrategy();
		
        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

        if (log.isTraceEnabled()) {
            log.trace("Iterating through {} realms for PAM authentication", realms.size());
        }

        AuthenticationException authenticationException = null;
        // 遍历realms
        for (Realm realm : realms) {
            aggregate = strategy.beforeAttempt(realm, token, aggregate);
			// 调用reaml的support方法,判断这个realm是否支持登陆token
            if (realm.supports(token)) {
                log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
                AuthenticationInfo info = null;
                Throwable t = null;
                try {
                // 登陆认证
                // 如果support方法支持,第一个realm遇见报错 抛异常,后面的reaml就不会循环。
                    info = realm.getAuthenticationInfo(token);
                } catch (Throwable throwable) {
                    t = throwable;
                    if (throwable instanceof AuthenticationException) {
                        authenticationException = (AuthenticationException) throwable;
                    }
                    if (log.isDebugEnabled()) {
                        String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
                        log.debug(msg, t);
                    }
                }

                // 增加此逻辑,只有authenticationException不为null,则表示有Realm较验到了异常,则立即中断后续Realm验证直接外抛
                if (authenticationException != null) {
                    throw authenticationException;
                }
                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
            } else {
                log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
            }
        }
        aggregate = strategy.afterAllAttempts(token, aggregate);
        return aggregate;
    }
}

第二个 短信验证码登陆
登陆功能

@PostMapping("/plogin")
@ApiOperation(value = "通过短信登陆")
public Object loginByMessage(@RequestParam("code") String messCode,@RequestParam("phone") String phone,HttpServletRequest request) throws LoginException {
    try {
        if (messCode == null || messCode.equals("")) {
            throw new ApplicationException("验证码不能为空");
        }
        Subject subject = SecurityUtils.getSubject();
        if(phoneLoing(messCode, phone, subject)) {
           // 查询数据库
            LoginReqDTO loginReqDTO = new LoginReqDTO();
            loginReqDTO.setLastLoginIp(getIpAddr(request));
            loginReqDTO.setLastLoginTime(LocalDateTime.now());
            BaseResponse<Long> resp = systemUserFacade.updateUserLoginInfo(loginReqDTO);
            if (!resp.isSuccess()) {
                return resp;
            }
            BaseResponse<Object> response = new BaseResponse<>();
            UserDTO user = (UserDTO) SecurityUtils.getSubject().getPrincipal();
            VisitLoginReqDTO visitLoginReqDTO = loginReqDTO.convertTo(VisitLoginReqDTO.class);
            loginLogUtil.saveLog(request, visitLoginReqDTO, LoginType.LOGIN.getCode(), user);
            response.setContent(user);
            return response;
        }
    } finally {
//            String key = MD5Util.getMD5Code(phone + MobileConstant.MOBILE_ASSISTANT_KEY);
//            redisTemplate.delete(key);
    }
    throw new ApplicationException("登录失败");
}

private boolean phoneLoing(String messCode, String phone, Subject subject) throws LoginException {
	// 指定自定义token ,用于验证realm是否支持
    UserToken token = new UserToken(phone, messCode);
    token.setRememberMe(true);
    try {
        subject.login(token);  // 调用realm的 认证方法
        return subject.isAuthenticated();
    } catch (LockedAccountException e) {
        throw new LoginException(Resources.getMessage("ACCOUNT_LOCKED", token.getPrincipal()));
    } catch (DisabledAccountException e) {
        throw new LoginException(Resources.getMessage("ACCOUNT_DISABLED", token.getPrincipal()));
    } catch (ExpiredCredentialsException e) {
        throw new LoginException(Resources.getMessage("ACCOUNT_EXPIRED", token.getPrincipal()));
    } catch (Exception e) {
        throw new ApplicationException(e.getMessage(), e);
    }
}

自定义UserToken

@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserToken implements AuthenticationToken {

    private String mobile;
    private String messCode;
    private boolean rememberMe = false;

    public UserToken(String mobile,String messCode) {
        this.mobile = mobile;
        this.messCode= messCode;
    }

// 在realm中可获取
    @Override
    public Object getPrincipal() {
        return getMobile();
    }

    @Override
    public Object getCredentials() {
        return getMessCode();
    }
// remember 不知道怎么用
    public void setRememberMe(boolean b) {
        this.rememberMe = rememberMe;
    }
}

自定义realm PhoneMessageRealm 权限校验器

@Component
@Slf4j
public class PhoneMessageRealm extends BaseRealm {

    @Reference(version = "${sys.service.version}")
    private SystemUserFacade systemUserFacade
    @Reference(version = "${sys.service.version}")
    private SystemSetsOfBooksUserFacade systemSetsOfBooksUserFacade;
    @Reference(check = false,version = "${archives.service.version}")
    ArchivesSalesmanFacade archivesSalesmanFacade;

    @Autowired
    RedisTemplate<String,Object> redisTemplate;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token != null && token instanceof UserToken;
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (null == principals || null == principals.getPrimaryPrincipal()) {
            return null;
        }
        // 从缓存获取
        SimpleAuthorizationInfo info = ShiroUtil.getAuthorizationInfo(principals, this.sessionDAO);
        if (info == null) {
            throw new AccountException("授权失败!");
        }
        return info;
    }

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        log.info("调用Realm ,doGetAuthenticationInfo:");
        LoginIdReqDTO mdpSecUserSearchParam = new LoginIdReqDTO();
        // 调用login时 封装数据
        // UserToken token = new UserToken(phone, messCode);
        String mobile = (String) authcToken.getPrincipal();   
        String messCode = (String) authcToken.getCredentials();
		// 查数据库
        SystemUserReqDTO systemUserReqDTO = new SystemUserReqDTO();
        systemUserReqDTO.setMobileTelephone(mobile);
        BaseResponse<SystemUserPwdResponseDTO> userResponse = systemUserFacade.getUserLoginByMobile(systemUserReqDTO);

        log.info("登录返回:" + userResponse.toString());
        SystemUserPwdResponseDTO user = userResponse.getContent();
        if (null == user) {
            throw new AccountException("无法通过手机号获取到用户数据!");
        }
        if (null != user && TrueOrFalseEnum.FALSE.getValue().equals(user.getUsable())) {
            throw new AccountException("用户已被禁用!");
        }
        // 比较验证码如果不一致 throw new AccountException("验证码错误!");
        String key = MD5Util.getMD5Code(mobile + MobileConstant.MOBILE_ASSISTANT_KEY);
        String value = (String)redisTemplate.opsForValue().get(key);
        if (null == value) {
            throw new ApplicationException("验证码不存在,或已失效");
        }
        if (!value.equals(messCode)) {
            throw new ApplicationException("验证码错误");
        } else {
            log.info(user.getName() + ",用户登录成功:" + user.getLoginId());
        }
		// 封装结果
        UserDTO userDTO = new UserDTO();
        userDTO.setUserId(user.getUserId());
        userDTO.setSetsOfBooksId(-1L);
        userDTO.setName(user.getName());
        userDTO.setLoginId(user.getLoginId());
        userDTO.setTenantId(user.getTenantId());
        userDTO.setMobileTelephone(user.getMobileTelephone());
        Subject currentUser = SecurityUtils.getSubject();
        if (null != currentUser) {
            super.clearCache(currentUser.getPrincipals());

            Session session = currentUser.getSession();
            userDTO.setSessionId(session.getId().toString());
        }
		//  SecurityUtils.getSubject().getPrincipal() 获取userDTO对象
        return new SimpleAuthenticationInfo(userDTO, user.getPassword(), getName());
    }

    @Override
    protected void assertCredentialsMatch(AuthenticationToken authcToken,
                                          AuthenticationInfo info) throws AuthenticationException {
        return;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值