I am trying to call REST endpoints on one application (spring-boot application) from another (angularjs). The applications are running on the following hosts and ports.
http://localhost:8080
http://localhost:50029
I am also using spring-security
with the spring-boot application. From the HTML application, I can authenticate to the REST application, but, thereafter, I still cannot access any REST endpoint. For example, I have an angularjs service defined as follows.
adminServices.factory('AdminService', ['$resource', '$http', 'conf', function($resource, $http, conf) { var s = {}; s.isAdminLoggedIn = function(data) { return $http({ method: 'GET', url: 'http://localhost:8080/api/admin/isloggedin', withCredentials: true, headers: { 'X-Requested-With': 'XMLHttpRequest' } }); }; s.login = function(username, password) { var u = 'username=' + encodeURI(username); var p = 'password=' + encodeURI(password); var r = 'remember_me=1'; var data = u + '&' + p + '&' + r; return $http({ method: 'POST', url: 'http://localhost:8080/login', data: data, headers: {'Content-Type': 'application/x-www-form-urlencoded'} }); }; return s; }]);
The angularjs controller looks like the following.
adminControllers.controller('LoginController', ['$scope', '$http', 'AdminService', function($scope, $http, AdminService) { $scope.username = ''; $scope.password = ''; $scope.signIn = function() { AdminService.login($scope.username, $scope.password) .success(function(d,s) { if(d['success']) { console.log('ok authenticated, call another REST endpoint'); AdminService.isAdminLoggedIn() .success(function(d,s) { console.log('i can access a protected REST endpoint after logging in'); }) .error(function(d, s) { console.log('huh, error checking to see if admin is logged in'); $scope.reset(); }); } else { console.log('bad credentials?'); } }) .error(function(d, s) { console.log('huh, error happened!'); }); }; }]);
On the call to http://localhost:8080/api/admin/isloggedin
, I get a 401 Unauthorized
.
On the REST application side, I have a CORS filter that looks like the following.
@Component @Order(Ordered.HIGHEST_PRECEDENCE) public class CORSFilter implements Filter { @Override public void destroy() { } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; response.setHeader("Access-Control-Allow-Origin", "http://localhost:50029"); response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, X-Auth-Token"); response.setHeader("Access-Control-Allow-Credentials", "true"); if(!"OPTIONS".equalsIgnoreCase(request.getMethod())) { chain.doFilter(req, res); } } @Override public void init(FilterConfig config) throws ServletException { } }
My spring security configuration looks like the following.
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint; @Autowired private JsonAuthSuccessHandler jsonAuthSuccessHandler; @Autowired private JsonAuthFailureHandler jsonAuthFailureHandler; @Autowired private JsonLogoutSuccessHandler jsonLogoutSuccessHandler; @Autowired private AuthenticationProvider authenticationProvider; @Autowired private UserDetailsService userDetailsService; @Autowired private PersistentTokenRepository persistentTokenRepository; @Value("${rememberme.key}") private String rememberMeKey; @Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable() .exceptionHandling() .authenticationEntryPoint(restAuthenticationEntryPoint) .and() .authorizeRequests() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/", "/admin", "/css/**", "/js/**", "/fonts/**", "/api/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .successHandler(jsonAuthSuccessHandler) .failureHandler(jsonAuthFailureHandler) .permitAll() .and() .logout() .deleteCookies("remember-me", "JSESSIONID") .logoutSuccessHandler(jsonLogoutSuccessHandler) .permitAll() .and() .rememberMe() .userDetailsService(userDetailsService) .tokenRepository(persistentTokenRepository) .rememberMeCookieName("REMEMBER_ME") .rememberMeParameter("remember_me") .tokenValiditySeconds(1209600) .useSecureCookie(false) .key(rememberMeKey); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .authenticationProvider(authenticationProvider); } }
All the handlers are doing is writing out a JSON response like {success: true}
based on if the user logged in, failed to authenticate, or logged out. The RestAuthenticationEntryPoint
looks like the following.
@Component public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException ex) throws IOException, ServletException { resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); } }
Any ideas on what I am missing or doing wrong?
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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class SimpleCORSFilter implements Filter { private final Logger log = LoggerFactory.getLogger(SimpleCORSFilter.class); public SimpleCORSFilter() { log.info("SimpleCORSFilter init"); } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin")); response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me"); chain.doFilter(req, res); } @Override public void init(FilterConfig filterConfig) { } @Override public void destroy() { } }
No need extra define this filter just add this class. Spring will be scan and add it for you. SimpleCORSFilter. Here is the example: spring-enable-cors
I had been into the similar situation. After doing research and testing, here is my findings:
With Spring Boot, the recommended way to enable global CORS is to declare within Spring MVC and combined with fine-grained @CrossOrigin
configuration as:
@Configuration public class CorsConfig { @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedMethods("GET", "POST", "PUT", "DELETE").allowedOrigins("*") .allowedHeaders("*"); } }; } }
Now, since you are using Spring Security, you have to enable CORS at Spring Security level as well to allow it to leverage the configuration defined at Spring MVC level as:
@EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and()... } }
Here is very excellent tutorial explaining CORS support in Spring MVC framework.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With