如何在 spring 中验证自定义会话
How to validate a custom session in spring
我是 spring 的新手,所以只是在寻找一个大概的方向/建议。
我正在构建一个小型微服务。服务身份验证已设置为验证传递给它的 JWT(由不同的微服务发布)。
JWT 包含 sessionid 声明。我希望能够获得该声明,并验证会话是否仍处于活动状态。
目前是通过直接查询数据库,尽管将来当我们有专用会话服务时,我会将其更改为调用该服务。
我显然可以在每个应该验证会话的控制器中获得声明(基本上所有这些),但我正在寻找一个更全局的选项,比如请求拦截器(仅在验证 JWT 之后触发,并且可以访问经过验证的 JWT)。
在spring中有推荐的方法来做这种事情吗?
您有几种选择:使用 ControllerAdvice
,也许是 AOP,但可能的方法是使用自定义安全过滤器。
这个想法在this article and this related SO question中得到了例证。
首先,创建一个过滤器来处理适当的声明。例如:
public class SessionValidationFilter extends GenericFilterBean {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String AUTHORIZATION_BEARER = "Bearer";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// We will provide our own validation logic from scratch
// If you are using Spring OAuth or something similar
// you can instead use the already authenticated token, something like:
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// if (authentication != null && authentication.getPrincipal() instanceof Jwt) {
// Jwt jwt = (Jwt) authentication.getPrincipal();
// String sessionId = jwt.getClaimAsString("sessionid");
// ...
// Resolve token from request
String jwt = getTokenFromRequest(httpServletRequest);
if (jwt == null) {
// your choice... mine
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// If the token is not valid, raise error
if (!this.validateToken(jwt)) {
throw new BadCredentialsException("Session expired");
}
// Continue filter chain
filterChain.doFilter(servletRequest, servletResponse);
}
// Resolve token from Authorization header
private String getTokenFromRequest(HttpServletRequest request){
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(bearerToken) && bearerToken.startsWith(AUTHORIZATION_BEARER)) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
// Validate the JWT token
// We can use the jjwt library, for instance, to process the JWT token claims
private boolean validateToken(String token) {
try {
Claims claims = Jwts.parser()
// .setSigningKey(...)
.parseClaimsJws(token)
.getBody()
;
String sessionId = (String)claims.get("sessionid");
// Process as appropriate to determine whether the session is valid or not
//...
return true;
} catch (Exception e) {
// consider logging the error. Handle as appropriate
}
return false;
}
}
现在,假设您正在使用 Java 配置,将过滤器添加到 Spring 安全过滤器链中实际执行身份验证的过滤器链中:
@Configuration
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
// the rest of your code
@Override
protected void configure(HttpSecurity http) throws Exception {
// the rest of your configuration
// Add the custom filter
// see https://docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/reference/htmlsingle/#filter-stack
// you can name every provided filter or any specified that is included in the filter chain
http.addFilterAfter(new SessionValidationFilter(), BasicAuthenticationFilter.class);
}
}
我是 spring 的新手,所以只是在寻找一个大概的方向/建议。
我正在构建一个小型微服务。服务身份验证已设置为验证传递给它的 JWT(由不同的微服务发布)。
JWT 包含 sessionid 声明。我希望能够获得该声明,并验证会话是否仍处于活动状态。
目前是通过直接查询数据库,尽管将来当我们有专用会话服务时,我会将其更改为调用该服务。
我显然可以在每个应该验证会话的控制器中获得声明(基本上所有这些),但我正在寻找一个更全局的选项,比如请求拦截器(仅在验证 JWT 之后触发,并且可以访问经过验证的 JWT)。
在spring中有推荐的方法来做这种事情吗?
您有几种选择:使用 ControllerAdvice
,也许是 AOP,但可能的方法是使用自定义安全过滤器。
这个想法在this article and this related SO question中得到了例证。
首先,创建一个过滤器来处理适当的声明。例如:
public class SessionValidationFilter extends GenericFilterBean {
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String AUTHORIZATION_BEARER = "Bearer";
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
// We will provide our own validation logic from scratch
// If you are using Spring OAuth or something similar
// you can instead use the already authenticated token, something like:
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// if (authentication != null && authentication.getPrincipal() instanceof Jwt) {
// Jwt jwt = (Jwt) authentication.getPrincipal();
// String sessionId = jwt.getClaimAsString("sessionid");
// ...
// Resolve token from request
String jwt = getTokenFromRequest(httpServletRequest);
if (jwt == null) {
// your choice... mine
filterChain.doFilter(servletRequest, servletResponse);
return;
}
// If the token is not valid, raise error
if (!this.validateToken(jwt)) {
throw new BadCredentialsException("Session expired");
}
// Continue filter chain
filterChain.doFilter(servletRequest, servletResponse);
}
// Resolve token from Authorization header
private String getTokenFromRequest(HttpServletRequest request){
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(bearerToken) && bearerToken.startsWith(AUTHORIZATION_BEARER)) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
// Validate the JWT token
// We can use the jjwt library, for instance, to process the JWT token claims
private boolean validateToken(String token) {
try {
Claims claims = Jwts.parser()
// .setSigningKey(...)
.parseClaimsJws(token)
.getBody()
;
String sessionId = (String)claims.get("sessionid");
// Process as appropriate to determine whether the session is valid or not
//...
return true;
} catch (Exception e) {
// consider logging the error. Handle as appropriate
}
return false;
}
}
现在,假设您正在使用 Java 配置,将过滤器添加到 Spring 安全过滤器链中实际执行身份验证的过滤器链中:
@Configuration
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter {
// the rest of your code
@Override
protected void configure(HttpSecurity http) throws Exception {
// the rest of your configuration
// Add the custom filter
// see https://docs.spring.io/spring-security/site/docs/5.2.1.RELEASE/reference/htmlsingle/#filter-stack
// you can name every provided filter or any specified that is included in the filter chain
http.addFilterAfter(new SessionValidationFilter(), BasicAuthenticationFilter.class);
}
}