范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

SpringSecurity权限控制系列(三)

  环境:Springboot2.4.12 + Spring Security 5.4.9
  本篇主要内容:自定义异常处理
  上一篇:《Spring Security权限控制系列(二)》
  注意 :记得不要忘记关闭CSRF功能,由于之前的案例演示开启了CSRF,忘记关闭,导致在本篇案例中在登录时总是403状态码,点登录后通过调试发现请求的url是总是/error(我自定义登录页面并没有添加_csrf隐藏域字段)。 默认异常原理
  基于前面两篇的内容我们发现只要没有无权限访问接口,就会报错误,错误信息如下:
  登录成功后五权限访问接口时默认的返回错误信息
  错误的用户名或密码时
  接下来我们看看系统默认是如何提供该错误页面信息的错误的用户名密码
  当登录时填写的错误用户名或密码时,再次返回了登录页面,并且携带了错误信息。接下来通过源码查看这部分路径。
  当前配置:public class SecurityConfig extends WebSecurityConfigurerAdapter {  @Override  protected void configure(AuthenticationManagerBuilder auth) throws Exception {    auth.inMemoryAuthentication()      .passwordEncoder(NoOpPasswordEncoder.getInstance())      .withUser("guest").password("123456").roles("ADMIN")      .and()      .withUser("test").password("666666").roles("USERS") ;  }  @Override  protected void configure(HttpSecurity http) throws Exception {    http.csrf().disable() ;    http.authorizeRequests().antMatchers("/resources/**", "/cache/**").permitAll() ;    http.authorizeRequests().antMatchers("/demos/**").hasRole("USERS") ;    http.authorizeRequests().antMatchers("/api/**").hasRole("ADMIN") ;    http.formLogin().loginPage("/custom/login") ;  }}
  上面我们自定义了登录页面/custom/login ,所以我们的过滤器链中有个核心的过滤器 UsernamePasswordAuthenticationFilter  该过滤器专门用来处理POST提交的登录URI,我们这里自定义了所以该过滤器拦截的是/custom/login ,该过滤器在判断当前请求的时候会先判断是不是POST方式提交的,然后判断URI,所以我们在浏览器直接访问该uri的时候是不会发生任何认证逻辑处理的。
  登录认证的流程:UsernamePasswordAuthenticationFilter#attemptAuthentication ProviderManager#authenticate AuthenticationProvider#authenticate
  在第三步中首先判断的是用户名是否存在,如果不存在则会抛出BadCredentialsException  异常。public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider {  public Authentication authenticate(Authentication authentication) {    try {      user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);    } catch (UsernameNotFoundException ex) {      // 通过国际化资源获取key = AbstractUserDetailsAuthenticationProvider.badCredentials      // 的错误信息,如果没有自定义,则默认显示Bad credentials。      // 该异常信息抛到了ProviderManager中      throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));    }  }}
  父ProviderManager 处理异常// 这里需要注意,在默认的情况下,我们当前的认证出来逻辑已经是在父ProviderManager中进行处理了public class ProviderManager {  public Authentication authenticate(Authentication authentication) {    AuthenticationException lastException = null;    // ...    for (AuthenticationProvider provider : getProviders()) {      try {        result = provider.authenticate(authentication);      } catch (AuthenticationException ex) {        lastException = ex;      }    }    // ...    // 注意这里其实继续将异常抛给了子ProviderManager对象    throw lastException;  }}
  子ProviderManager 处理异常public class ProviderManager {  public Authentication authenticate(Authentication authentication) {    AuthenticationException lastException = null;AuthenticationException parentException = null;    // ...    if (result == null && this.parent != null) {      try {        parentResult = this.parent.authenticate(authentication);        result = parentResult;      } catch (AuthenticationException ex) {        // 进入该处        parentException = ex;        lastException = ex;      }    }    // ...    throw lastException;  }}
  过滤器UsernamePasswordAuthenticationFilter 接收到异常,该异常是有该过滤器的父类中进行处理。public abstract class AbstractAuthenticationProcessingFilter {  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {    doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);  }  private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {    try {      Authentication authenticationResult = attemptAuthentication(request, response);    } catch (AuthenticationException ex) {      unsuccessfulAuthentication(request, response, ex);    }  }  protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {    SecurityContextHolder.clearContext();    // ...    // 默认failureHandler = SimpleUrlAuthenticationFailureHandler    // 这里也就是我们自定义的一个功能点    this.failureHandler.onAuthenticationFailure(request, response, failed);  }}public class SimpleUrlAuthenticationFailureHandler {  public void onAuthenticationFailure(...) {    // 将异常保存到Session对象中    saveException(request, exception);    // 最后直接Redirect调整到登录页面    // defaultFailureUrl = /custom/login?error    this.redirectStrategy.sendRedirect(request, response, this.defaultFailureUrl);  }  protected final void saveException(HttpServletRequest request, AuthenticationException exception) {    HttpSession session = request.getSession(false);    if (session != null || this.allowSessionCreation) {      // AUTHENTICATION_EXCEPTION = SPRING_SECURITY_LAST_EXCEPTION      // 在页面中就可以通过Session获取异常的信息了      // 在上一篇的文章中自定义登录页面中就有从该session中获取异常信息      request.getSession().setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, exception);    }  }}
  以上就是Spring Security在处理登录失败的情景下如何进行处理的,同时我们也知道了为 UsernamePasswordAuthenticationFilter (父类) 配置AuthenticationFailureHandler 是一个自定义的扩展点,我们可以在自定义的SecurityConfig 中配置该失败句柄。无权限的异常
  在上面的自定义配置中我们配置了两个用户guest ADMIN test USERS
  /demos/** 一类的请求必须拥有 USERS  权限(角色)
  /api/** 一类的请求必须拥有 ADMIN  权限(角色)
  接下来通过guest用户登录后,访问/demos/home接口查看默认的错误显示
  该授权检查的流程:FilterSecurityInterceptor#invoke AbstractSecurityInterceptor#beforeInvocation AbstractSecurityInterceptor#attemptAuthorization
  在上面的流程中主要核心方法是attemptAuthorization尝试授权操作。public abstract class AbstractSecurityInterceptor {  protected InterceptorStatusToken beforeInvocation(Object object) {    // ...    attemptAuthorization(object, attributes, authenticated);    // ...  }  private void attemptAuthorization(...) {    try {      // accessDecisionManager = AffirmativeBased      this.accessDecisionManager.decide(authenticated, object, attributes);    } catch (AccessDeniedException ex) {      // ...      // 异常抛给了子类处理      throw ex;    }  }}public class AffirmativeBased extends AbstractAccessDecisionManager {  // 该方法开始判断当前登录的用户信息是否具有相应的权限信息  public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException {    int deny = 0;    for (AccessDecisionVoter voter : getDecisionVoters()) {      int result = voter.vote(authentication, object, configAttributes);      switch (result) {        case AccessDecisionVoter.ACCESS_GRANTED:          return;        case AccessDecisionVoter.ACCESS_DENIED:          deny++;          break;        default:          break;      }    }    // 当拒绝次数 > 0 那么将会抛出AccessDeniedException异常    // 默认的异常信息会先从国际化资源中获取key = AbstractAccessDecisionManager.accessDenied    // 如果没有配置,则默认信息:Access is denied    if (deny > 0) {      throw new AccessDeniedException(this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));    }  }}
  最终异常AccessDeniedException 并没在FilterSecurityInterceptor 中进行处理,那么该异常就会被过滤器链中的ExceptionTranslationFilter 中得到处理public class ExceptionTranslationFilter extends GenericFilterBean {  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {    doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);  }  private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {    try {      chain.doFilter(request, response);    } catch (Exception ex) {      Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);      RuntimeException securityException = (AuthenticationException) this.throwableAnalyzer.getFirstThrowableOfType(AuthenticationException.class, causeChain);      if (securityException == null) {        securityException = (AccessDeniedException) this.throwableAnalyzer.getFirstThrowableOfType(AccessDeniedException.class, causeChain);      }      // 处理异常      handleSpringSecurityException(request, response, chain, securityException);    }  }  private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response,FilterChain chain, RuntimeException exception) throws IOException, ServletException {    if (exception instanceof AuthenticationException) {      handleAuthenticationException(request, response, chain, (AuthenticationException) exception);    } else if (exception instanceof AccessDeniedException) {      // 处理被拒绝的异常      handleAccessDeniedException(request, response, chain, (AccessDeniedException) exception);    }  }  private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {    // ...    // accessDeniedHandler = AccessDeniedHandlerImpl    // 访问拒绝句柄的默认实现    // 这里也就成为了我们的一个自定义处理点    this.accessDeniedHandler.handle(request, response, exception);  }}public class AccessDeniedHandlerImpl implements AccessDeniedHandler {  public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {    if (this.errorPage == null) {      // 默认这里的errorPage = null ,所以执行这里的逻辑      // 这设置响应状态码403      response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());      return;    }    // Put exception into request scope (perhaps of use to a view)    request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);    // Set the 403 status code.    response.setStatus(HttpStatus.FORBIDDEN.value());    request.getRequestDispatcher(this.errorPage).forward(request, response);  }}
  到此你应该了解到了,当我们没有权限访问资源时默认是如何处理的,同时也了解到了如何进行自定义异常处理句柄。自定义异常配置
  上面介绍了错误产生的原理及了解到了自定义异常处理句柄的方法,接下来通过自定义的方式展示错误信息。错误的用户名密码  protected void configure(HttpSecurity http) throws Exception {  http.csrf().disable() ;  http.authorizeRequests().antMatchers("/resources/**", "/cache/**").permitAll() ;  http.authorizeRequests().antMatchers("/demos/**").hasRole("USERS") ;  http.authorizeRequests().antMatchers("/api/**").hasRole("ADMIN") ;  http    .formLogin()    .failureHandler(new AuthenticationFailureHandler() {      @Override      public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {        response.setContentType("application/json;charset=UTF-8") ;        PrintWriter out = response.getWriter() ;        out.println("{"code": -1, "message": "" + exception.getMessage() + ""}") ;        out.close();      }    })    .loginPage("/custom/login") ;}
  我们也可以将上面的AuthenticationFailureHandler  定义为一个Bean对象这样方便我们做其它的一些操作。
  登录测试
  无权限的异常
  上面介绍了当没有权限访问指定的资源时错误产生的原理及了解到了自定义拒绝访问句柄的方法,接下来通过自定义的方式展示错误信息。
  1 自定义访问拒绝页面的方式
  在如下位置新建denied.html页面
  // 自定义Controller@Controllerpublic class ErrorController {  @GetMapping("/access/denied")  public String denied() {    return "denied" ;  }}// 自定义配置protected void configure(HttpSecurity http) throws Exception {  http.csrf().disable() ;  http.authorizeRequests().antMatchers("/resources/**", "/cache/**").permitAll() ;  http.authorizeRequests().antMatchers("/demos/**").hasRole("USERS") ;  http.authorizeRequests().antMatchers("/api/**").hasRole("ADMIN") ;  http    .formLogin()    .failureHandler(new AuthenticationFailureHandler() {      @Override      public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {        response.setContentType("application/json;charset=UTF-8") ;        PrintWriter out = response.getWriter() ;        out.println("{"code": -1, "message": "" + exception.getMessage() + ""}") ;        out.close();      }    })    .loginPage("/custom/login") ;  // 自定义访问拒绝页面  http.exceptionHandling().accessDeniedPage("/access/denied") ;}
  简单的页面内容

Access Denied

  测试   2 自定义403错误页面   将上面的http.exceptionHandling().accessDeniedPage("/access/denied") 代码注释了   然后在下面位置新建403.html页面   简单的页面内容

Denied Access This is page

  测试   3 自定义访问拒绝句柄的方式 protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() ; http.authorizeRequests().antMatchers("/resources/**", "/cache/**").permitAll() ; http.authorizeRequests().antMatchers("/demos/**").hasRole("USERS") ; http.authorizeRequests().antMatchers("/api/**").hasRole("ADMIN") ; http .formLogin() .failureHandler(new AuthenticationFailureHandler() { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8") ; PrintWriter out = response.getWriter() ; out.println("{"code": -1, "message": "" + exception.getMessage() + ""}") ; out.close(); } }) .loginPage("/custom/login") ; // 自定义访问拒绝页面 // http.exceptionHandling().accessDeniedPage("/access/denied") ; http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandler() { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { response.setContentType("application/json;charset=UTF-8") ; PrintWriter out = response.getWriter() ; out.println("{"code": -1, "message": "" + accessDeniedException.getMessage() + ""}") ; out.close(); } }) ;}   测试   总结: 认证失败后的处理原理及自定义配置授权失败后的处理原理及自定义配置   到此本篇内容结束。下一篇将介绍:核心过滤器创建原理 自定义过滤器   SpringBoot项目中Redis之管道技术   SpringCloud Hystrix实现资源隔离应用   springboot mybatis jpa 实现读写分离   Springboot中Redis事务的使用及Lua脚本   Spring Cloud 中断路器 Circuit Breaker的应用   SpringBoot开发自己的Starter   Springboot整合RabbitMQ死信队列详解   SpringBoot RabbitMQ消息可靠发送与接收   SpringMVC核心组件HandlerMapping你清楚了吗?   Spring Cloud 微服务日志收集管理Elastic Stack完整详细版   Spring Cloud链路追踪zipkin及整合Elasticsearch存储   SpringBoot一个提升N倍性能的操作   Springboot中接口参数校验N种方法你会几个?

李一桐情人节大片马上就是2。14情人节了一起来看看孟钰带来的惊喜吧!图中的李一桐和毕雯珺,帅哥美女演绎的爱情,是在黑与白之间的拉扯,这表现力,谁看了不说绝!这场爱情的博弈中,从来就没有输赢,这只不再当妈!蕾哈娜带球登超级碗舞台证实怀二胎(19)1905电影网讯天后蕾哈娜跨足美妆界多年,6年来没出新专5年没公开演出的她,在歌迷的期盼下终于重拾歌手身份,在2023年超级碗中场秀回归,带来了13分钟的劲歌热舞秀,燃爆全张馨予疑与何捷一起过情人节,因文案被猜怀二胎,还撞脸赵丽颖2月14日情人节,不少明星纷纷晒出夫妻合影狂撒狗粮,偌大的娱乐圈也笼罩在幸福的氛围中,今天,身为军嫂张馨予也晒出视频,虽然老公何捷没有出镜,不过可以看到IP定位是广东,视频中还出现来自3000年前!青藏高原有新发现研究人员发现3000年前青藏高原奶制品消费证据第二次青藏科考队人类活动历史及其影响分队日前在我国综合类学术期刊ScienceBulletin(科学通报)上发表联合署名文章称,科考队大数据已死!从业10年老哥爆文抨击这套唬不住客户了詹士发自凹非寺量子位公众号QbitAI大数据已死。说这话的,正是来自Google十年员工,数据分析产品BigQuery创始成员之一,JordanTigani。在最新发布的一篇博文中首次!湖南学者现代人出东亚理论写进国内教科书近日,中南大学遗传学教授黄石的遗传多样性上限理论被分别写进国外国内两本教科书一本是美国教授DavidBickel所著英文书系统发育树与分子进化另一本是国内朱作斌教授等学者们合著的中颜值性能双升级,125W强势蓄能,motoX30Pro手机评测前言摩托罗拉从开始就带着最顶尖的配置更亲民的价格,想给用户带来最好的体验无论是首发骁龙888,还是全球首发2亿像素主摄,或者是独家定制的高定人像,都将影像提升至更高的地位。motoPolygonNetwork(MATIC)的zkEVM新区块链指南中你应该知道的一切这是迄今为止我们队Polygon(MATIC)最新的以太坊(ETH)扩展解决方案的了解以及为什么它对Web3至关重要2022年7月20日,以太坊扩展解决方案生态系统Polygon(2023年了,给大家介绍一下现在前沿科学技术都有啥人工智能人工智能(AI)是模拟人类智能的科技。通过机器学习深度学习自然语言处理等技术,使计算机能够像人类一样理解学习决策和创新。人工智能应用广泛,如智能机器人智能家居自动驾驶智能医车主弃车而逃,新能源车叫来警察司机酒后驾车撞上了路边桥墩为逃避法律制裁弃车而逃想着第二天再来处理这样就可以神不知鬼不觉然而智慧新能源车自动发出了警报2月4日凌晨某品牌新能源车自带的紧急求助系统向客服中心发出警报擦亮万物互联之眼南阳市传感器产业链创新发展采访札记夜幕低垂,一盏盏智能灯随着路人的到来而点亮,随着路人的离开渐次熄灭步入监控区,摄像头紧跟人体移动而旋转,视线一刻不离开启自动泊车功能,车辆可以自己旋转方向盘,寻找车位停放你可知道,
女排张常宁公布婚期,归队遥遥无期情人节的这天,女排队员张常宁在社交软件上公布了自己的婚期,球迷在恭喜与祝福之际,也有一丝遗憾!!在全运会结束后张常宁做了膝盖手术,目前仍在修养恢复中。女排新一期的集训将于16日开始女版奥尼尔!3场狂轰50分,带队3胜0负登顶第一!真防不住她啊在FIBA赛场上,篮球,始终是属于巨人的运动。只有你拥有碾压式的体型,那就可以在禁区轻松称王。比如有着女版奥尼尔之称,被球迷亲切地喊作妹妹的李月汝。昨天的贝尔格莱德世界杯预选赛上,实锤打脸!冬奥会伙食差?日本队员气恼谁在偷偷打包?提出投诉冬奥会目前进入赛程的下半段,运动健儿们为我们贡献了十分精彩的表现,在茶余饭后工作之余,奥运会的比赛也吸引着我们的目光。00后小将谷爱凌在大跳台夺冠后,拿下自由式滑雪女子坡面障碍技巧空中技巧男子资格赛,齐广璞贾宗洋晋级决赛,王心迪孙佳旭出局北京时间2月15日,北京冬奥会自由式滑雪空中技巧比赛结束男子资格赛争夺,中国选手齐广璞贾宗洋晋级决赛,而王心迪和孙佳旭遗憾出局。作为中国雪上项目的优势项目,中国空中技巧是绝对的实力哈登走了,篮网大胜,是小库里很香?还是大哈登太差?小库里俗话说,盛极必衰,否极泰来。一场无关紧要的常规赛,尤其是在篮网和国王两个队都参加了大交易,人员状况发生了极大变化的情境下,什么事情发生都是有可能的。何况,一波的11连败,是个场馆七千座椅被盗,奥运村成荒废鬼城,北京冬奥会竟如此环保北京决心使本届奥运会成为迄今为止最环保的奥运会之一。自里约奥运会惨败以来,这种思路就定义了奥运会,主办城市和国际奥委会(IOC)因预算过高和浪费观念而受到强烈批评。里约奥运村场馆变张艺谋,没有安全感的日子张艺谋就要72岁了。这些年,他在大风大浪里忽上忽下,有时是让人敬仰的国师,偶尔是江郎才尽的导演,除此之外,大多数时候,他更像是一个始终在转动的陀螺只要有一根叫做目标的鞭子,他就能够宁德时代名誉董事长逝世赌性坚强,一手带出曾毓群,自称又懒又想赢本文来源时代周报作者何明俊2月15日,宁德时代官网发布讣告。宁德时代荣誉董事长张毓捷博士因病医治无效,于2022年2月14日逝世,享年79岁。张毓捷出生于1943年,祖籍山东省泰安实探儋州海花岛,十年千亿地产投资改变了什么?丨楼市地理春节期间,位于海南省儋州市的中国海南海花岛(以下简称海花岛)车水马龙,人声鼎沸。在主体功能为游乐的一号岛外,马路两旁停满了来自全国各地游客的汽车。但马路旁约30米外的世界级销售中心中级和副高的工资差距有多大?我正好就在事业单位工作,所以我对中级和副高的工资待遇比较了解,下面我就详细谈谈我身边中级职称的同事和副高的工资待遇差距有多大。我身边中级职称同事和副高同事的工资差距一每个月的打卡工从发微信,可以看清一个人微信不只是一种工具,更是一种联系你我的桥梁。很多时候,我们可以通过聊天方式,看清一个人和你的关系。01这样发微信的人,就是瞧不起你!有一种人,一直不回你的信息,并不是他太忙,也不是