Spring Security - 获取登录页面时,安全性尝试进行身份验证并返回 401 错误
Posted
技术标签:
【中文标题】Spring Security - 获取登录页面时,安全性尝试进行身份验证并返回 401 错误【英文标题】:Spring Security - when get login page, security try to authenticate and return 401 error 【发布时间】:2020-10-11 16:48:04 【问题描述】:我正在开发具有微服务架构的 Spring Boot 应用程序。我正在使用 JWT 身份验证。
1-http://localhost:8762/auth "username":"admin", "password":"12345"(POST 请求)
2-http://localhost:8762/auth/loginPage(获取页面请求)
当我尝试第一个请求时,身份验证运行良好,我得到登录信息和 jwt 令牌。 但是当我尝试获取登录页面的第二个请求时,spring 正在尝试进行身份验证并返回 401 错误。
如何忽略登录页面的身份验证。
我有 zull 项目作为网关,身份验证项目作为 auth。
if(header == null || !header.startsWith(jwtConfig.getPrefix()))
chain.doFilter(request, response); // If not valid, go to the next filter.
return;
我认为在这一点上,我必须覆盖过滤器。但是我不知道我是怎么写过滤器的。
这是我的身份验证代码。
auth 项目 -> WebSecurityConfigurerAdapter
@EnableWebSecurity
public class SecurityCredentialsConfig extends WebSecurityConfigurerAdapter
@Autowired
private JwtConfig jwtConfig;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception
http
.csrf().disable()
// make sure we use stateless session; session won't be used to store user's state.
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
// handle an authorized attempts
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
// Add a filter to validate user credentials and add token in the response header
// What's the authenticationManager()?
// An object provided by WebSecurityConfigurerAdapter, used to authenticate the user passing user's credentials
// The filter needs this auth manager to authenticate the user.
.addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig()))
.authorizeRequests()
// allow all POST requests
.antMatchers("/auth/**").permitAll()
.antMatchers("/user/register").permitAll()
// any other requests must be authenticated
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/loginPage");
// Spring has UserDetailsService interface, which can be overriden to provide our implementation for fetching user from database (or any other source).
// The UserDetailsService object is used by the auth manager to load the user from database.
// In addition, we need to define the password encoder also. So, auth manager can compare and verify passwords.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
@Bean
public JwtConfig jwtConfig()
return new JwtConfig();
auth -> 用户名密码验证过滤器
public class JwtUsernameAndPasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter
private AuthenticationManager authManager;
private final JwtConfig jwtConfig;
public JwtUsernameAndPasswordAuthenticationFilter(AuthenticationManager authManager, JwtConfig jwtConfig)
this.authManager = authManager;
this.jwtConfig = jwtConfig;
// By default, UsernamePasswordAuthenticationFilter listens to "/login" path.
// In our case, we use "/auth". So, we need to override the defaults.
//this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(jwtConfig.getUri(), "POST"));
this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/auth/**")
, new AntPathRequestMatcher("/user/register")
));
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException
try
// 1. Get credentials from request
UserDTO creds = new ObjectMapper().readValue(request.getInputStream(), UserDTO.class);
// 2. Create auth object (contains credentials) which will be used by auth manager
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
creds.getUsername(), creds.getPassword(), Collections.emptyList());
// 3. Authentication manager authenticate the user, and use UserDetialsServiceImpl::loadUserByUsername() method to load the user.
return authManager.authenticate(authToken);
catch (IOException e)
throw new RuntimeException(e);
// Upon successful authentication, generate a token.
// The 'auth' passed to successfulAuthentication() is the current authenticated user.
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication auth) throws IOException, ServletException
Long now = System.currentTimeMillis();
String token = Jwts.builder()
.setSubject(auth.getName())
// Convert to list of strings.
// This is important because it affects the way we get them back in the Gateway.
.claim("authorities", auth.getAuthorities().stream()
.map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
.setIssuedAt(new Date(now))
.setExpiration(new Date(now + jwtConfig.getExpiration() * 1000)) // in milliseconds
.signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret().getBytes())
.compact();
// Add token to header
response.addHeader(jwtConfig.getHeader(), jwtConfig.getPrefix() + token);
控制器
@GetMapping("/auth/loginPage")
public String loginPage()
return "login";
【问题讨论】:
【参考方案1】:通过这样做:
this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/auth/**")
, new AntPathRequestMatcher("/user/register")
));
过滤器将对/auth/**
的任何请求进行身份验证(因此是 /auth/loginPage),并且由于您将身份验证入口点设置为仅返回 401 状态,因此您将遇到该问题。
评论一下:
.and()
// handle an authorized attempts
.exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED))
它应该将您重定向到登录页面。
PS:根据您的配置,如果我未通过身份验证并尝试访问/auth/loginPage
,我将被重定向到/auth/LoginPage,
,一旦我输入凭据,我将成功通过身份验证并再次重定向到同一页面/auth/loginPage
【讨论】:
我评论了这些行并收到此错误:错误:超过 maxRedirects。可能陷入重定向循环desktop-1cm5oaa.home:8763/auth/loginPage。 @SemihKoyu 我认为这是不必要的: this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher( new AntPathRequestMatcher("/auth/**") , new AntPathRequestMatcher("/user/register") ));因为您已经将其设置为 jwtConfig.getUir()【参考方案2】:如何忽略登录页面的身份验证。
OncePerRequestFilter
有一个方法 shouldNotFilter
可以覆盖。
例如:
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException
return new AntPathMatcher().match("/auth/loginPage", request.getServletPath());
【讨论】:
【参考方案3】:我认为你的问题在JwtUsernameAndPasswordAuthenticationFilter
您也已将这一点注释掉。您正在POST
和GET
上触发此过滤器。您只想为 POST 触发它。
当前方法
this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/auth/**")
, new AntPathRequestMatcher("/user/register")
));
更新
this.setRequiresAuthenticationRequestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/auth/**", "POST")
, new AntPathRequestMatcher("/user/register", "POST")
));
【讨论】:
以上是关于Spring Security - 获取登录页面时,安全性尝试进行身份验证并返回 401 错误的主要内容,如果未能解决你的问题,请参考以下文章
Spring Security:如何获取自动渲染的登录页面代码?
Keycloak 登录页面未显示,我改为获取 Spring Security 登录
如何在 Spring Security 中通过 jdbc 身份验证使用自定义登录页面
使用 Spring Security 时移动和桌面的不同登录页面