刚刚开通了一个公众号,会分享一些技术博客和自己觉得比较好的项目,同时会更新一些自己使用的工具和图书资料,后面会整理一些面试资料进行分享,觉得有兴趣的可以关注一下。
前言
最近开发的新需求中需要给前端的权限做验证,由于项目中统一引用的spring security
,那么直接使用就好了。
问题
1.权限的配置是统一的,权限的配置不是使用的spring security
提供的方式,而是结合了oauth2
跟jwt
的方式,解析到用户名之后再发送请求获取的。
2.azure
提供的jwt
的token
解析的用户名不是我们需要的,而是azure
生成的自己系统的用户id
解决
1.先看第二个问题,比较简单
经过debug
发现,解析用户名的其实是JwtAuthenticationConverter
,查看其方法,他默认是将sub
作为了用户名。具体代码如下:
private String principalClaimName = JwtClaimNames.SUB;
@Override
public final AbstractAuthenticationToken convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = extractAuthorities(jwt);
String principalClaimValue = jwt.getClaimAsString(this.principalClaimName);
return new JwtAuthenticationToken(jwt, authorities, principalClaimValue);
}
那么需要找到这个bean
是怎么配置的,设置一下这个principalClaimName
就OK了。
这个是在OAuth2ResourceServerConfigurer
里面配置的,他先看容器里面有没有,没有的话自己new
一个。具体代码如下:
Converter<Jwt, ? extends AbstractAuthenticationToken> getJwtAuthenticationConverter() {
if (this.jwtAuthenticationConverter == null) {
if (this.context.getBeanNamesForType(JwtAuthenticationConverter.class).length > 0) {
this.jwtAuthenticationConverter = this.context.getBean(JwtAuthenticationConverter.class);
}
else {
this.jwtAuthenticationConverter = new JwtAuthenticationConverter();
}
}
return this.jwtAuthenticationConverter;
}
那就简单了,直接生命一个Bean
放容器里就好了。
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
converter.setPrincipalClaimName("username");
return converter;
}
2.再来看第一个问题
一般来说,权限信息不会变更那么频繁,也不可能每次请求我都去重新查一下权限,那么就必须缓存下来,能增加请求速度。
但是由于这是jwt
和spring security
,仅缓存时不够的,必须写入spring security
认证的数据当中。就是JwtAuthenticationToken
。请求还得存储,本来想的是通过一个请求,在service
中存储并写权限,那这样的话请求其他接口就不会生效,
所以必须放在spring security
的请求链里面。
配置如下:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, WebSecurityResponseExceptionHandler webSecurityResponseExceptionHandler) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(webSecurityResponseExceptionHandler).accessDeniedHandler(webSecurityResponseExceptionHandler)
.and()
.anyRequest().authenticated().and().sessionManagement()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.addFilterAfter(new PermissionProviderFilter(), BearerTokenAuthenticationFilter.class);
return http.build();
}
filter
代码如下,其实就是照着葫芦画瓢:
private class PermissionProviderFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String requestURI = ((HttpServletRequest) request).getRequestURI();
if (requestURI.startsWith(CConstant.API_BACKEND_URL)) {
chain.doFilter(request, response);
return;
}
JwtAuthenticationToken oldAuthentication= (JwtAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
if (Objects.isNull(oldAuthentication)) {
chain.doFilter(request, response);
return;
}
String username = ((Jwt) oldAuthentication.getPrincipal()).getClaim("username");
List<GrantedAuthority> authorities = CacheUtils.getCache(username);
if (Objects.isNull(authorities)) {
List<GrantedAuthority> authorities = userService.userDetail(username);
CacheUtils.setCache(username, authorities);
}
JwtAuthenticationToken jwtAuthenticationToken = new JwtAuthenticationToken(oldAuthentication.getToken(), authorities, username);
jwtAuthenticationToken.setDetails(oldAuthentication.getDetails());
jwtAuthenticationToken.eraseCredentials();
SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);
chain.doFilter(request, response);
}
}