仅在需要身份验证时应用弹簧安全过滤器(由我的应用程序)
Posted
技术标签:
【中文标题】仅在需要身份验证时应用弹簧安全过滤器(由我的应用程序)【英文标题】:Applying a spring security filter only if authentication is required (by my application) 【发布时间】:2020-06-07 01:38:10 【问题描述】:我已阅读How to apply spring security filter only on secured endpoints?,这似乎与我的问题最接近,但没有充分回答。
您将在下面看到我当前正在使用的 WebSecurityConfigurerAdapter 配置。它不会一直这样,因为我以后不会公开 h2-console。
我的问题是,JwtAuthenticationFilter
总是被执行。我宁愿希望过滤器在需要身份验证的请求上执行(在我的特定情况下:仅此处描述的内容:
.authorizeRequests()
.anyRequest()
.authenticated()
)。
如何做到这一点?
P.s.:我的应用程序登录按预期工作,而 H2-console 也可以,但一直抛出 io.jsonwebtoken.SignatureException
,因为 JWT H2-console 生成和使用的自然与我的应用程序使用的不同。
WebSecurityConfigurerAdapter:
package com.particles.authservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.particles.authservice.jwt.JwtAuthenticationEntryPoint;
import com.particles.authservice.jwt.JwtAuthenticationFilter;
import com.particles.authservice.service.UserService;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
@Autowired
private UserService userService;
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Override
public void configure(final AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception
authenticationManagerBuilder
.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
@Override
protected void configure(final HttpSecurity http) throws Exception
//@formatter:off
http
.cors()
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers(HttpMethod.POST, "/register")
.permitAll()
.antMatchers(HttpMethod.GET, "/confirm")
.permitAll()
.antMatchers(HttpMethod.POST, "/login")
.permitAll()
.antMatchers(HttpMethod.GET, "/user")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
;
//@formatter:on
@Bean(BeanIds.AUTHENTICATION_MANAGER)
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter()
return new JwtAuthenticationFilter();
@Bean
public BCryptPasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder();
编辑:这里是 JwtAuthenticationFilter。如果您也需要 TO,请告诉我。
JwtAuthenticationFilter:
package com.particles.authservice.jwt;
import java.io.IOException;
import java.util.Optional;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.particles.authservice.tos.UserJwt;
public class JwtAuthenticationFilter extends OncePerRequestFilter
private static final String AUTHORIZATION_HEADER_PREFIX = "Authorization";
private static final String AUTHORIZATION_HEADER_BEARER_PREFIX = "Bearer ";
private static final int AUTHORIZATION_HEADER_BEARER_PREFIX_LENGTH = AUTHORIZATION_HEADER_BEARER_PREFIX.length();
@Autowired
private JwtService jwtService;
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException
if (request.getHeader(AUTHORIZATION_HEADER_PREFIX) != null)
final Optional<String> optToken = extractTokenFromRequest(request);
if (optToken.isPresent() && StringUtils.hasText(optToken.get()) && jwtService.isTokenValid(optToken.get()))
// if token exists and is valid, retrieve corresponding UserJwt-object
final UserJwt jwt = jwtService.getJwtFromToken(optToken.get());
final UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(jwt.getUser(), null,
jwt.getUser().getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
/**
* This method extracts a JWT from a @link HttpServletRequest-object.
*
* @param request
* (@link HttpServletRequest) request, which supposedly contains a JWT
* @return (Optional<String>) JWT as String
*/
private Optional<String> extractTokenFromRequest(final HttpServletRequest request)
final String bearerToken = request.getHeader(AUTHORIZATION_HEADER_PREFIX);
String bearerTokenContent = null;
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(AUTHORIZATION_HEADER_BEARER_PREFIX))
bearerTokenContent = bearerToken.substring(AUTHORIZATION_HEADER_BEARER_PREFIX_LENGTH, bearerToken.length());
return Optional.ofNullable(bearerTokenContent);
如果您需要查看其他课程,请告诉我,我会在此处粘贴。
【问题讨论】:
@Code_Is_Law:我添加了 JwtAuthenticationFilter 源代码。UserJwt
类由 3 个属性组成:字符串令牌、PersistableUser 用户(包含用户数据并实现 UserDetails 的实体)和 Date expiresOn。 UserJwt 也是一个实体,因为我决定保存令牌以允许正确注销。
【参考方案1】:
添加以下方法以公开您的公共端点
@Override
public void configure(WebSecurity web) throws Exception
web.ignoring()
.antMatchers("/public-api/**");
【讨论】:
我完全会这样做,但目前我的 API 在localhost:9191/auth/*
下,h2-console (localhost:9191/auth/h2-console
) 也是如此。我是否应该尝试重新调整以使我的所有公共端点都在 localhost:9191/auth/publicapi
下,而私有 API 在 localhost:9191/auth/privateapi
下公开,而 H2 控制台在 localhost:9191/auth/h2-console
下公开?【参考方案2】:
似乎不可能只使用WebSecurityConfigurerAdapter#configure
方法将过滤器应用于特定端点。
相反,我决定将私有 API 中的端点与所有其他端点分开。
需要过滤器的端点(在我的情况下为JwtAuthenticationFilter
)收集在/api/
下,而不是单独定义,因为很有可能有人忘记将它们添加到configure
方法中
所有其他端点的路径都不同于/api/
我的配置方法是这样的:
@Override
protected void configure(final HttpSecurity http) throws Exception
//@formatter:off
http
.cors()
.and()
.csrf()
.disable()
.headers()
.frameOptions()
.disable()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/",
"/favicon.ico",
"/**/*.png",
"/**/*.gif",
"/**/*.svg",
"/**/*.jpg",
"/**/*.html",
"/**/*.css",
"/**/*.js")
.permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers(HttpMethod.POST,
PUBLIC_API_PATH + "register",
PUBLIC_API_PATH + "login")
.permitAll()
.antMatchers(HttpMethod.GET,
PUBLIC_API_PATH + "confirm")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler)
.accessDeniedHandler(unauthorizedHandler)
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
;
//@formatter:on
在JwtAuthenticationFilter
中,我检查请求路径是否包含私有API 路径/api/
。我只应用过滤器。
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException
if (request.getRequestURI().contains(SecurityConfiguration.PRIVATE_API_PATH))
// perform Jwt-authentication since request-URI suggests a call to private-API
...
filterChain.doFilter(request, response);
我不喜欢这个解决方案,特别是因为我必须保持 SecurityConfiguration.PRIVATE_API_PATH
一个常量,因为 @*Mapping(value) 需要一个常量。不过,它可以完成工作。
如果您有更好的建议,我很想尝试一下。
编辑
显然可以像这样在@*Mapping(value) 中使用变量:@PostMapping(value = "$apipath/user")
。所以我毕竟可以使路径可配置 - 但是JwtAuthenticationFilter
中的检查仍然必须保留;我只是不必使用常量,而是变量,其中包含来自例如application.yaml
.
【讨论】:
以上是关于仅在需要身份验证时应用弹簧安全过滤器(由我的应用程序)的主要内容,如果未能解决你的问题,请参考以下文章