Swagger - Custom authentication for /api-docs - java

We have have a Spring 5, non-Spring Boot application, using Springfox 2.9.2 + Swagger UI.
I don't know how to secure /api-docs endpoint: I'd like it to call my authentication function each time it's accessed. I made it work for swagger-ui.html, but without success for /api-docs. Here's what I got.
#Configuration
#EnableSwagger2
#EnableWebMvc
public class SwaggerConfig implements WebMvcConfigurer {
#Autowired
protected AuthService authService;
#Override
public void addViewControllers(ViewControllerRegistry registry) {
// registry.addViewController("/docs/swagger/api-docs"); doesnt work
registry.addRedirectViewController("/docs/swagger/swagger-resources/configuration/ui", "/swagger-resources/configuration/ui");
registry.addRedirectViewController("/docs/swagger/swagger-resources/configuration/security", "/swagger-resources/configuration/security");
registry.addRedirectViewController("/docs/swagger/swagger-resources", "/swagger-resources");
}
class Interceptor implements HandlerInterceptor{
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler ) {
try{
authService.assertAdmin(); // I need to call this
}catch (Exception e){
return false;
}
return true;
}
}
#Override
public void addInterceptors( final InterceptorRegistry registry) {
registry.addInterceptor(new Interceptor());
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// docs/swagger/index.html
registry.addResourceHandler("/docs/swagger/swagger-ui.html**")
.addResourceLocations("classpath:/META-INF/resources/swagger-ui.html");
// docs/swagger/webjars
registry.addResourceHandler("/docs/swagger/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Another option would be to close access to /api-docs permanently and just directly call the method that generates JSON from some new endpoint. Would that be possible?

Eventually I solved this by spring security, as #UsamaAmjad proposed.
open class SecurityInitializer : AbstractSecurityWebApplicationInitializer()
#Configuration
#EnableWebSecurity
open class SecurityConfig : WebSecurityConfigurerAdapter() {
#Throws(Exception::class)
override fun configure(http: HttpSecurity) {
http.antMatcher("/docs/swagger/v2/api-docs").addFilter(myFilter())
}
open fun myFilter() = object : FilterSecurityInterceptor() {
override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain?) {
if (do your stuff here) {
chain!!.doFilter(request,response) // continue with other filters
} else {
super.doFilter(request, response, chain) // filter this request
}
}
}
}

Related

Spring Data Rest #RepositoryRestController and #RequestMapping

I want to override #RepositoryRestResource autogenerated controller methods using #RepositoryRestController having set the SDR's Base Path
to "/api".
Spring Data Rest 3.0 (and earlier) says:
"This controller [as shown in the snippet] will be served from the same API base path defined in RepositoryRestConfiguration.setBasePath that is used by all other RESTful endpoints (e.g. /api)".
https://docs.spring.io/spring-data/rest/docs/3.0.1.RELEASE/reference/html/#customizing-sdr.overriding-sdr-response-handlers (chapter 15.4)
This code snippet DOES NOT have a #RequestMapping on the class level, though.
My SDR app is configured with RepositoryRestConfiguration object
config.setBasePath("/api");
and yet #RepositoryRestController doesn't override SDR's autogenerated controller methods.
Please consider the accepted answear to this post:
Spring Data Rest controllers: behaviour and usage of #BasePathAwareController, #RepositoryRestController, #Controller and #RestController
Please help me understand this! :)
AppConf.java:
#Configuration
#Import(value = {DataConf.class})
#EnableWebMvc
#ComponentScan(value = "pl.mydomain.controller")
public class AppConf
{
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setBasePath("/api");
}
};
}
}
TokenController.java:
#RepositoryRestController
public class TokenController
{
private TokenRepository repository;
#Autowired
public TokenController(TokenRepository tokenRepository) {
this.repository = tokenRepository;
}
#RequestMapping(method = GET, path = "/tokens")
public #ResponseBody ResponseEntity<?> tokens()
{
return ResponseEntity.ok("Hello");
}
}
TokenRepository.java:
#RepositoryRestResource(path = "tokens")
public interface TokenRepository extends CrudRepository<Token, Long>{
}
The key to resolve the above dilemma was configuring the project in a correct fashion. That is, to put #ComponentScan in the class passed to AbstractAnnotationConfigDispatcherServletInitializer::getServletConfigClasses() method (not in AppConf.java passed to getRootConfigClasses()).
DispatcherConf.java:
public class DispatcherConf extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] {AppConf.class};
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[] {WebConf.class}; // !!!
}
#Override
protected String[] getServletMappings() {
return new String[] {"/*"};
}
}
AppConf.java:
#Configuration
#Import({DataConf.class})
public class ApplicationConf
{
#Bean
public RepositoryRestConfigurer repositoryRestConfigurer() {
return new RepositoryRestConfigurerAdapter() {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.setBasePath("/api"); // !!!
}
};
}
}
DataConf.java:
#Configuration
#EnableJpaRepositories(basePackages = {
"pl.example.data.repository"
})
#EnableTransactionManagement
public class DataConf
{ ... }
WebConf.java:
#Import(RepositoryRestMvcConfiguration.class)
#ComponentScan({"pl.example.api.controller"}) // !!!
public class WebConf {
}
Even if I solved the riddle I don't understand why it was an issue. The rather that https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html states:
Annotation Type ComponentScan onfigures component scanning directives
for use with #Configuration classes.

