Configure Spring Boot for SPA frontend - java

I have application where whole frontend part is laying in resource. I would like to separate things apart. And have separate server for UI, provided by gulp, for example.
So that I assume that my server should return index.html for all requests that are rendered by client side.
Eg: I have 'user/:id' rout that is managing by angular routing and doesn't need server for anything. How can I configure so that server will not reload or redirect me to anywhere?
My security config is following(don't know if it responsible for such things):
public class Application extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**", "/app/**", "/app.js")
.permitAll().anyRequest().authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")).and().logout()
.logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}

For routing, according to this guide at Using "Natural" Routes (specifically here), you have to add a controller that does the following:
#Controller
public class RouteController {
#RequestMapping(value = "/{path:[^\\.]*}")
public String redirect() {
return "forward:/";
}
}
Then using Spring Boot, the index.html loads at /, and resources can be loaded; routes are handled by Angular.

EpicPandaForce has a great answer, and I wanted to expand on it. The following endpoint will allow matching on nested routes as well. If you wanted to have an admin section, you can configure it to return a different index.html.
#Controller
class PageController {
#GetMapping("/**/{path:[^\\.]*}")
fun forward(request: HttpServletRequest): String? {
if(request.requestURI.startsWith("/admin")) {
return "forward:/admin/index.html"
}
return "forward:/index.html"
}
}
This RequestMapping (or #GetMapping) works by excluding any request that contains a period (i.e. "index.html").

If you are using Angular with Spring Data Rest, I think that the most straightforward way to do it is using angular hash location strategy.
Just putting this in the providers array in your app module:
{ provide: LocationStrategy, useClass: HashLocationStrategy }
and, obviously, import it.

Related

Serve static files, template files and REST endpoints with and without authentication from one application

I am trying to create a single Spring Boot application (latest version) that can serve:
static HTML, CSS, JavaScript and image (favicon, jpg, png etc) files
HTML content based on Thymeleaf template files
REST endpoints
All of the above should be able to serve:
without authentication (public access)
with authentication (restricted access)
Meaning that the following mappings should apply:
URL request
Served from (resource folder or a controller)
Public
Notes
/ui/media/*
../resources/web/ui/media/*
Yes
/ui/style/*
../resources/web/ui/style/*
Yes
/ui/scrippt/*
../resources/web/ui/scrippt/*
Yes
/ui/login
../resources/web/ui/login.html
Yes
/ui/forgot
../resources/web/ui/forgot.html
Yes
/ui/admin/*
../resources/web/ui/admin/*
No
1
/ui/user/*
../resources/web/ui/user/* and UiUserController using Thymeleaf template files
No
1, 2
/api/auth/login
AuthenticationController::login()
Yes
/api/auth/forgot
AuthenticationController::login()
Yes
/api/ping
ApiPingPongController::ping()
Yes
/api/pong
ApiPingPongController::pong()
No
1
/api/v1/*
WildcardController::handle()
Yes
3
Notes:
Requires user to be authenticated
UiUserController class handles endpoints and uses Thymeleaf template files from resource folder
This single method should be able to handle any request (GET/POST/...) starting with /api/v1/** and based on a hardcoded list of values either can serve for public access or check if JWT token is present and valid (meaning validation should be inside this method. I can validate JWT inhere, so I don't need a solution for that. Just wanted to add it so you know this endpoint is special compared to most examples around.
I've listed lots of endpoints, but only because I haven't been able to combine all of these and security public/non-public and can't find any examples on the Internet that combines all of these.
What I have so far:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
// Public static files
.antMatchers(HttpMethod.GET, "/ui/login", "/ui/forgot", "/ui/media/**", "/ui/style/**", "/ui/script/**").permitAll()
// User static files based on Thymeleaf
.antMatchers(HttpMethod.GET,"/ui/user/**").hasRole("USER")
// Administration static file
.antMatchers(HttpMethod.GET,"/ui/admin/**").hasRole("ADMIN")
// Authentication REST endpoints
.antMatchers(HttpMethod.POST, "/api/auth/login").permitAll()
.antMatchers(HttpMethod.POST, "/api/auth/forgot").permitAll()
// /api/ping, /api/pong endpoints
.antMatchers("/api/ping").permitAll()
.antMatchers("/api/pong").hasAnyRole("USER", "ADMIN")
// /api/v1/** endpoint
.antMatchers("/api/v1/**").permitAll()
.anyRequest().authenticated().and()
// JWT filter
.addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/resources/**");
}
}
The authentication process and REST endpoints works, but the Thymeleaf endpoint can not find resource files and the static files are not accessible at all (even public or authenticated).
I've tried so many combinations for the last 3 weeks that I am really close to giving up on this.
Can someone point me in the right direction?
add this in your webSecurityConfig class
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**");
}

Access OAuth2 Protected api from the Java Batch Application

I have Springboot Microservice app comprises( Discovery , Eureka Client , Zulu Proxy , Gateway )which is configured with OAUTH2 which is working fine.
and OAUTH2 is configured as the in memory token store. i have rest end points gateway exposed
ex :
localhost:8080/hello/gateway
now i have java batch , which will call micro service app gateway example (above api) to get the required response. since that is protected with OAUTH2 i cant access api directly.
is there a way to access the api without token or can we bypass the authorization logic by passing the hardcoded token from batch and validating in the Gateway
Tried to create a non-expiring token but since its in-memory token, it will not work after the api restart
Tired to create a custom filter and but it didnt work as expected . below is my resource server code.
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
private static final String RESOURCE_ID = "resource_id";
#Autowired
private AppProperties appProperties;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID).stateless(false);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.
anonymous().disable()
.authorizeRequests()
.antMatchers("/testService/**").authenticated()
.and()
//.addFilterBefore(new BatchCustomFilter(), BasicAuthenticationFilter.class)
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
}
Let me if know there is any good way of doing this , Suggestions are highly appreciated .
yes had overridden WebSecurityConfigurerAdapter configure method to ignore the specific endpoint.
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/testService/testApi/**");
}
Now im able to access Api from batch after ignoring the endpoint from web security.

Resource blocked due to MIME type in Spring MVC when DELETE endpoint added

I'm developing a web app using Spring Boot v2.1.9.RELEASE and Thymeleaf. The app mixes server-side rendering (Spring MVC regular controllers) with rest controllers called through AJAX. The page works like a charm as long as GET and POST are the only HTTP methods I use. As soon as I add a - totally unrelated, yet unused - DELETE REST controller, CSS and JS resources stop being loaded and errors like the following are shown in browser console:
The resource from “http://localhost:8080/css/demo.css” was blocked due to MIME type (“application/json”) mismatch (X-Content-Type-Options: nosniff)
If I remove the new controller, the page starts working again.
When I inspect the request with Network Monitor, I observe that:
X-Content-Type-Options: nosniff header is always present in the response, which is consistent with Spring Security documentation.
if the DELETE endpoint is not there, a GET /css/demo.css request returns with a HTTP 200 code as expected. Both Accept (request header) and Content-Type (response header) are marked as text/css.
when I add the endpoint, the above request returns a HTTP 405. Accept header is text/css but Content-Type becomes application/json. Also, a new response header is added: Allow: DELETE.
My guess is that when Spring scans for controllers and a DELETE method is found, some extra configuration is automatically added, but I couldn't find any reference to confirm that. I would like to know why is this happening and how can I avoid this behaviour.
Since I'm developing local, my Spring Security config is pretty straightforward:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public AuthenticationManagerConfigurer authConfigurer() {
return new InMemoryAuthenticationManagerConfigurer();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
authConfigurer().configure(auth);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The REST controller I'm adding does almost nothing:
#RestController
public class MotivosController {
private final String CONTROLLER_PATH = "/rest/motivos/{id}";
#RequestMapping(name=CONTROLLER_PATH, method=RequestMethod.DELETE)
public void deleteMotivo(#PathVariable(name="id") String id) {
logger.debug("Eliminando el motivo " + id);
}
// Other members I consider unrelated to the problem go here...
}
CSS and JS files are being loaded in a template.html file placed under src/main/resources/templates folder, as follows:
<link type="text/css" href="/css/demo.css" rel="stylesheet" />
Answer was easier than I thought and I was totally wrong with my assumptions. I confused #RequestMapping's name attribute with value attribute, and because of that any URL matches that method. Changing name for value made it work.

Basic Authentication using Swagger UI

I am trying to develop a spring-boot based rest API service with API documentation through Swagger UI. I want to enable basic authentication via the swagger UI so that the user can only run the API's once he/she authenticates using the Authorize button on swagger UI (by which a "authorization: Basic XYZ header is added to the API Call
At the front end (in the .json file for the Swagger UI I have added basic authentication for all the APIs using the following code (as per the documentation):
"securityDefinitions": {
"basic_auth": {
"type": "basic"
}
},
"security": [
{
"basic_auth": []
}
]
How should I implement the backend logic for the use case mentioned above (user can only run the API's once he/she authenticates using the Authorize button on swagger UI and it otherwise shows a 401 Error on running the API)
Some documentation or sample code for the same would be helpful
One option is to use the browser pop up authorization.
When you enable basic auth for your spring boot app, swagger ui will automatically use the browser's pop up window in order to use it for basic auth. This means that the browser will keep the credentials for making requests just like when you trying to access a secured GET endpoint until you close it.
Now, let's say you DON'T want to use the above and want swagger-ui for basic authentication as you say, you have to enable auth functionality on swagger-ui and optionally add security exception when accessing swagger-ui url.
To enable the basic auth functionality to swagger UI (with the "Authorize button" in UI) you have to set security Context and Scheme to your Swagger Docket (This is a simplified version):
#Configuration
#EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer{
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.securityContexts(Arrays.asList(securityContext()))
.securitySchemes(Arrays.asList(basicAuthScheme()));
}
private SecurityContext securityContext() {
return SecurityContext.builder()
.securityReferences(Arrays.asList(basicAuthReference()))
.forPaths(PathSelectors.ant("/api/v1/**"))
.build();
}
private SecurityScheme basicAuthScheme() {
return new BasicAuth("basicAuth");
}
private SecurityReference basicAuthReference() {
return new SecurityReference("basicAuth", new AuthorizationScope[0]);
}
}
This enables the authorization button in ui.
Now you probably want for your users to access the swagger-ui freely and use this button for authorization. To do this you have to exempt swagger for app's basic auth. Part of this configuration is Security config and you have to add following code:
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(
"/", "/csrf",
"/v2/api-docs",
"/swagger-resources/**",
"/swagger-ui.html",
"/webjars/**"
).permitAll()
.anyRequest().authenticated();
}
}
A similar problem I was facing was that when using springfox documentation with Swagger OAS 3.0, the "Authenticate" button would not appear on the swagger UI.
Turns out there was a bug created for this very issue-
https://github.com/springfox/springfox/issues/3518
The core of the problem-
Class BasicAuth is deprecated.
The solution as found in the bug report above is to use HttpAuthenticationScheme instead to define the SecurityScheme object.
The Docket configuration then looks like so-
return new Docket(DocumentationType.OAS_30)
.groupName("Your_Group_name")
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.mypackage"))
.paths(PathSelectors.regex("/.*"))
.build().securitySchemes(Arrays.asList(HttpAuthenticationScheme.BASIC_AUTH_BUILDER.name("basicAuth").description("Basic authorization").build()))
.securityContexts(); //define security context for your app here
Use a following dependency in build.gradle to enable a security:
"org.springframework.boot:spring-boot-starter-security"
In application.properties you can define your own username and password using:
spring.security.user.name=user
spring.security.user.password=password
Those who want to basic auth only for endpoints should do everything what #Sifis wrote but need to change antMatchers as:
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(
"/",
"/v2/api-docs/**",
"/v3/api-docs/**",
"/swagger-resources/**",
"/swagger-ui/**",
"/swagger-ui.html").permitAll()
.anyRequest().authenticated();
}
}

How do I use "/images" path in Spring boot Rest controller with security?

I have a Spring boot REST service (spring-boot-starter-parent:1.3.2) that exposes some endpoints using RestController defined methods. I am also using Spring security. Everything works fine until I try to define a controller method that is mapped to "/images". When I try to access this api path I get the following error. By debugging I can see that my controller handler is being mapped, but the preauthorize filter is not being called (it is called properly for other mappings). I have set the following properties, but with no change. How do I fix this so that I can use "/images"?
spring.resources.add-mappings=false
spring.mvc.static-path-pattern=/hide-me/**
Error:
"exception": "org.springframework.security.authentication.AuthenticationCredentialsNotFoundException",
"message": "An Authentication object was not found in the SecurityContext",
Code:
#RestController
#PreAuthorize(value = "hasAnyAuthority('SOMEUSER')")
public class ImageController {
...
#RequestMapping(value = { "/images/{imageId}" }, method = RequestMethod.GET)
#ResponseBody
public Image getImage(#PathVariable UUID imageId) {
return imageDataService.getImage(imageId);
}
...
If I change the mapping to the following then it works just fine.
#RequestMapping(value = { "/image/{imageId}" }, method = RequestMethod.GET)
#ResponseBody
public Image getImage(#PathVariable UUID imageId) {
return imageDataService.getImage(imageId);
}
I'm thinking that the config for static resources has a default entry that tells Spring security to ignore the "/images" path for the preauth filter. I'm debugging around trying to figure out where that might be overridden.
SpringBoot by default use some paths
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
"classpath:/META-INF/resources/", "classpath:/resources/",
"classpath:/static/", "classpath:/public/" };
https://spring.io/blog/2013/12/19/serving-static-web-content-with-spring-boot
And one of this paths is /images
Java Web Application. Spring Boot. Locating Images
Also you have the following restrictions when usind SpringSecurity
The basic features you get out of the box in a web application are:
An AuthenticationManager bean with in-memory store and a single user
(see SecurityProperties.User for the properties of the user). Ignored
(insecure) paths for common static resource locations (/css/,
/js/, /images/, /webjars/ and **/favicon.ico). HTTP Basic
security for all other endpoints. Security events published to
Spring’s ApplicationEventPublisher (successful and unsuccessful
authentication and access denied).
http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/
Common low-level features (HSTS, XSS, CSRF, caching) provided by Spring Security are on by default.
You need to ensure, that security is done for every request. This can be done using the following SecurityConfiguration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated();
}
}

Categories

Resources