在我的应用程序中,我既有休息也有网络部分。
Web部件的URL模式为/admin/**
,它使用基于表单的身份验证。
而其余部分的网址格式为/api/**
,它使用jwt令牌进行身份验证。
同样在默认配置下,还有另一个网址格式/oauth/*
,即/oauth/token , /oauth/token_key etc
我正在尝试公开专用于/open/api/**
的rest api,该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
我尝试过google,但找不到任何配置。
我的配置是
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;
}
}