I have a problem using springboot-starter-security. I want to secure only urls that does not begin with "/api", all urls such as "/api" or "/api/" or "/api/**" must no be secured.
In WebSecurityConfigClass I have:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api*");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
In the application I have two different controllers, webcontroller and restcontroller. For the moment web isn't yet implemented but it must address urls that i want to be secured, the rest controller manage urls that i want not to need an authentication.
I have a test class for the rest controller, and all tests fails because they are expecting a 200 as status, but they receive 401. For example:
#Test
public void testStatus200() throws Exception{
mvc.perform(get("/api")).andExpect(status().isOk());
}
this test fails due to status is 401 and not 200. Why?
I solved, the test class did not use the websecurityconfig class.
I fixed by adding an explicit import of the WebSecurityConfig as class annotation:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers=ARestController.class)
#Import(WebSecurityConfig.class) // <---
public class ARestControllerTest { ... }
Related
Before implementing Spring Security, I could easily create a docker image on my server and access the REST-API when it ran on my server. Since I implemented the Spring Security, I get a '403 Forbidden' response on every path when I run the docker image on my server (via Jenkins and NGINX).
When I run the application locally, I can access all paths (except for the, by login, restricted ones). E.g. http://localhost:8080/movies/1 returns the movie with id 1 properly.
I followed this tutorial and tried to adjust most things to my own setup.
This is an example of one of my controllers, the MovieController:
#RestController
#RequestMapping("/movies")
public class MovieController {
final MovieRepository MR;
public MovieController(MovieRepository MR) {this.MR = MR;}
#RequestMapping("/all")
#ResponseBody
public List<Movie> getAllMovies() {
return MR.findAll();
}
#RequestMapping("/byId")
#ResponseBody
public Optional<Movie> getMovieById(#RequestParam int id) {
return MR.findById(id);
}
#RequestMapping("/byTitle")
#ResponseBody
public Movie getMovieByTitle(#RequestParam String title) {
return MR.findByTitle(title);
}
}
The following code is from my WebSecurity class:
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
public WebSecurity(UserDetailsServiceImpl userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.antMatchers("/movies/**").permitAll()
.antMatchers("/chairs/**").permitAll()
.anyRequest().authenticated()
.and().addFilter(new JWTAuthenticationFilter(authenticationManager())).addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
}
This is the SIGN_UP_URL constant used in the WebSecurity class:
public static final String SIGN_UP_URL = "/users/register";
As you can see, I should at least be able to visit the /movies/ paths on my website, but even those are forbidden (on the server).
I am using reverse proxy on my server, the {domain}/api/ routes to port 3001 on my server, which is rerouted to internal port 8080, where the API is running.
I think this is all the information needed for this problem, if you need any more information, please request it in the responses to this post.
Could anybody please tell me why I would get a '403 forbidden' response when I run it in docker, and how I might fix this?
I think that this is most probably a CORS issue.
Try adding:
http.cors().disable()
and check the result.
If it works, then you can try implementing CORS-enabled server.
I'm trying to build a microservice architecture. But i'm struggling since two days with OAuth2 and Zuul. I managed to run a Auth/Resource-Server on the same service, but not with Zuul. At the moment i'm swichting to another service, the authorization doesn't work anymore. I tried many guides (as an example Baeldung) but no one works for me. Probably because i'm using Spring Boot 2.0.3? Most guides using Spring Boot 1.5.x.
I think it's a problem of configuration. I'm using Eureka for service discovering, Zuul as Gateway and entry point. When the user request a protected service, he should be redirectet to my auth-service (OAuth2/JWT). The token he gets after login should be stored by Zuul (right ?). Actually Zuul doesn't get the token or doesn't store it. Do I have to do this by my own or should Zuul and OAuth manage this and I just have bad configurations? Could someone show me, how you configure this architecture or a new/working guide for Spring Boot 2.0.3? I'm actually really frustrated, need help. I'm new to Spring, but have to learn it for work. But at the moment i'm just overstrained.
Additional infos:
I didnt create any views now. I just defined some default controller which return Strings and are secured by #PreAuthorize.
Gateway-Service:
GatewayServiceApplication.java
#SpringBootApplication
#EnableZuulProxy
#EnableDiscoveryClient
#Configuration
public class GatewayServiceApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayServiceApplication.class, args);
}
}
SecurityConfig.java
#Configuration
#EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.httpBasic().disable()
.authorizeRequests()
.antMatchers("/", "/core/", "/core/login**", "/oauth/authorize",
"/core/oauth/authorize", "/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin().permitAll();
}
}
Here I had a lot of different antMatchers.
application.properties
server.port=8000
spring.application.name=gateway-service
eureka.client.service-url.defaultZone=http://localhost:8001/eureka/
security.oauth2.sso.login-path=http://localhost:8000/core/login
security.oauth2.client.client-id=zuul
security.oauth2.client.client-secret=zuul
security.oauth2.client.access-token-uri=http://localhost:8000/core/oauth/token
security.oauth2.client.user-authorization-uri=http://localhost:8000/core/oauth/authorize
#security.oauth2.resource.user-info-uri=http://localhost:8000/core/user/me
security.oauth2.resource.user-info-uri=http://localhost:8000/core/secured
spring.thymeleaf.cache=false
I think here's a failure.
Core-Service
CoreApplication.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableResourceServer
public class CoreApplication {
public static void main(String[] args) {
SpringApplication.run(CoreApplication.class, args);
}
}
AuthServerConfig
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Override
public void configure(
AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient("zuul")
.secret(passwordEncoder.encode("zuul"))
.authorizedGrantTypes("authorization_code")
.scopes("user_info")
.autoApprove(true)
.redirectUris("http://localhost:8000/core/secured");
}
}
SecurityConfig
#Configuration
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("john")
.password(passwordEncoder().encode("123"))
.roles("USER");
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
application.properties
server.port=8003
spring.application.name=core
eureka.client.service-url.defaultZone=http://localhost:8001/eureka
Ok, especially here I changed many things. So probably I destroyed much from older guides. (Sorry my english is bad!)
With Spring Boot 1.5.6.RELEASE I was able to send HTTP Status code 401 instead of 403 as described in How let spring security response unauthorized(http 401 code) if requesting uri without authentication, by doing this:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//...
http.exceptionHandling()
.authenticationEntryPoint(new Http401AuthenticationEntryPoint("myHeader"));
//...
}
}
using the org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint class.
I just upgraded to Spring Boot 2.0.0.RELEASE and found there is not such class any more (at least in that package).
Questions:
Does this class (Http401AuthenticationEntryPoint) exist yet in Spring Boot?
If no, what could be a good alternative for keeping the same behavior in an existing project in order to keep consistency with other implementations which depend on this status code (401) instead of 403?
Please notice this is different from Spring Security anonymous 401 instead of 403 because it's referring specifically to SpringBoot 2 (there are solutions in that post not applicable anymore in SpringBoot version 2 or others are not needed at all)
Heads up
By default Spring Boot 2 will return 401 when spring-boot-starter-security is added as a dependency and an unauthorized request is performed.
This may change if you place some custom configurations to modify the security mechanism behavior. If that's the case and you truly need to force the 401 status, then read the below original post.
Original Post
The class org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint was removed in favor of org.springframework.security.web.authentication.HttpStatusEntryPoint.
In my case the code would go like this:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
//...
http.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
//...
}
}
Bonus
If you need to return some information in the response body or customize the response somehow you can do something like this:
1- Extend AuthenticationEntryPoint
public class MyEntryPoint implements AuthenticationEntryPoint {
private final HttpStatus httpStatus;
private final Object responseBody;
public MyEntryPoint(HttpStatus httpStatus, Object responseBody) {
Assert.notNull(httpStatus, "httpStatus cannot be null");
Assert.notNull(responseBody, "responseBody cannot be null");
this.httpStatus = httpStatus;
this.responseBody = responseBody;
}
#Override
public final void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
response.setStatus(httpStatus.value());
try (PrintWriter writer = response.getWriter()) {
writer.print(new ObjectMapper().writeValueAsString(responseBody));
}
}
}
2- Provide an instance of MyEntryPoint to the security configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// customize your response body as needed
Map<String, String> responseBody = new HashMap<>();
responseBody.put("error", "unauthorized");
//...
http.exceptionHandling()
.authenticationEntryPoint(new MyEntryPoint(HttpStatus.UNAUTHORIZED, responseBody));
//...
}
}
Just to elaborate #lealceldeiro's answer:
Before Spring Boot 2 my Securiy Configuration class looked like this:
#Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {
#Bean
public Http401AuthenticationEntryPoint securityException401EntryPoint() {
return new Http401AuthenticationEntryPoint("Bearer realm=\"webrealm\"");
}
#Autowired
private Http401AuthenticationEntryPoint authEntrypoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
// some http configuration ...
// Spring Boot 1.5.x style
http.exceptionHandling().authenticationEntryPoint(authEntrypoint);
}
//...
}
And now in Spring Boot 2 it looks like this:
#Configuration
public class MyConfig extends WebSecurityConfigurerAdapter {
//Bean configuration for Http401AuthenticationEntryPoint can be removed
//Autowiring also removed
#Override
protected void configure(HttpSecurity http) throws Exception {
// some http configuration ...
// Spring Boot 2 style
http.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}
//...
}
See also this comment in Spring Boot Github Repo > PR Remove Http401AuthenticationEntryPoint.
Http401AuthenticationEntryPoint was removed.
See Spring Boot Github Repo > Issue #10715 (Remove Http401AuthenticationEntryPoint):
Remove Http401AuthenticationEntryPoint
rwinch commented on 20 Oct 2017
As far as I can tell it is not being used in the Spring Boot code base, so it might be good to remove Http401AuthenticationEntryPoint.
Depending on your requirements, you could use:
HttpStatusEntryPoint
BasicAuthenticationEntryPoint
For reactive (WebFlux) stack you can override the returned status code by adding such #Bean to catch some specific exceptions:
#Component
class MyErrorAttributes : DefaultErrorAttributes() {
override fun getErrorAttributes(
request: ServerRequest,
options: ErrorAttributeOptions
): MutableMap<String, Any> {
val cause = super.getError(request)
val errorAttributes = super.getErrorAttributes(request, options)
when (cause) {
is TokenExpiredException -> {
errorAttributes["status"] = HttpStatus.UNAUTHORIZED.value()
errorAttributes["error"] = HttpStatus.UNAUTHORIZED.reasonPhrase
}
}
return errorAttributes
}
}
You can customize your logic with overriding the class AuthenticationEntryPoint
this should be working:
#Component public class AuthEntryPointException implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -8970718410437077606L;
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.setStatus(HttpStatus.SC_UNAUTHORIZED);
response.setContentType("application/json");
response.getWriter().write("{\"result\":\"UNAUTHORIZED\",\"message\":\"UNAUTHORIZED or Invalid Token\"}");
}
}
I am trying to build Spring boot REST application with angularJS.
So, application is loading well, every JS and CSS files included. The problem is that when I am doing GET request, it goes right way, but when I am doing POST request it fails and doesn't try to call the controller method.
That's my Spring Boot Application class
#EnableAspectJAutoProxy(proxyTargetClass=true)
#SpringBootApplication(scanBasePackages = {"org.test.controllers", "org.test.services"})
#Import({ WebSecurityConfig.class, DBConfig.class, ViewConfig.class})
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
System.out.println("STARTING APP");
SpringApplication.run(Application.class, args);
}
}
And that's my controller class
#RestController
#RequestMapping("/tag")
public class TagController {
#Autowired
private TagService tagService;
#RequestMapping(method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public Iterable<Tag> getAllTags() {
return tagService.getAll();
}
#RequestMapping(method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public Tag saveTag(#RequestBody Tag tag) {
return tagService.save(tag);
}
}
So, when I am doing $http.get("/tag", success, error) it gives [], which means that controller was called.
And when I am doing $http.post("/tag", {name: 'name'}, success, error) it returns {"timestamp":1489939480282,"status":404,"error":"Not Found","message":"No message available","path":"/tag"}
To make sure that mapping was done, here's part of logs
Mapped "{[/tag],methods=[POST]}" onto public org.test.model.Tag org.expotest.controllers.TagController.saveTag(org.test.model.Tag)
I am running on Tomcat server if it matters.
Any ideas what could be wrong in my configuration? That seems really strange for me.
Thanks in advance.
If you are using Spring security try disabling csrf in your configure method, should look something like this:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable();
}
I am having some issues getting my application set up using method level annotation controlled by #EnableGlobalMethodSecurity I am using Servlet 3.0 style initialization using
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
public SecurityWebApplicationInitializer() {
super(MultiSecurityConfig.class);
}
}
I have attempted 2 different ways of initialising an AuthenticationManager both with their own issues. Please note that not using #EnableGlobalMethodSecurity results in a successful server start up and all of the form security executes as expected. My issues arise when I add #EnableGlobalMethodSecurity and #PreAuthorize("hasRole('ROLE_USER')") annotations on my controller.
I am attempting to set up form-based and api-based security independently. The method based annotations need only work for the api security.
One configuration was the following.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").httpBasic();
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and()
.formLogin().loginPage("/login").permitAll();
}
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
}
}
This is not ideal as I really want only a single registration of the authentication mechanism but the main issue is that it results in the following exception:
java.lang.IllegalArgumentException: Expecting to only find a single bean for type interface org.springframework.security.authentication.AuthenticationManager, but found []
As far as I am aware #EnableGlobalMethodSecurity sets up its own AuthenticationManager so I'm not sure what the problem is here.
The second configuration is as follows.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return new AuthenticationManagerBuilder(ObjectPostProcessor.QUIESCENT_POSTPROCESSOR)
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN").and()
.and()
.build();
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Override protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**").httpBasic();
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and()
.formLogin().loginPage("/login").permitAll();
}
}
}
This config actually starts successfully but with an exception
java.lang.IllegalArgumentException: A parent AuthenticationManager or a list of AuthenticationProviders is required
at org.springframework.security.authentication.ProviderManager.checkState(ProviderManager.java:117)
at org.springframework.security.authentication.ProviderManager.<init>(ProviderManager.java:106)
at org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder.performBuild(AuthenticationManagerBuilder.java:221)
and when I test I found that the security doesn't work.
I've been looking at this for a couple of days now and even after diving into spring security implementation code I can't seem to find what is wrong with my configuration.
I am using spring-security-3.2.0.RC1 and spring-framework-3.2.3.RELEASE.
When you use the protected registerAuthentication methods on WebSecurityConfigurerAdapter it is scoping the Authentication to that WebSecurityConfigurerAdapter so EnableGlobalMethodSecurity cannot find it. If you think about this...it makes sense since the method is protected.
The error you are seeing is actually a debug statement (note the level is DEBUG). The reason is that Spring Security will try a few different ways to automatically wire the Global Method Security. Specifically EnableGlobalMethodSecurity will try the following ways to try and get the AuthenticationManager:
If you extend GlobalMethodSecurityConfiguration and override the registerAuthentication it will use the AuthenticationManagerBuilder that was passed in. This allows for isolating the AuthenticationManager in the same way you can do so with WebSecurityConfigurerAdapter
Try to build from the global shared instance of AuthenticationManagerBuilder, if it fails it logs the error message you are seeing (Note the logs also state "This is ok for now, we will try using an AuthenticationManager directly")
Try to use an AuthenticationManager that is exposed as a bean.
For your code, you are going to be best off using something like the following:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class MultiSecurityConfig {
// Since MultiSecurityConfig does not extend GlobalMethodSecurityConfiguration and
// define an AuthenticationManager, it will try using the globally defined
// AuthenticationManagerBuilder to create one
// The #Enable*Security annotations create a global AuthenticationManagerBuilder
// that can optionally be used for creating an AuthenticationManager that is shared
// The key to using it is to use the #Autowired annotation
#Autowired
public void registerSharedAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
// Since we didn't specify an AuthenticationManager for this class,
// the global instance is used
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.httpBasic();
}
}
#Configuration
public static class FormWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
// Since we didn't specify an AuthenticationManager for this class,
// the global instance is used
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**","/status");
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
}
}
NOTE: More documentation around this will be getting added to the reference in the coming days.