在使用Spring Security的过程中,我们会发现框架内部按照错误及问题出现的场景,划分出了许许多多的异常,但是在业务调用时一般都会向外抛一个统一的异常出来,为什么要这样做呢,以及对于抛出来的异常,我们又该如何分场景进行差异化的处理呢,今天来跟我一起看看吧。

一个登陆场景下的外层代码

    @PostMapping("/login")
public void login(@NotBlank String username,
@NotBlank String password, HttpServletRequest request) {
try {
request.login(username, password);
System.out.println("login success");
} catch (ServletException authenticationFailed) {
System.out.println("a big exception authenticationFailed");
}
}

request.login(username,password)跳入到了HttpServlet3RequestFactory类中,点击去发现login方法只是统一向外抛出了一个ServletException异常。

        public void login(String username, String password) throws ServletException {
if (this.isAuthenticated()) {
throw new ServletException("Cannot perform login for '" + username + "' already authenticated as '" + this.getRemoteUser() + "'");
} else {
AuthenticationManager authManager = HttpServlet3RequestFactory.this.authenticationManager;
if (authManager == null) {
HttpServlet3RequestFactory.this.logger.debug("authenticationManager is null, so allowing original HttpServletRequest to handle login");
super.login(username, password);
} else {
Authentication authentication;
try {
authentication = authManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (AuthenticationException var6) {
SecurityContextHolder.clearContext();
throw new ServletException(var6.getMessage(), var6);
} SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}  

但是在ProviderManager类中的public Authentication authenticate(Authentication authentication) throws AuthenticationException {}

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
Iterator var6 = this.getProviders().iterator(); while(var6.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
if (provider.supports(toTest)) {
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
} try {
result = provider.authenticate(authentication);
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var11) {
this.prepareException(var11, authentication);
throw var11;
} catch (InternalAuthenticationServiceException var12) {
this.prepareException(var12, authentication);
throw var12;
} catch (AuthenticationException var13) {
lastException = var13;
}
}
} if (result == null && this.parent != null) {
try {
result = this.parent.authenticate(authentication);
} catch (ProviderNotFoundException var9) {
;
} catch (AuthenticationException var10) {
lastException = var10;
}
} if (result != null) {
if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
((CredentialsContainer)result).eraseCredentials();
} this.eventPublisher.publishAuthenticationSuccess(result);
return result;
} else {
if (lastException == null) {
lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
} this.prepareException((AuthenticationException)lastException, authentication);
throw lastException;
}
}

这里就涉及到了多态的知识点,异常的多态。如子异常AccountStatusException都可以向上转型为统一的验证异常AuthenticationException。

在设计之初的时候,验证类统一的父级异常是AuthenticationException。然后根据业务需求向下拓展出了很多个场景性质的异常,可能有十个、一百个、一千个。

但是这些具体的场景异常都是从AuthenticationException延伸出来的。

在这个验证登陆的方法中,会验证各种场景下登陆是否合法,就有可能出现很多的异常场景,诸如:
  • 密码不正确 BadCredentialsException
  • 账号是否被锁定 LockedException
  • 账号是否被禁用 DisabledException
  • 账号是否在有效期内 AccountExpiredException
  • 密码失效 CredentialsExpiredException
...几十个几百个异常,如果每个都需要事无巨细的抛出,那你需要在方法后面写几百个异常。
 
但是你会发现在验证方法那里统一抛出的是他们的统一父类AuthenticationException,这里用到的就是自动的向上转型。
到业务层我们拿到AuthenticationException后,需要进行对特定场景下的业务处理,如不同的异常错误返回提示不一样,这个时候就需要用到向下转型。

Throwable throwable = authenticationFailed.getRootCause();
if (throwable instanceof BadCredentialsException) {}
如果父类引用实际指的是凭证错误,则进行密码错误提示,这里又有一个骚操作,ServletException和AuthenticationException是两个框架下的顶级父级别的异常,两个怎么建立联系,直接将两个都统一转为Throwable可抛出的祖先异常,这样向下都可以转成他们自己了,以及各自场景下的所有异常了。

两个场景下的异常类关系图谱

ServletException

ServletException可以向上转型为Throwable

BadCredentialsException,密码错误

BadCredentialsException可以向上转型为Throwable

账号被禁用,DisabledException

DisabledException可以向上转型为Throwable 
怎么转过去的?
public void login(String username, String password) throws ServletException{
...
catch (AuthenticationException loginFailed) {
SecurityContextHolder.clearContext();
throw new ServletException(loginFailed.getMessage(), loginFailed);
}
} // 在捕获到异常之后会构建一个ServletException并将AuthenticationException统一的包装进去,比如说内部报了BadCredentialsException,那么在这里就会向上转型为Throwable
public ServletException(String message, Throwable rootCause) {
super(message, rootCause);
}
// 在Throwable类中会将最下面冒出来的异常传给cause,getRootCause就能获得异常的具体原因
public Throwable(String message, Throwable cause) {
fillInStackTrace();
detailMessage = message;
this.cause = cause;
} // Throwable向下转型BadCredentialsException
if (throwable instanceof BadCredentialsException)

调整后的代码

在外层根据不同异常而做不同的业务处理的代码就可以改造为如下

    @PostMapping("/login")
public void login(@NotBlank String username,
@NotBlank String password, HttpServletRequest request) {
try {
request.login(username, password);
System.out.println("login success");
} catch (ServletException authenticationFailed) {
Throwable throwable = authenticationFailed.getRootCause();
if (throwable instanceof BadCredentialsException) {
System.out.println("user password is wrong");
}else if (throwable instanceof DisabledException){
System.out.println("user is disabled");
}else {
System.out.println("please contact the staff");
}
}
}

