在使用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. Sublime text3配置LiveReload 浏览器即时刷新

    1.在sublime控件台 install livereload插件(缺点:每次重新打开Sublime都需要启动) 2.配置Preference > Package Settings > ...

  2. 算法题目-记hulu失败的实习面试

    1.对于数组A[0,1,2,3,4,...,k],求得0<=i < j < k,且使得A[j] - A[i]为最大值. 最简单也最容易想到的搜索两遍,即可得到答案.i的位置从起始至倒 ...

  3. Egret 中实现3种状态切换按钮

    一.游戏中的常用3种状态按钮 Egret种提供了2种状态切换的按钮ToggleButton. 但是在游戏中常用到3种状态的按钮,比如任务系统的领取.已领取.未领取. 比如下图中宝箱的打开.浏览后打开. ...

  4. 【SPOJ419】Transposing is Fun Pólya定理+欧拉函数

    [SPOJ419]Transposing is Fun 题意:给你一个$2^a\times2^b$的矩阵,将$1...n$中的数依次从左到右,从上往下填到矩阵里,再把矩阵转置,然后把所有数从左到右,从 ...

  5. Thinkphp框架下对某个字段查询数据的时候进行唯一过滤,返回唯一不同的值

    方法一. DISTINCT 方法用于返回唯一不同的值 . *distinct方法的参数是一个布尔值. 用法: $data = $Model->Distinct(true)->field(' ...

  6. iOS - 国际化语言切换

    iOS国际化:如何切换语言   1.国际化就是将标签.提示信息等信息放到资源文件中,随着程序需要的语言提供对应的资源文件.以key/value对存储,每个资源的key值不变,value随着需求改变. ...

  7. thinkCMF----使用自定义函数

    thinkCMF使用自定义函数:app 下新建 common.php

  8. 关于Thinkphp访问不正常的问题

    最近遇见了个蹩脚的问题,我放在服务器的项目(thinkphp框架)只能访问默认路径内容,不管你url怎么写的,他就访问默认那个文件.. 对于有强迫症的我来说实在是欺人太甚!!! 于是乎我就抓耳挠腮了. ...

  9. Saltstack之api

    Salt-API 1,htps证书 2,配置文件 3,验证.使用PAM验证 4,启动salt-api 安装salt-api yum -y install salt-api 创建用户 useradd - ...

  10. Saltstack之SSH

    salt-minion也可以不安装通过在master安装salt-ssh 1,安装 yum -y install salt-ssh 2,配置salt的花名册 vim /etc/salt/roster ...