I have a controller to upload a user's avatar that has been working for quite a while. Until recently, after I made a change to my spring security configuration. The change was to have unauthorized calls to API's return 403 forbidden and unauthorized calls to anything else redirect to login. Since making this change the app throws a 403 every time a call is made to upload an avatar. Every other API works as intended.
Here are the snippets I believe are relevant to the issue at hand:
Controller:
#Controller
#RequestMapping("/api/users")
public class UsersController {
#RequestMapping(value = "/upload_avatar", params = { "filename" }, method = RequestMethod.POST)
public #ResponseBody ResponseStatusDTO handleFileUpload(
#RequestParam("file") MultipartFile file,
#RequestParam(value = "filename") String filename) {
if (!file.isEmpty()) {
try {
String newFilename = userUtil.uploadAvatar(file, filename);
return new ResponseStatusDTO(1, newFilename);
} catch (Exception e) {
return new ResponseStatusDTO(1, "Failed to upload " + filename
+ "!");
}
} else {
return new ResponseStatusDTO(1, "Failed to upload " + filename
+ " because the file was empty.");
}
}
}
Ajax Call Performing Request:
uploadAvatar : function(){
var file = this.getSelectedFile();
var data = new FormData();
data.append('file', file);
var name = file.name;
$.ajax({
url: './api/users/upload_avatar?filename='+ name,
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: _.bind(function(data){
this.avatar = data.message;
}, this),
error: _.bind(function(data){
//TODO
}, this)
});
}
Latest Spring Security Configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Autowired
private CommonAuthenticationProvider authProvider;
#Autowired
AuthFailureHandler authFailureHandler;
#Autowired
AuthSuccessHandler authSuccessHandler;
#Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authProvider);
}
#Configuration
#Order(1)
public static class ApiLoginWebSecurityConfigurationAdapter extends
WebSecurityConfigurerAdapter {
#Autowired
private Http403ForbiddenEntryPoint forbiddenEntryPoint;
#Bean
public Http403ForbiddenEntryPoint forbiddenEntryPoint() {
return new Http403ForbiddenEntryPoint();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic()
.authenticationEntryPoint(forbiddenEntryPoint);
// #formatter:on
}
}
#Configuration
public static class FormLoginWebSecurityConfigurationAdapter extends
WebSecurityConfigurerAdapter {
#Autowired
AuthFailureHandler authFailureHandler;
#Autowired
AuthSuccessHandler authSuccessHandler;
#Autowired
private LoginUrlAuthenticationEntryPoint loginEntryPoint;
#Bean
public LoginUrlAuthenticationEntryPoint loginEntryPoint() {
return new LoginUrlAuthenticationEntryPoint("/login");
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/webjars/**",
"/login/**", "/session/**", "/public/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("username")
.passwordParameter("password")
.failureHandler(authFailureHandler)
.successHandler(authSuccessHandler)
.permitAll()
.and()
.logout()
.logoutUrl("/j_spring_security_logout")
.logoutSuccessUrl("/login")
.invalidateHttpSession(true)
// .deleteCookies(cookieNamesToClear)
.and()
.httpBasic().authenticationEntryPoint(loginEntryPoint)
.and()
.csrf().disable();
// #formatter:on
}
}
}
Finally found the issue after several hours. In the new api security configuration I didn't disable csrf and wasn't sending a token. Once I disabled csrf it worked as expected.
#Configuration
#Order(1)
public static class ApiLoginWebSecurityConfigurationAdapter extends
WebSecurityConfigurerAdapter {
#Autowired
private Http403ForbiddenEntryPoint forbiddenEntryPoint;
#Bean
public Http403ForbiddenEntryPoint forbiddenEntryPoint() {
return new Http403ForbiddenEntryPoint();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.httpBasic()
.authenticationEntryPoint(forbiddenEntryPoint)
.and()
.csrf().disable();
// #formatter:on
}
}
Related
I am programing a Springboot api rest but i have a problem with Spring security.
When i want to Make a request to the server , it throws Unauthorized 401 but i have already configured spring security. Here is the code:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.GET, "/characters/**").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
#Bean
protected UserDetailsService userDetailsService() {
UserDetails admin= User.builder().username("admin").password(passwordEncoder().encode("123")).roles("ADMIN")
.build();
UserDetails user= User.builder().username("user").password(passwordEncoder().encode("123")).roles("USER")
.build();
return new InMemoryUserDetailsManager(admin,user);
}
}
Request method:
#PreAuthorize("hasRole('ADMIN')")
#RequestMapping(value ="/characters" ,method = RequestMethod.GET)
public List<ImagenNombreDTO> listarPersonajes(){
try {
return personajeService.listarPersonajes();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Make following changes inside antmatchers() method of SecurityConfig.java file.
Add one more entry of "/characters" endpoint like following and see whether the error still persists or not.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.GET, "/characters","/characters/**").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
I have a Spring Boot application with this mappging:
#GetMapping(value = { "/", })
public String home(Model model) {
}
and
localhot:8080,
localhost:8080/ ,
localhost:8080/.,
localhost:8080/..
redirects to / but not
localhost:8080/...
and in the WebSecurityConfig the only public matcher I have is this one: /.
I would like to restrict the access for localhost:8080/. and localhost:8080/..
here:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final UserSecurityService userSecurityService;
private final Environment env;
private static final String SALT = "fd&l23j§sfs23#$1*(_)nof";
public WebSecurityConfig(UserSecurityService userSecurityService, Environment env) {
this.userSecurityService = userSecurityService;
this.env = env;
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12, new SecureRandom(SALT.getBytes()));
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
encodingFilter.setEncoding("UTF-8");
encodingFilter.setForceEncoding(true);
http.addFilterBefore(encodingFilter, CsrfFilter.class);
http.csrf().disable();
http
.authorizeRequests()
.antMatchers(publicMatchers()).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.defaultSuccessUrl("/advertise.html")
.failureUrl("/login.html?error").permitAll()
.and()
.logout()
.permitAll()
.and()
.rememberMe()
.key("uniqueAndSecret");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userSecurityService)
.passwordEncoder(passwordEncoder());
}
private String[] publicMatchers() {
final String[] PUBLIC_MATCHERS = {
"/webjars/**",
"/css/**",
"/fonts/**",
"/images/**",
"/img/**",
"/js/**",
"/home.html",
"/links/**",
"/links.html",
"/favicon.ico",
"/forgotmypassword.html",
"/directory/**",
"/",
"/error/**/*",
"/h2-console/**",
ForgotMyPasswordController.FORGOT_PASSWORD_URL_MAPPING,
ForgotMyPasswordController.CHANGE_PASSWORD_PATH
};
return PUBLIC_MATCHERS;
}
}
I have made a simple example similar to yours. I am testing with curl (not a web browser) and this is the result:
localhost:8080/. Internal server error.
This exception is thrown in the server: org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL was not normalized.
localhost:8080/.. Bad request
It seems that the embedded tomcat gives you that response. I tried adding a Global error controller and I could get the error in spring
localhost:8080/... Endpoint not found
This is exepcted as I don't have any mapping for such endpoint "/..."
I think that your browser is actually requesting for localhost:8080/ when you type localhost:8080/. or localhost:8080/.. Your spring boot app is not redirecting
I'm trying to add an other URL to log in my application for a mobile device. Because I want a custom response from server for mobile application. For example, I don't want an HTML in response but only code error.
My problem is my second URL for login: /loginMobile return code 405.
My code:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MultiHttpSecurityConfig {
#Configuration
#Order(1)
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/chat/**").permitAll()
.antMatchers("/service/boite_idee_note/**", "/service/atelier_vote/**", "/service/equipe/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPER_ADMIN')")
.antMatchers("/", "/Repository/**", "/uploadFile", "/uploadPdf", "/uploadLogo","/upload_nouveau_venu", "/service/**").access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN') or hasRole('ROLE_SUPER_ADMIN')")
.and()
.formLogin()
.loginPage("/login")
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf()
.ignoringAntMatchers("/chat/**")
.csrfTokenRepository(this.csrfTokenRepository());
}
/**
* #return
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
#Configuration
#Order(2)
public static class WebSecurityConfigAppMobile extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/loginMobile")
.and()
.logout()
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf()
.csrfTokenRepository(this.csrfTokenRepository());
}
/**
* #return
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
}
.authorizeRequests()
Was catching all requests so my request did'nt go in second Adapter.
Thx for help :)
I have following websocket security configuration:
#Configuration
public class WebSocketAuthorizationSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(final MessageSecurityMetadataSourceRegistry messages) {
// You can customize your authorization mapping here.
//messages.anyMessage().authenticated();
messages.simpDestMatchers("/hello").hasRole("ADMIN");
}
// TODO: For test purpose (and simplicity) i disabled CSRF, but you should re-enable this and provide a CRSF endpoint.
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
I expect that only admin can send messages into the /hello topic.
and following security configuration:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String SECURE_ADMIN_PASSWORD = "rockandroll";
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.formLogin()
.loginPage("/index.html")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/sender.html")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/index.html")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/js/**", "/lib/**", "/images/**", "/css/**", "/index.html", "/","/*.css","/webjars/**", "/*.js").permitAll()
.antMatchers("/websocket").hasRole("ADMIN")
.requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN")
.anyRequest().authenticated();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new AuthenticationProvider() {
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
List<GrantedAuthority> authorities = SECURE_ADMIN_PASSWORD.equals(token.getCredentials()) ?
AuthorityUtils.createAuthorityList("ROLE_ADMIN") : null;
return new UsernamePasswordAuthenticationToken(token.getName(), token.getCredentials(), authorities);
}
});
}
}
Also I have following websocket controller:
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(#Payload HelloMessage message, Principal principal) throws Exception {
Thread.sleep(1000); // simulated delay
simpMessagingTemplate.convertAndSendToUser(principal.getName(), "/queue/greetings", new Greeting("Ololo"));
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
Next I login as user/pass(It is not admin).
And client successfully sends messages to the /hello topic:
stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
and method greetinginvokes successfully.
What do I wrong?
It became working after I had provided following definition(added /app):
messages.simpDestMatchers("/app/hello").hasRole("ADMIN");
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER");
}
}
My JS FILE:
$scope.del = function (record) {
if (confirm('Do you really want to delete?')){
$http['delete']('/camera/list/' + record.filename).then(function() {
$scope.records.splice($scope.records.indexOf(record), 1);
});
}
};
My delete controller:
#RequestMapping(value = "/list/{fn}", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Record> deleteUser(#PathVariable("fn") String filename) {
System.out.println("Fetching & Deleting data " + filename);
Record user1 = rep.findByfilename(filename);
if (user1 == null) {
System.out.println("Unable to delete." + filename + " not found");
return new ResponseEntity<Record>(HttpStatus.NOT_FOUND);
}
rep.deleteByfilename(filename);
return new ResponseEntity<Record>(HttpStatus.NO_CONTENT);
}
}
My Repository:
public interface RecordRepository extends MongoRepository<Record, String> {
#Query("{ 'filename' : ?0 }")
Record findByfilename(String filename);
long deleteByfilename(String filename);
}
When I click on delete button, it shows me this error:
DELETE
XHR
http://localhost:8086/camera/list/2fb1a2e020285cd91dc68a4fa7822151 [HTTP/1.1 403 Forbidden 14ms]
Anybody know what is the error? At first my delete worked but when I used spring security my delete is not working.
You need to review your spring security config:
http.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
When you say anyRequest().authenticated(), it means that all requests should be authenticated.
If you want to allow camera/list to be called without authentication add it to permitAll()