CAS单点登录自定义登录页面异常提示信息

一、第一个问题

看了很多文章,遇到的问题是:在自定义的认证处理器中抛出了自定义的异常,配置文件也已经配好自定义的异常,但是登录界面提示的错误信息依然是默认的AccountNotFountException配置的信息。通过debug发现了端倪,最后覆盖源码的AuthenticationExceptionHandlerAction.java就好了。
  1. 自定义异常

  1. yml配置

  1. 国际化文件配置

  1. 在自定义认证器抛出异常

  1. 复制源码AuthenticationExceptionHandlerAction.java并修改覆盖源码

5.1建包:

5.2AuthenticationExceptionHandlerAction.java源码70-85行左右)修改后的结果:

为什么这样修改?读者可以dubug自行调试这部分代码,这里不再介绍(如何调试请看标题一、6.)。


package org.apereo.cas.web.flow.actions;

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Generated;
import org.apereo.cas.authentication.AuthenticationException;
import org.apereo.cas.services.UnauthorizedServiceForPrincipalException;
import org.apereo.cas.ticket.AbstractTicketException;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.action.EventFactorySupport;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

public class AuthenticationExceptionHandlerAction extends AbstractAction {
  @Generated
  private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationExceptionHandlerAction.class);
  private static final String DEFAULT_MESSAGE_BUNDLE_PREFIX = "authenticationFailure.";
  private static final String UNKNOWN = "UNKNOWN";
  private final Set<Class<? extends Throwable>> errors;
  private String messageBundlePrefix;

  public AuthenticationExceptionHandlerAction() {
    this(new LinkedHashSet());
  }

  public AuthenticationExceptionHandlerAction(final Set<Class<? extends Throwable>> errors) {
    this.messageBundlePrefix = "authenticationFailure.";
    this.errors = errors;
  }

  public Set<Class<? extends Throwable>> getErrors() {
    return new LinkedHashSet(this.errors);
  }

  public String handle(final Exception e, final RequestContext requestContext) {
    MessageContext messageContext = requestContext.getMessageContext();
    if (e instanceof AuthenticationException) {
      return this.handleAuthenticationException((AuthenticationException)e, requestContext);
    } else if (e instanceof AbstractTicketException) {
      return this.handleAbstractTicketException((AbstractTicketException)e, requestContext);
    } else {
      LOGGER.trace("Unable to translate errors of the authentication exception [{}]. Returning [{}]", e, "UNKNOWN");
      String messageCode = this.messageBundlePrefix + "UNKNOWN";
      messageContext.addMessage((new MessageBuilder()).error().code(messageCode).build());
      return "UNKNOWN";
    }
  }

  protected String handleAuthenticationException(final AuthenticationException e, final RequestContext requestContext) {
    if (e.getHandlerErrors().containsKey(UnauthorizedServiceForPrincipalException.class.getSimpleName())) {
      URI url = WebUtils.getUnauthorizedRedirectUrlIntoFlowScope(requestContext);
      if (url != null) {
        LOGGER.warn("Unauthorized service access for principal; CAS will be redirecting to [{}]", url);
        return "serviceUnauthorizedCheck";
      }
    }

    Collection<Class> values = (Collection) e.getHandlerErrors().values().stream().map(Object::getClass).collect(Collectors.toList());
    Objects.requireNonNull(values);
    ArrayList<Class> tempList = new ArrayList(values);
    Class myException = null;
    for (Class clazz : tempList) {
      // 自定义登录异常类,必须以My开头
      if (clazz.getSimpleName().contains("My")) {
        myException = clazz;
        break;
      }
    }
    if (myException == null) {
      myException = tempList.get(0);
    }
    String handlerErrorName = myException.getSimpleName();
    MessageContext messageContext = requestContext.getMessageContext();
    String messageCode = this.messageBundlePrefix + handlerErrorName;
    messageContext.addMessage((new MessageBuilder()).error().code(messageCode).build());
    return handlerErrorName;
  }

  protected String handleAbstractTicketException(final AbstractTicketException e, final RequestContext requestContext) {
    MessageContext messageContext = requestContext.getMessageContext();
    Optional<String> match = this.errors.stream().filter((c) -> {
      return c.isInstance(e);
    }).map(Class::getSimpleName).findFirst();
    match.ifPresent((s) -> {
      messageContext.addMessage((new MessageBuilder()).error().code(e.getCode()).build());
    });
    return (String)match.orElse("UNKNOWN");
  }

  protected Event doExecute(final RequestContext requestContext) {
    Event currentEvent = requestContext.getCurrentEvent();
    LOGGER.debug("Located current event [{}]", currentEvent);
    Exception error = (Exception)currentEvent.getAttributes().get("error", Exception.class);
    if (error != null) {
      LOGGER.debug("Located error attribute [{}] with message [{}] from the current event", error.getClass(), error.getMessage());
      String event = this.handle(error, requestContext);
      LOGGER.debug("Final event id resolved from the error is [{}]", event);
      return (new EventFactorySupport()).event(this, event, currentEvent.getAttributes());
    } else {
      return (new EventFactorySupport()).event(this, "error");
    }
  }
}

