java - Spring Boot使用客户端ID和客户端密码公开具有基本身份验证的rest API
问题描述
在我的应用程序中,我既有休息也有 web 部件。
Web 部件具有/admin/**
使用基于表单的身份验证的 url 模式。
而其余部分具有/api/**
使用 jwt 令牌进行身份验证的 url 模式。
此外,在默认配置下,还有另一个 url 模式,/oauth/*
即/oauth/token , /oauth/token_key etc
我正在尝试专门公开 rest api /open/api/**
,它使用基本身份验证,如/oauth/token
.
这样任何请求/open/api/**
看起来像
POST http://{{host}}/open/api/test
Accept: application/json
Authorization: Basic {base64encoded(clientId:clientSecret)} // this is important to expose the api
cache-control: no-cache
我试过谷歌我找不到它的任何配置。
我的配置是
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.AuthenticationManager;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MultiHttpSecurityConfig {
@Autowired
private CustomUserDetailsService userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
@Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Configuration
@Order(1)
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
Logger logger = LoggerFactory.getLogger(FormLoginWebSecurityConfigurerAdapter.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/admin/**")
.authorizeRequests()
.anyRequest().hasAnyAuthority("ADMIN_USER")
.and()
.formLogin()
.loginPage("/admin/login")
.permitAll()
.and()
.logout()
.logoutUrl("/admin/logout")
.invalidateHttpSession(true)
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/403");
http.csrf().disable();
http.headers().frameOptions().disable();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
@Configuration
@EnableResourceServer
@Order(2)
public class CustomResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
Logger logger = LoggerFactory.getLogger(CustomResourceServerConfigurerAdapter.class);
@Autowired
private JdbcTemplate jdbcTemplate;
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(jdbcTemplate.getDataSource());
}
@Bean
@Primary
//Making this primary to avoid any accidental duplication with another token service instance of the same name
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenServices(tokenServices());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.authorizeRequests()
.antMatchers("/api/**" ).authenticated();
}
}
}
有没有办法/open/api
在这个配置中进行配置,还是我必须编写自己的实现才能这样做?
编辑....
经过多次尝试,想出了添加一个过滤器,如下所示的代码。它有效,但我不知道它是否是正确的方法。欢迎提出建议。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.stereotype.Component;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component
public class OpenApiFilter implements Filter {
Logger logger = LoggerFactory.getLogger(OpenApiFilter.class);
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ClientDetailsServiceConfigurer clients;
@Autowired
private PasswordEncoder encoder;
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
logger.info("Initializing filter :{}", this);
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String auth = req.getHeader(HttpHeaders.AUTHORIZATION);
if(auth == null ){
httpResponse.setContentType("application/json");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}else{
if(!auth.startsWith("Basic ")){
httpResponse.setContentType("application/json");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}else{
auth = auth.substring("Basic ".length());
if(!Base64.isBase64(auth)){
httpResponse.setContentType("application/json");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}else{
byte[] decoded = Base64.decodeBase64(auth);
auth = new String(decoded, "UTF-8");
if( !(auth.indexOf(":") > 1) ){
httpResponse.setContentType("application/json");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}else{
String[] credentials = auth.split(":");
try {
ClientDetailsService jdbcClientDetailsServiceBuilder = clients.jdbc(jdbcTemplate.getDataSource()).build();
ClientDetails clientDetails = jdbcClientDetailsServiceBuilder.loadClientByClientId(credentials[0]);
if(!encoder.matches(credentials[1], clientDetails.getClientSecret())){
httpResponse.setContentType("application/json");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
} catch (Exception e) {
logger.error("{}", e.getMessage());
httpResponse.setContentType("application/json");
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
}
}
}
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
logger.info("Destructing filter :{}", this);
}
}
并用bean注册
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@Configuration
public class OpenApiFilterConfig {
@Autowired
private OpenApiFilter openApiFilter;
@Bean
public FilterRegistrationBean < OpenApiFilter > filterRegistrationBean() {
FilterRegistrationBean < OpenApiFilter > registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(openApiFilter);
registrationBean.addUrlPatterns("/open/api/*");
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); //set precedence
return registrationBean;
}
}
解决方案
推荐阅读
- javascript - 如何在 React 中动态渲染嵌套组件?
- java - 如何解决由于加载图像而导致屏幕闪烁的问题?
- regex - 如何使用字符向量长度大于 1 的 grepl?
- c# - ASP.NET Core CORS 配置不适用于 Firefox
- swift - 如何在 Flutter 中实现 3D Secure(通过 Visa / MasterCard SecureCode 验证)?
- c# - 我如何为类型列表应用运算符“/”和“-”
和双? - xcode - 在 Swift 中沿用鼠标绘制的线运行 SKSpriteNode 对象
- c++ - 虚拟析构函数将对象移出rodata部分
- asp.net-core - 如何仅使用 Blazor 获取 API 响应状态代码?
- c++ - 子进程在 fork 和 exec 后变为 Defunct