如何处理 UsernameNotFoundException 春季安全
Posted
技术标签:
【中文标题】如何处理 UsernameNotFoundException 春季安全【英文标题】:How to handle UsernameNotFoundException spring security 【发布时间】:2020-04-12 11:42:10 【问题描述】:UsernameNotFoundException
如何处理?
在 Spring Security 中,当找不到用户名时,UserDetailsService
实现会抛出 UsernameNotFoundException
。比如这样:
@Override
@Transactional
public UserDetails loadUserByUsername(java.lang.String username) throws UsernameNotFoundException
logger.info("Load user by username: ", username);
User user = userRepository.findUserByUsername(username).orElseThrow(
() -> new UsernameNotFoundException("User Not Found with -> username or email: " + username));
return UserPrinciple.build(user);
我想构建一个自定义的“未找到用户 REST 响应”。 我应该如何捕捉/处理这个异常?我已经在 WebSecurityConfigurerAdapter 实现处理程序中实现了处理程序方法:
private static void handleException(HttpServletRequest req, HttpServletResponse rsp, AuthenticationException e)
throws IOException
PrintWriter writer = rsp.getWriter();
writer.println(new ObjectMapper().writeValueAsString(new AuthResponse("", null, null, null, null,
"Authentication failed.", false)));
rsp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
但是这个方法应该等待 AuthenticationException
异常,并且在运行时异常的类型是 java.lang.NullPointerException
所以我无法转换或检索初始的 UsernameNotFoundException
。
任何建议将不胜感激。 很多很多的问候:)。
【问题讨论】:
看看here 【参考方案1】:安全层位于控制器和@ControllerAdvice
中的任何内容之前。
因此@ControllerAdvice
不是一个选项,因为UsernameNotFoundException
是AuthenticationException
的子类,在身份验证期间被抛出,使@ControllerAdvice
中的异常处理程序无法访问。
如果您将 UsernameNotFoundException
扔进控制器或从控制器引用的任何其他 bean,则只能使用 @ControllerAdvice
和 ResponseEntityExceptionHandler
。
这是我的建议 - 您实现 AuthenticationFailureHandler
并将其与您用于安全配置的 AuthenticationFilter
一起使用。
Spring boot security 附带了大约 4 个处理程序接口,用于解决与安全相关的问题
AccessDeniedHandler
- 处理用户没有所需角色等问题。
AuthenticationEntryPoint
- 这可以处理用户尝试访问没有适当身份验证元素的资源等问题。
AuthenticationFailureHandler
- 这会处理诸如找不到用户(即UsernameNotFoundException
)或身份验证提供程序内部引发的其他异常等问题。事实上,这处理了AccessDeniedException
和AuthenticationEntryPoint
没有处理的其他身份验证异常。
AuthenticationSuccessHandler
- 这有助于在用户成功通过身份验证后执行重定向等操作。
请参阅以下示例 sn-ps 了解所有 4 个接口的实现。请根据您的口味定制这些。
AccessDeniedHandler
实现
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized api access");
//httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out,response);
//mapper.writeValue(out, response);
out.flush();
AuthenticationEntryPoint
实施
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized access");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out, response);
out.flush();
AuthenticationFailureHandler
实现
package com.ibiller.webservices.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
@Component
public class RestAuthenticationFailureHandler implements AuthenticationFailureHandler
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse httpServletResponse,
AuthenticationException ex) throws IOException, ServletException
Map<String,Object> response = new HashMap<>();
response.put("status","34");
response.put("message","unauthorized access");
httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
OutputStream out = httpServletResponse.getOutputStream();
ObjectMapper mapper = new ObjectMapper();
mapper.writerWithDefaultPrettyPrinter().writeValue(out, response);
out.flush();
AuthenticationSuccessHandler
实现
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class RestSuccessHandler implements AuthenticationSuccessHandler
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException
Set<String> roles =
AuthorityUtils.authorityListToSet(authentication.getAuthorities());
if (roles.contains("ROLE_ADMIN"))
//do something
这是扩展 WebSecurityConfigurerAdapter
的安全配置,将所有内容连接在一起。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
new AntPathRequestMatcher("/v1/**"),new AntPathRequestMatcher("/admin/**")
);
AuthenticationProvider provider;
public SecurityConfiguration(final AuthenticationProvider authenticationProvider)
super();
this.provider=authenticationProvider;
@Override
protected void configure(final AuthenticationManagerBuilder auth)
auth.authenticationProvider(provider);
@Override
public void configure(final WebSecurity webSecurity)
webSecurity.ignoring().antMatchers("/info/**");//url that will be ignored
@Override
public void configure(HttpSecurity http) throws Exception
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler())
.authenticationEntryPoint(authenticationEntryPoint())
.and()
.authenticationProvider(provider)
.addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/v1/**").hasRole("API")
.antMatchers("/admin/**").hasAnyRole("SUPER_ADMIN","ADMIN")
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
@Bean
AuthenticationFilter authenticationFilter() throws Exception
final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
return filter;
@Bean
RestAccessDeniedHandler accessDeniedHandler()
return new RestAccessDeniedHandler();
@Bean
RestAuthenticationEntryPoint authenticationEntryPoint()
return new RestAuthenticationEntryPoint();
@Bean
RestAuthenticationFailureHandler authenticationFailureHandler()
return new RestAuthenticationFailureHandler();
@Bean
RestSuccessHandler successHandler()
return new RestSuccessHandler();
【讨论】:
这个可以更新吗?目前,您用于设置AuthenticationFilter
对象的构造函数在当前 Spring Security 版本中已过时。现在,新的构造函数是AuthenticationFilter(AuthenticationManager, AuthenticationConverter)
构造函数。 docs.spring.io/spring-security/site/docs/current/api/org/…【参考方案2】:
我不知道您项目的结构,但在这种情况下,通常的解决方案是使用@ControllerAdvice
机制(分离的类或在控制器中):
@ControllerAdvice
public class CustomExceptionHandler
@ExceptionHandler(value = UsernameNotFoundException.class)
public ResponseEntity handle(final UsernameNotFoundException exception)
...//set headers, response attributes and response body
【讨论】:
谢谢,会努力实现的。 :)【参考方案3】:在继承自 UsernamePasswordAuthenticationFilter
的类中,您必须重写方法 unsuccessfulAuthentication
它调用超类方法,但它所做的是重定向到另一个错误上下文,这使得授权过滤器激活。
相反,只需按照客户的期望填写请求信息(在我的情况下为 Json)
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse res, AuthenticationException failed) throws IOException, ServletException
res.addHeader("Access-Control-Allow-Origin", "*");
res.setStatus(HttpServletResponse.SC_OK);
ObjectMapper mapper = new ObjectMapper();
ObjectNode message = mapper.createObjectNode();
message.put("success", false);
message.put("message", "Invalid credentials");
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(message);
PrintWriter out = res.getWriter();
res.setContentType("application/json");
res.setCharacterEncoding("UTF-8");
out.print(json);
out.flush();
【讨论】:
以上是关于如何处理 UsernameNotFoundException 春季安全的主要内容,如果未能解决你的问题,请参考以下文章