5.3配置spring.factories扫描注册这个类到容器中:(其实不需要配置只有启动类扫描不到的且需要被注册为bean的类才需要配置,如@Configuration等注解标注的类

  1. cas调试debug

build.cmd debug方式启动。

并且配置一个启动项,在build.cmd debug命令执行完成后启动

7.cas客户端一个项目启动多个端口

二、第二个问题

自定义异常在登录页面生效,但是提示信息是国际化文件配置的固定信息,不是代码抛出异常时括号中定义的信息。
throw new MyFailedLoginException("account and password do not match!");
  1. 继续改造目录标题一5.2中已建好的AuthenticationExceptionHandlerAction.java文件中的handleAuthenticationException方法

//获取自定义异常携带的自定义提示信息并通过.defaultText(customerExceptionMsg)向下传递


package org.apereo.cas.web.flow.actions;

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import com.genertech.plm.aia.login.cas.customerAuthentication.MyAccountDisabledException;
import com.genertech.plm.aia.login.cas.customerAuthentication.MyAccountLockedException;
import com.genertech.plm.aia.login.cas.customerAuthentication.MyFailedLoginException;
import lombok.Generated;
import org.apereo.cas.authentication.AuthenticationException;
import org.apereo.cas.services.UnauthorizedServiceForPrincipalException;
import org.apereo.cas.ticket.AbstractTicketException;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.action.EventFactorySupport;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 该类规定了自定义异常类应以My开头
 * 覆盖修改AuthenticationExceptionHandlerAction.java源码,解决自定义异常登录界面提示信息无效 和
 * 自定义异常的msg在登录界面不展示提示信息(展示的是国际化配置文件配置的固定信息)
 */
public class AuthenticationExceptionHandlerAction extends AbstractAction {
  @Generated
  private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationExceptionHandlerAction.class);
  private static final String DEFAULT_MESSAGE_BUNDLE_PREFIX = "authenticationFailure.";
  private static final String UNKNOWN = "UNKNOWN";
  private final Set<Class<? extends Throwable>> errors;
  private String messageBundlePrefix;


  public AuthenticationExceptionHandlerAction() {
    this(new LinkedHashSet());
  }

  public AuthenticationExceptionHandlerAction(final Set<Class<? extends Throwable>> errors) {
    this.messageBundlePrefix = "authenticationFailure.";
    this.errors = errors;
  }

  public Set<Class<? extends Throwable>> getErrors() {
    return new LinkedHashSet(this.errors);
  }

  public String handle(final Exception e, final RequestContext requestContext) {
    MessageContext messageContext = requestContext.getMessageContext();
    if (e instanceof AuthenticationException) {
      return this.handleAuthenticationException((AuthenticationException) e, requestContext);
    } else if (e instanceof AbstractTicketException) {
      return this.handleAbstractTicketException((AbstractTicketException) e, requestContext);
    } else {
      LOGGER.trace("Unable to translate errors of the authentication exception [{}]. Returning [{}]", e, "UNKNOWN");
      String messageCode = this.messageBundlePrefix + "UNKNOWN";
      messageContext.addMessage((new MessageBuilder()).error().code(messageCode).build());
      return "UNKNOWN";
    }
  }

  protected String handleAuthenticationException(final AuthenticationException e, final RequestContext requestContext) {
    if (e.getHandlerErrors().containsKey(UnauthorizedServiceForPrincipalException.class.getSimpleName())) {
      URI url = WebUtils.getUnauthorizedRedirectUrlIntoFlowScope(requestContext);
      if (url != null) {
        LOGGER.warn("Unauthorized service access for principal; CAS will be redirecting to [{}]", url);
        return "serviceUnauthorizedCheck";
      }
    }

    Collection<Class> values = (Collection) e.getHandlerErrors().values().stream().map(Object::getClass).collect(Collectors.toList());
    Objects.requireNonNull(values);
    ArrayList<Class> tempList = new ArrayList(values);
    Class myException = null;
    for (Class clazz : tempList) {
      // 自定义登录异常类,必须以My开头
      if (clazz.getSimpleName().contains("My")) {
        myException = clazz;
        break;
      }
    }
    if (myException == null) {
      myException = tempList.get(0);
    }
    String handlerErrorName = myException.getSimpleName();
    MessageContext messageContext = requestContext.getMessageContext();
    String messageCode = this.messageBundlePrefix + handlerErrorName;
    // 获取自定义异常携带的自定义提示信息并通过.defaultText(customerExceptionMsg)向下传递
    // 这里只处理自定义抛出的异常
    String customerExceptionMsg = "认证信息无效。";
    boolean b = false;
    if (handlerErrorName.equals("MyFailedLoginException")) {
      List<Throwable> list = e.getHandlerErrors().values().stream()
              .filter(O -> O.getClass().getSimpleName().equals("MyFailedLoginException"))
              .collect(Collectors.toList());
      MyFailedLoginException myFailedLoginException = (MyFailedLoginException) list.get(0);
      customerExceptionMsg = myFailedLoginException.getMessage();
    } else if (handlerErrorName.equals("MyAccountDisabledException")) {
      List<Throwable> list = e.getHandlerErrors().values().stream()
              .filter(O -> O.getClass().getSimpleName().equals("MyAccountDisabledException"))
              .collect(Collectors.toList());
      MyAccountDisabledException myAccountDisabledException = (MyAccountDisabledException) list.get(0);
      customerExceptionMsg = myAccountDisabledException.getMessage();
    } else if (handlerErrorName.equals("MyAccountLockedException")) {
      List<Throwable> list = e.getHandlerErrors().values().stream()
              .filter(O -> O.getClass().getSimpleName().equals("MyAccountLockedException"))
              .collect(Collectors.toList());
      MyAccountLockedException myAccountLockedException = (MyAccountLockedException) list.get(0);
      customerExceptionMsg = myAccountLockedException.getMessage();
    }
    // 如果抛出自定义登录异常时未配置message,getMessage()时customerExceptionMsg="No supported authentication handlers found for given credentials"
    // 这里置为空字符串,才能在界面展示国际化文件配置的固定提示信息
    if ("No supported authentication handlers found for given credentials".equals(customerExceptionMsg)) {
      customerExceptionMsg = "";
    }
    messageContext.addMessage((new MessageBuilder()).error().defaultText(customerExceptionMsg).code(messageCode).build());
    return handlerErrorName;
  }

  protected String handleAbstractTicketException(final AbstractTicketException e, final RequestContext requestContext) {
    MessageContext messageContext = requestContext.getMessageContext();
    Optional<String> match = this.errors.stream().filter((c) -> {
      return c.isInstance(e);
    }).map(Class::getSimpleName).findFirst();
    match.ifPresent((s) -> {
      messageContext.addMessage((new MessageBuilder()).error().code(e.getCode()).build());
    });
    return (String) match.orElse("UNKNOWN");
  }

  protected Event doExecute(final RequestContext requestContext) {
    Event currentEvent = requestContext.getCurrentEvent();
    LOGGER.debug("Located current event [{}]", currentEvent);
    Exception error = (Exception) currentEvent.getAttributes().get("error", Exception.class);
    if (error != null) {
      LOGGER.debug("Located error attribute [{}] with message [{}] from the current event", error.getClass(), error.getMessage());
      String event = this.handle(error, requestContext);
      LOGGER.debug("Final event id resolved from the error is [{}]", event);
      return (new EventFactorySupport()).event(this, event, currentEvent.getAttributes());
    } else {
      return (new EventFactorySupport()).event(this, "error");
    }
  }
}

