首页 > 解决方案 > Spring Security - 获取登录页面时,安全性尝试进行身份验证并返回 401 错误

问题描述

我正在开发具有微服务架构的 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;
        }

我认为在这一点上,我必须覆盖过滤器。但我不知道我如何编写过滤器。

这是我的身份验证代码。

身份验证项目-> 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";
    }

标签: spring-bootspring-security

解决方案


我认为你的问题在这里JwtUsernameAndPasswordAuthenticationFilter

您也已将这一点注释掉。您在 和 上触发此过滤POSTGET。您只想为 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")
            ));

推荐阅读