更新时间:2023-12-03 20:52:40
首先,应在 ExceptionTranslationFilter ,此过滤器将处理 AccessDeniedException
和 AuthenticationException
捕获到它们。
http.addFilterAfter(siteMinderFilter(),ExceptionTranslationFilter.class);
接下来,您需要插入 AuthenticationEntryPoint
到处理 AuthenticationException
,已经有一些已经存在的 AuthenticationEntryPoint
您可以使用它,例如 HttpStatusEntryPoint
, LoginUrlAuthenticationEntryPoint
,...;
如果要返回JSON响应,则需要实现自己的 AuthenticationEntryPoint
,例如:
公共类ExampleAuthenticationEntryPoint实现AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(ExampleAuthenticationEntryPoint.class);
@Override
public void start(HttpServletRequest请求,HttpServletResponse响应,AuthenticationException authException)引发IOException,ServletException {
logger.error(authException.getMessage(),authException);
if(!response.isCommitted()){
Map< String,Object> responseMsg = new HashMap< String,Object>();
responseMsg.put( message,authException.getMessage());
ObjectMapper映射器= new ObjectMapper();
try {
response.setContentType( application / json; charset = utf-8);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.write(mapper.writeValueAsString(responseMsg));
writer.flush();
writer.close();
} catch(IOException e){
//忽略
logger.error(e.getMessage(),e);
}
}
}
}
I认为***返回http雕像代码 401
而不是 403
, 403
用于 AccessDeniedException
;
然后注入自己的 AuthenticationEntryPoint
到 ExceptionTranslationFilter
;
http.exceptionHandling() .authenticationEntryPoint(myEntryPoint());
@Bean
AuthenticationEntryPoint myEntryPoint(){
返回新的ExampleAuthenticationEntryPoint();
}
/ *
或仅返回401
AuthenticationEntryPoint myEntryPoint(){
返回新的HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
* /
BTW:
如果在 RequestHeaderAuthenticationFilter $中将
continueFilterChainOnUnsuccessfulAuthentication
设置为 true
c $ c>,该异常不会抛出超级过滤器,并且其默认值为 true
,也许您需要将其设置为false,否则需要注入 DelegateRequestMatchingFilter
中的 AuthenticationFailureHandler
在进行身份验证时处理异常。
**更新**
否则,如果(AccessDeniedException的instanceInstance除外){
身份验证= SecurityContextHolder.getContext( ).getAuthentication();
if(authenticationTrustResolver.isAnonymous(authentication)|| authenticationTrustResolver.isRememberMe(authentication)){
logger.debug(
访问被拒绝(用户为+(authenticationTrustResolver.isAnonymous(authentication)) ?匿名:未完全认证)+);重定向到认证入口点,
异常);
sendStartAuthentication(
请求,
响应,
链,
新InsufficientAuthenticationException(
需要完全认证才能访问此资源) );
}
else {
logger.debug(
访问被拒绝(用户不是匿名用户);委托给AccessDeniedHandler,
例外);
accessDeniedHandler.handle(请求,响应,
(AccessDeniedException)异常);
}
}
您可以从代码中读取,现在它是 AccessDeniedException
,我认为您可以禁用匿名用户,或者您需要检查为什么得到 AccessDeniedException
。
http.anonymous()。disable();
UPDATE 2
身份验证currentUser = SecurityContextHolder.getContext()
.getAuthentication();
if(currentUser == null){
返回true;
}
if(!checkForPrincipalChanges){
返回false;
}
if(!principalChanged(request,currentUser)){
返回false;
}
checkForPrincipalChanges的默认值
是 false
,因此现在如果启用 anon
过滤器,它将返回false并跳过 RequestHeaderAuthenticationFilter ,因此请尝试禁用 anon
过滤器。并且您需要学习spring安全代码并通过调试对其进行研究。
I have a question about the exceptions on the Spring security level.
I user the SM_USER header for authorization and for the validation the request goes through the DelegateRequestMatchingFilter (it helps to understand whether the SM_USER is needed).
The problem is that if there is no SM_USER-header at all the line super.doFilter(...)
throws a PreAuthenticatedCredentialsNotFoundException
which cannot be processed with the standard ExceptionResolver for controllers and that's why looks strange and differs from all the other exception thrown by the application.
I tried to write an own method marked with @ExceptionResolver
annotation for the filter, but it is ignored.
How can I insert an ExceptionsResolver for this Filter?
DelegateRequestMatchingFilter snippet
public class DelegateRequestMatchingFilter extends RequestHeaderAuthenticationFilter {
private RequestMatcher ignoredRequests;
public DelegateRequestMatchingFilter(RequestMatcher matcher) {
super();
super.setPrincipalRequestHeader("SM_USER");
this.ignoredRequests = matcher;
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpReq = (HttpServletRequest) req;
if(ignoredRequests.matches(httpReq)) {
chain.doFilter(req,resp);
} else {
super.doFilter(req,resp,chain);//<-- throws exception
}
}
}
Fragment from ExceptionResolver which does not solve the problem because it works only for controllers
@ControllerAdvice
public class ExceptionResolver extends AbstractHandlerExceptionResolver{
@Override
protected ModelAndView doResolveException(HttpServletRequest request,
HttpServletResponse responce, Object handler, Exception exception) {
ModelAndView toReturn = new ModelAndView();
toReturn.setView(new MappingJackson2JsonView());
toReturn.addObject("message", exception.getMessage());
toReturn.addObject("exceptionClass", exception.getClass().getCanonicalName());
return toReturn;
}
@ExceptionHandler(PreAuthenticatedCredentialsNotFoundException.class)
public ResponseEntity<Result> handleBindException(PreAuthenticatedCredentialsNotFoundException e) {
Result result = new Result("the identification header is missing");
result.setException(PreAuthenticatedCredentialsNotFoundException.class);
ResponseEntity<Result> response = new ResponseEntity<Result>(result, HttpStatus.FORBIDDEN);
return response;
}
}
SecurityConfiguration
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
private UserDetailsService userDetailsService;
private PreAuthenticatedAuthenticationProvider preAuthenticatedProvider;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
Map<String, List<GrantedAuthority>> rolesAuthorities = getRolesWithAutorities();
userDetailsService = new CustomUserDetailsService(...);
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper =
new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>(userDetailsService);
preAuthenticatedProvider = new PreAuthenticatedAuthenticationProvider();
preAuthenticatedProvider.setPreAuthenticatedUserDetailsService(wrapper);
auth.authenticationProvider(preAuthenticatedProvider);
}
@Bean
public SmUserFailureHandler smUserFailureHandler(){
return new SmUserFailureHandler();
}
@Bean
public AccessDeniedHandler accessDeniedHandler(){
AccessDeniedHandlerImpl handler = new AccessDeniedHandlerImpl();
handler.setErrorPage("/errorPage/");
return handler;
}
public RequestHeaderAuthenticationFilter siteMinderFilter() throws Exception
{
RequestHeaderAuthenticationFilter siteMinderFilter = new DelegateRequestMatchingFilter(
new OrRequestMatcher(
new AntPathRequestMatcher("/uriToSkip/")));
siteMinderFilter.setPrincipalRequestHeader("SM_USER");
siteMinderFilter.setAuthenticationManager(authenticationManager());
return siteMinderFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//...
http.addFilter(siteMinderFilter());
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
//adding methods...
http = registry.and();
http.csrf().disable();
http.headers().cacheControl().disable();
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.NEVER);
}
}
If I change my code according to the answer, I get this exception even SM_USEr header is present and is correct
org.springframework.security.authentication.InsufficientAuthenticationException: Full authentication is required to access this resource
at org.springframework.security.web.access.ExceptionTranslationFilter.handleSpringSecurityException(ExceptionTranslationFilter.java:177)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:133)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:168)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:185)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
at org.eclipse.jetty.server.Server.handle(Server.java:497)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
at java.lang.Thread.run(Thread.java:745)
At first, you should add your filter after ExceptionTranslationFilter
, this filter will handle the AccessDeniedException
and AuthenticationException
when it catches them.
http.addFilterAfter(siteMinderFilter(), ExceptionTranslationFilter.class);
and next you need inject a AuthenticationEntryPoint
to handle AuthenticationException
, there are some already exist AuthenticationEntryPoint
you can use it, like HttpStatusEntryPoint
, LoginUrlAuthenticationEntryPoint
, ...;
If you want to return a JSON response you need to implement your own AuthenticationEntryPoint
, for example:
public class ExampleAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger logger = LoggerFactory.getLogger(ExampleAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
logger.error(authException.getMessage(), authException);
if (!response.isCommitted()) {
Map<String, Object> responseMsg = new HashMap<String, Object>();
responseMsg.put("message", authException.getMessage());
ObjectMapper mapper = new ObjectMapper();
try {
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.write(mapper.writeValueAsString(responseMsg));
writer.flush();
writer.close();
} catch (IOException e) {
// ignore
logger.error(e.getMessage(), e);
}
}
}
}
I think it's better to return http statue code 401
instead of 403
, 403
is used for AccessDeniedException
;
and then inject your own AuthenticationEntryPoint
to ExceptionTranslationFilter
;
http.exceptionHandling().authenticationEntryPoint(myEntryPoint());
@Bean
AuthenticationEntryPoint myEntryPoint() {
return new ExampleAuthenticationEntryPoint();
}
/*
Or just return 401
AuthenticationEntryPoint myEntryPoint() {
return new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED);
}
*/
BTW:
If you set continueFilterChainOnUnsuccessfulAuthentication
to true
in RequestHeaderAuthenticationFilter
, the exception will not throw in super filter, and its default value is true
, maybe you need to set it to false, or you will need to inject a AuthenticationFailureHandler
in DelegateRequestMatchingFilter
to handle the exception when do authentication.
** UPDATE **
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
logger.debug(
"Access is denied (user is " + (authenticationTrustResolver.isAnonymous(authentication) ? "anonymous" : "not fully authenticated") + "); redirecting to authentication entry point",
exception);
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
"Full authentication is required to access this resource"));
}
else {
logger.debug(
"Access is denied (user is not anonymous); delegating to AccessDeniedHandler",
exception);
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
As you can read from the code, now it's AccessDeniedException
, I think you can disable the Anonymous user or you need to check why you get AccessDeniedException
.
http.anonymous().disable();
UPDATE 2
Authentication currentUser = SecurityContextHolder.getContext()
.getAuthentication();
if (currentUser == null) {
return true;
}
if (!checkForPrincipalChanges) {
return false;
}
if(!principalChanged(request, currentUser)) {
return false;
}
default value of checkForPrincipalChanges
is false
, so now if you enable the anon
filter, this will return false and skip do authentication in RequestHeaderAuthenticationFilter
, so just try do disable anon
filter. and you need to learn spring security code and research it by debugging.