spring-boot - 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";
}
解决方案
我认为你的问题在这里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")
));
推荐阅读
- android - 未找到首选项中的 setOnBindEditTextListenerEditText
- git - 掌舵图的分支策略
- javascript - 为什么 jQuery .click() 仅在使用 Rails 销毁 link_to 的第二次点击时有效?
- java - Java:如何解析 doc 文件(word 97-2003)?
- javascript - JS 程序将给定的字符串在指定的方向上旋转指定的大小
- vba - 按“类型”或“邮件类别”删除 Outlook 中的项目
- azure-data-catalog - 如何使用 REST API 将 columnDescription 添加到 Azure 数据目录表
- css - 有没有办法隔离 SVG 样式?
- mysql - 用于检查日历可用性的 SQL 查询?
- android - 来自 Firebase 的应用(Android 或 iOS)中的提醒通知