2.复制改造粘贴源码DefaultMessageResolver.java

DefaultMessageResolver.java改造结果(仅修改了resolveMessage方法):


//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.binding.message;

import org.apache.commons.lang3.StringUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.core.style.ToStringCreator;

import java.util.Locale;
/**
 * 覆盖修改DefaultMessageResolver.java源码,解决自定义异常的msg在登录界面不展示提示信息(展示的是国际化配置文件配置的固定信息)
 */
public class DefaultMessageResolver implements MessageResolver, MessageSourceResolvable {
  private Object source;
  private String[] codes;
  private Severity severity;
  private Object[] args;
  private String defaultText;

  public DefaultMessageResolver(Object source, String[] codes, Severity severity, Object[] args, String defaultText) {
    this.source = source;
    this.codes = codes;
    this.severity = severity;
    this.args = args;
    this.defaultText = defaultText;
  }

  public Message resolveMessage(MessageSource messageSource, Locale locale) {
    if(StringUtils.isEmpty(this.defaultText)){
      // 从国家化文件取提示信息
      return new Message(this.source, this.postProcessMessageText(messageSource.getMessage(this, locale)), this.severity);
    } else {
      // 使用自己配置的msg作为提示信息
      return new Message(this.source, this.defaultText, this.severity);
    }
  }

  protected String postProcessMessageText(String text) {
    return text;
  }

  public String[] getCodes() {
    return this.codes;
  }

  public Object[] getArguments() {
    return this.args;
  }

  public String getDefaultMessage() {
    return this.defaultText;
  }

  public String toString() {
    return (new ToStringCreator(this)).append("source", this.source).append("severity", this.severity).append("codes", this.codes).append("args", this.args).append("defaultText", this.defaultText).toString();
  }
}

3.测试可以了,二者一致

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值