Spring Security中异常上抛机制及对于转型处理的一些感悟的更多相关文章

  1. Spring Security 中的过滤器

    本文基于 spring-security-core-5.1.1 和 tomcat-embed-core-9.0.12. Spring Security 的本质是一个过滤器链(filter chain) ...

  2. [收藏]Spring Security中的ACL

    ACL即访问控制列表(Access Controller List),它是用来做细粒度权限控制所用的一种权限模型.对ACL最简单的描述就是两个业务员,每个人只能查看操作自己签的合同,而不能看到对方的合 ...

  3. Spring Security中实现微信网页授权

    微信公众号提供了微信支付.微信优惠券.微信H5红包.微信红包封面等等促销工具来帮助我们的应用拉新保活.但是这些福利要想正确地发放到用户的手里就必须拿到用户特定的(微信应用)微信标识openid甚至是用 ...

  4. java中异常的抛出:throw throws

    java中异常的抛出:throw throws Java中的异常抛出 语法: public class ExceptionTest{ public void 方法名(参数列表) throws 异常列表 ...

  5. Spring Security 中的 Bcrypt

    最近在写用户管理相关的微服务,其中比较重要的问题是如何保存用户的密码,加盐哈希是一种常见的做法.知乎上有个问题大家可以先读一下: 加盐密码保存的最通用方法是? 对于每个用户的密码,都应该使用独一无二的 ...

  6. 看源码,重新审视Spring Security中的角色(roles)是怎么回事

    在网上看见不少的博客.技术文章,发现大家对于Spring Security中的角色(roles)存在较大的误解,最大的误解就是没有搞清楚其中角色和权限的差别(好多人在学习Spring Security ...

  7. 六:Spring Security 中使用 JWT

    Spring Security 中使用 JWT 1.无状态登录 1.1 什么是有状态? 1.2 什么是无状态 1.3 如何实现无状态 2.JWT 2.1 JWT数据格式 2.2 JWT交互流程 2.3 ...

  8. 五:Spring Security 中的角色继承问题

    Spring Security 中的角色继承问题 以前的写法 现在的写法 源码分析 SpringSecurity 在角色继承上有两种不同的写法,在 Spring Boot2.0.8(对应 Spring ...

  9. Spring Security中html页面设置hasRole无效的问题

    Spring Security中html页面设置hasRole无效的问题 一.前言 学了几天的spring Security,偶然发现的hasRole和hasAnyAuthority的区别.当然,可能 ...

随机推荐

  1. sencha touch list(列表) item(单行)单击事件触发顺序

    测试代码如下 Ext.define('app.view.new.List', { alternateClassName: 'newList', extend: 'app.view.util.MyLis ...

  2. 使用 udev 管理 Linux 设备文件

    本文以通俗的方法阐述 udev 及相关术语的概念.udev 的配置文件和规则文件,然后以 Red Hat Enterprise Server 为平台演示一些管理设备文件和查询设备信息的实例.本文会使那 ...

  3. Windows Phone Bing lock screen doesn't change解决方法

    之前一直用的Lumia 925,Bing lock screen每天都会更换.这几天换了Lumia 930,同步了账号相关的设置,发现Bing lock screen不再每天更换.尝试重启.使用cel ...

  4. 23种设计模式之代理模式(Proxy)

    代理模式是一种对象结构型模式,可为某个对象提供一个代理,并由代理对象控制对原对象的引用.代理模式能够协调调用者和被调用者,能够在一定程度上降低系统的耦合度,其缺点是请求的处理速度会变慢,并且实现代理模 ...

  5. 【BZOJ1210】[HNOI2004]邮递员 插头DP+高精度

    [BZOJ1210][HNOI2004]邮递员 Description Smith在P市的邮政局工作,他每天的工作是从邮局出发,到自己所管辖的所有邮筒取信件,然后带回邮局.他所管辖的邮筒非常巧地排成了 ...

  6. 21ic编辑推荐:从单片机开始的程序运行

    一直不清楚单片机中程序的执行过程,就是知道一个程序总是从一个main函数开始执行,然后把程序段存放在ROM里面,动态数据存放在RAM里面,而单片机的RAM资源又是及其的稀少,所以要省着用,但是到底怎么 ...

  7. 7.24python协程(2)和IO模型

    2018-7-24 08:50:29 异步IO模型 epoll  机制  linux 给每个监听对象绑定回调函数,当要读的对象来了时候,回调函数直接被执行,然后通知用户,效率非常高! python无法 ...

  8. POJ 3067 - Japan - [归并排序/树状数组(BIT)求逆序对]

    Time Limit: 1000MS Memory Limit: 65536K Description Japan plans to welcome the ACM ICPC World Finals ...

  9. JavaScript学习12.1

    JavaScript弹窗可以创建3种消息框:警告框.确认框.提示框,可以不带window对象直接使用相应的方法警告框:保护用户可以得到某些信息,当出现警告框后需要用户点击确认按钮之后才能操作windo ...

  10. 通过微信服务号推送Zabbix告警

    近期看到一篇通过微信实现Zabbix告警的文章,但实践时发现,无法成功发送消息. 分析原因,应该是微信公众平台加强了登录验证,在登录时会需要管理员进行扫描二维码操作才能成功登陆后台: 而之前文章中的A ...