SpringSecurity5 异常

回到序章

来来来,点这

认证异常( 401 )

都是 AuthenticationException 的子类。错误码: 401

  • UsernameNotFoundException( 用户不存在 )
  • BadCredentialsException( 坏的凭据 )
  • CerdentialsExpiredException( 证书过期 )
  • DisableException( 账号冻结 )
  • LockedException( 账号锁定 )
  • 等等…

实现 - 源码分析

先出总结,主要的就是依次进入了下面四个方法:

  1. AbstractAuthenticationProcessingFilter.doFilter()
  2. AbstractAuthenticationProcessingFilter.unsuccessfulAuthentication()
  3. SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure()
  4. SimpleUrlAuthenticationFailureHandler.saveException()

过滤器中捕捉异常

AbstractAuthenticationProcessingFilter-doFilter

打印,然后转交给 onAuthencicationFailure() 处理

AbstractAuthenticationProcessingFilter-unsuccessfulAuthentication

错误返回

SimpleUrlAuthenticationFailureHandler-onAuthenticationFailure

保存异常的代码

SimpleUrlAuthenticationFailureHandler-saveException

spring-security5 提供的异常的配置

自定义登陆、登出等接口 这一篇里有个 .failureUrl("/login/fail"),就是指定异常后要去的url。

没有权限引起的异常( 403 )

这个的话,就是这个了,AccessDeniedException。错误码: 403

这个打个比方,你配置 a 可以请求 /test1/** 的接口,那么 a 请求 /test2/** 的接口就会报这个异常。

可以通过这段代码自制响应( 返回 json 啊啥的 ):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return (request, response, accessDeniedException) -> {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("非法请求");
response.setStatus(403);
};
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/test1/**").anonymous()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler());
}

另外,因为 spring-security5 没有登陆的用户也是拥有权限的( 匿名用户 ),所以没登陆去请求接口的时候,会返回 403,而不是 401。上面的 a 也指的匿名用户。

把配置改成如下,则可以实现匿名用户返回 401,而没权限返回 403( 当然也可以配置跳转到某个登陆页面请求上 )。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return (request, response, accessDeniedException) -> {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("非法请求");
response.setStatus(403);
};
}

@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, authException) -> {
response.setContentType("text/html;charset=utf-8");
response.getWriter().print("请登录");
response.setStatus(401);
};
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/test1/**").anonymous()
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint())
.accessDeniedHandler(accessDeniedHandler());
}

异常消息本地化( 国际化 )

官方文档地址

新起一个类,增加本地化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;

@Configuration
public class SecurityLocalizationConfiguration {

@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.addBasenames("classpath:org/springframework/security/messages");
return messageSource;
}
}

必须要新起一个配置类,如果放在继承了 WebSecurityConfigurerAdapter 的配置类中,会报以下的错误:

相关的 Github issues

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'resourceHandlerMapping' defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658)
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:638)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1334)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1177)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:524)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:944)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:917)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:582)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:144)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:767)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:426)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:326)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1311)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300)
at com.hades.api.ApiApplication.main(ApiApplication.java:27)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.web.servlet.HandlerMapping]: Factory method 'resourceHandlerMapping' threw exception; nested exception is java.lang.IllegalStateException: No ServletContext set
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
... 20 common frames omitted
Caused by: java.lang.IllegalStateException: No ServletContext set
at org.springframework.util.Assert.state(Assert.java:76)
at org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.resourceHandlerMapping(WebMvcConfigurationSupport.java:587)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
... 21 common frames omitted