Spring boot: How to add interceptors to static resources?

I have several folders in /static/img/** and I need to add interceptors to some of them to check user permissions. I've used interceptors earlier and added them this way:
#SpringBootApplication
#EnableTransactionManagement
public class Application extends WebMvcConfigurerAdapter {
...
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
#Bean
public AuthHeaderInterceptor authHeaderInterceptor() {
return new AuthHeaderInterceptor();
}
#Bean
public AuthCookieInterceptor authCookieInterceptor() {
return new AuthCookieInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(authHeaderInterceptor())
.addPathPatterns(REST_URL)
.excludePathPatterns(
new String[] {
REST_SECURITY_URL,
REST_SETTINGS_URL,
REST_REPORTS_URL
}
);
registry
.addInterceptor(authCookieInterceptor())
.addPathPatterns(REST_REPORTS_URL);
}
}
All works fine for rest controllers and their URLs, but now I need to secure some static resources and I added this:
#SpringBootApplication
#EnableTransactionManagement
public class Application extends WebMvcConfigurerAdapter {
...
#Bean
public RoleAdminInterceptor roleAdminInterceptor() {
return new RoleAdminInterceptor();
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry
.addInterceptor(authHeaderInterceptor())
.addPathPatterns(REST_URL)
.excludePathPatterns(
new String[] {
REST_SECURITY_URL,
REST_SETTINGS_URL,
REST_REPORTS_URL
}
);
//THIS NOT WORK
registry
.addInterceptor(roleAdminInterceptor())
.addPathPatterns("/static/img/admin/**");
registry
.addInterceptor(authCookieInterceptor())
.addPathPatterns(REST_REPORTS_URL);
}
}
Commented line doesn't work. When I send request to /static/img/admin/test.png RoleAdminInterceptor is never called.
What I'm doing wrong?
I know this is an old question, but since it's unanswered it might help others searching for it.
This is what worked for me:
1- Declare an interceptor class:
class RoleBasedAccessInterceptor implements HandlerInterceptor {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
AntPathMatcher matcher = new AntPathMatcher();
String pattern = "/static/img/admin/**";
String requestURI = request.getRequestURI();
if (matcher.match(pattern, requestURI)) {
//Do whatever you need
return validateYourLogic();
}
return true;
}
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2- Configure WebMvcConfigurer
public class WebMvcConfiguration implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RoleBasedAccessInterceptor());
}
}
I think in this case you could use Filters with Spring Security instead of Interceptors as you could Validate the access earlier on the process even before hitting the Interceptor, unless there is a specific use case that you need to use the interceptor here.
Some topic about the difference between these two:
filters-vs-interceptor

Use #PathParam(javax.websocket.server.PathParam) in WebSocketConfigurer for Spring Boot application

I have created a spring boot application in which I want to use Web Sockets. When I am using it without parameters its working fine. Below is the code without the parameters
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ABC(), "/getABC").setAllowedOrigins("*");
registry.addHandler(new XYZ(), "/getXYZ").setAllowedOrigins("*");
}
}
But now I need to pass a parameter to it using #PathParam. I am not able to use it in this configuration like
registry.addHandler(new XYZ(), "/getXYZ{someId}").setAllowedOrigins("*");
My Handler code:
public class XYZ extends TextWebSocketHandler {
static List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
String someId;
public XYZ() {
}
public XYZ(#PathParam(value = "someId") String someId) {
this.someId= someId;
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// the messages will be broadcasted to all users.
sessions.add(session);
}
}
I think there is some problem with the syntax, try using
public XYZ(#PathParam("someId") String someId)

Other way to achive GWTP placeManager than Constructor's #Inject annotation

I use GWTP and restyGWT. I would like to use placeManager in restyGWT DispatcherCallback, when my rest server will answer with 401 unauthorised I would like to redirect application to login page, that User could apply credentials and retried his request.
To do this I have to somehow get instance of PlaceManager (from gwtp framework). I cannot use #Inject annotation, cause I have manuall call to constructor as follow:
public class ForbiddenDispatcherFilter implements DispatcherFilter {
#Override
public boolean filter(Method method, RequestBuilder builder) {
builder.setCallback(new ForbiddenDispatcherCallback(method));
return true;
}
}
public class ForbiddenDispatcherCallback implements RequestCallback {
protected RequestCallback requestCallback;
public ForbiddenDispatcherCallback(Method method) {
this.requestCallback = method.builder.getCallback();
}
#Override
public void onResponseReceived(Request request, Response response) {
if (response.getStatusCode() == Response.SC_FORBIDDEN || response.getStatusCode() == Response.SC_UNAUTHORIZED) {
// make a hard redirect to login page
// TODO change redirect to GWTP native
Window.Location.assign("#login");
// PlaceRequest placeRequest = new
// PlaceRequest.Builder(placeManager.getCurrentPlaceRequest()).nameToken(Routing.Url.login).build();
// placeManager.revealPlace(placeRequest);
} else {
requestCallback.onResponseReceived(request, response);
}
}
public class RestyDispatcher extends DefaultFilterawareDispatcher {
public RestyDispatcher() {
addFilter(new ForbiddenDispatcherFilter());
addFilter(new BasicAuthHeaderDispatcherFilter());
}
#Override
public Request send(Method method, RequestBuilder builder) throws RequestException {
return super.send(method, builder);
}
}
Please help.
Edit
public class ClientModule extends AbstractPresenterModule {
#Override
protected void configure() {
bind(RestyGwtConfig.class).asEagerSingleton();
install(new DefaultModule.Builder()//
.defaultPlace(Routing.HOME.url)//
.errorPlace(Routing.ERROR.url)//
.unauthorizedPlace(Routing.LOGIN.url)//
.tokenFormatter(RouteTokenFormatter.class).build());
install(new AppModule());
// install(new
// GinFactoryModuleBuilder().build(AssistedInjectionFactory.class));
bind(CurrentUser.class).in(Singleton.class);
bind(IsAdminGatekeeper.class).in(Singleton.class);
bind(UserLoginGatekeeper.class).in(Singleton.class);
// Google Analytics
// bindConstant().annotatedWith(GaAccount.class).to("UA-8319339-6");
// Load and inject CSS resources
bind(ResourceLoader.class).asEagerSingleton();
}
}
and:
public class RestyGwtConfig {
static {
// GWT.log("--> RestyGwtConfig -> setDispatcher");
Defaults.setDispatcher(new RestyDispatcher());
// GWT.log("--> RestyGwtConfig -> setServiceRoot");
Defaults.setServiceRoot(new Resource(GWT.getModuleBaseURL()).resolve(ServiceRouting.SERVICE_ROOT).getUri());
UserCredentials.INSTANCE.setUserName("ronan");
UserCredentials.INSTANCE.setPassword("password");
}
}
How and where do you create your ForbiddenDispatcherFilter ?
You could use guice's AssistedInjection to inject the PlaceManager into your ForbiddenDispatcherCallback.
public class ForbiddenDispatcherCallback implements RequestCallback {
protected RequestCallback requestCallback;
protected PlaceManager placeManager;
#Inject
public ForbiddenDispatcherCallback(PlaceManager placeManager, #Assisted Method method) {
this.placeManager = placeManager;
this.requestCallback = method.builder.getCallback();
}
}
You need to define an factory interface:
public interface AssistedInjectionFactory {
ForbiddenDispatcherCallback createForbiddenCallback(Method method);
}
In the configure method of your ClientModule you need to call:
install(new GinFactoryModuleBuilder().build(AssistedInjectionFactory.class));
Then you can instantiate your class this way:
public class ForbiddenDispatcherFilter implements DispatcherFilter {
AssistedInjectionFactory factory;
#Inject
public ForbiddenDispatcherFilter(AssistedInjectionFactory factory)
{
this.factory = factory;
}
#Override
public boolean filter(Method method, RequestBuilder builder) {
builder.setCallback(factory.AssistedInjectionFactory(method))
return true;
}
}
Of course this requires that you also inject the ForbiddenDispatcherFilter.
Edit:
You could try to pass the RestyDispatcher to the constructor of your RestyGWTConfig:
public class RestyGwtConfig {
#Inject
public RestyGwtConfig(RestyDispatcher dispatcher) {
Defaults.setDispatcher(dispatcher);
}
static {
// GWT.log("--> RestyGwtConfig -> setServiceRoot");
Defaults.setServiceRoot(new Resource(GWT.getModuleBaseURL()).resolve(ServiceRouting.SERVICE_ROOT).getUri());
UserCredentials.INSTANCE.setUserName("ronan");
UserCredentials.INSTANCE.setPassword("password");
}
}
The RestyDispatcher looks like this:
public class RestyDispatcher extends DefaultFilterawareDispatcher {
#Inject
public RestyDispatcher(ForbiddenDispatcherFilter filter) {
addFilter(filter);
addFilter(new BasicAuthHeaderDispatcherFilter());
}
#Override
public Request send(Method method, RequestBuilder builder) throws RequestException {
return super.send(method, builder);
}
}

How to allow specific requests when using spring-security

I've a scenario where I need to let some request (let's say request method is GET) so that no 401 error can be thrown.
Below is my Spring Security configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/bower_components/**")
.antMatchers("/fonts/**")
.antMatchers("/images/**")
.antMatchers("/scripts/**")
.antMatchers("/styles/**")
.antMatchers("/views/**")
.antMatchers("/i18n/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/app/rest/register")
.antMatchers("/app/rest/activate");
}
}
And this is my ResourceServerConfigurerAdapter implementation:
How can I allow requests?
There is a method called requestMatchers where you can call it with one or more RequestMatcher implementaions.
public void configure(HttpSecurity http){
.....
web.ignoring().requestMatchers(new MethodTypeRequestMatcher(RequestMethod.GET));
.....
}
And you can define your implementation:
public class MethodRequestMatcher implements RequestMatcher {
private RequestMethod method;
public MethodRequestMatcher(RequestMethod method) {
this.method = method;
}
#Override
public boolean matches(HttpServletRequest request) {
if (method == null) {
return false;
}
return request.getMethod().equals(method.name());
}
}
I think you can try like follows:
<code>
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers()
.httpStrictTransportSecurity()
.xssProtection()
.frameOptions()
.and().authorizeRequests()
// PERMIT ALL
.antMatchers("/home").permitAll()
// UNAUTHENTICATED USER
.antMatchers("/ForgetPassword").anonymous()
// TO SPECIFIC PERSON
.antMatchers("/Report").access("hasRole('ADMIN') or hasRole('S_USER')");
}
</code>

Categories