I want to call a web service on the server using jQuery Ajax from a web page, it returns 302 (redirect the request to access-denied page). I am adding my code below:
jQuery Code:
$.ajax({
type: "POST",
contentType: "application/json",
url: "/posts",
data: JSON.stringify(obj),
success: function (data) {
},
error: function (e) {
}
});
WebSecurityConfigurerAdapter Code
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/",
"/css/**",
"/js/**").permitAll()
.antMatchers("/user/**").hasRole("ROLE_ADMIN")
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
and my accessDeniedHandler looks like this:
#Component
public class LoggingAccessDeniedHandler implements AccessDeniedHandler {
#Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException ex) throws IOException, ServletException {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
// auth has a value
if (auth != null) {
// log
}
response.sendRedirect(request.getContextPath() + "/access-denied");
}
}
I don't know what I'm doing wrong, Why is my Ajax request goes to AccessDeniedHandler?
Try disabling the csrf token in protected void configure(HttpSecurity http) this method like below.
http.csrf().disable()
You can refer this thread for more clarification.
Related
I have secured my application using oauth2 standard flow with amazon cognito. It works fine.
Sometimes i want to link to my webapp through my mobile app, and i dont want the user to sign in again. How do i create an #AnonymousAllowed endpoint that takes in an access token, authenticates using spring security and redirects to the homepage of my application?
I've tried creating an endpoint that returns the jsessionid, then another anonymous endpoint that accepts a jsessionid, sets the cookie and redirects to the homepage, but it didnt work.
I've tried setting the authorization header to the access token. But i feel like that wont work properly because the token will expire after few minutes, and spring wont handle authorization for me, rather i have to do it "manually"
I don't know if this can help in your situation. But you can check if anything you can refer from below. It's a custom filter with custom token validator.
You can use Custom TokenAuthenticationFilter by extending AbstractAuthenticationProcessingFilter
public class CustomTokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
#Autowired
private TokenValidatorService tokenValidatorService;
public CustomTokenAuthenticationFilter(final RequestMatcher requiresAuth) {
super(requiresAuth);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String token= httpServletRequest.getHeader(CommonConstants.OAUTH_HEADER_KEY_AUTHORIZATION);
//Get the custom header
//Validate token using custom validator based on header value
Optional<OAuthResponseData> oauthResponseData = tokenValidatorService.validateAccessToken(token, provider);
...
Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(oauthResponseData.get(), oauthResponseData.get().getOauthToken());
return getAuthenticationManager().authenticate(requestAuthentication);
}
#Override
protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException {
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
In TokenValidatorService you can implement OAuth2TokenValidator to validate the token
Spring Security config class:
public class SecurityConfig extends GlobalAuthenticationConfigurerAdapter {
#Configuration
public static class CustomTokenSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
protected CustomTokenAuthenticationProvider customTokenAuthenticationProvider;
#Override
public void configure(final WebSecurity webSecurity) {
webSecurity.ignoring()
...
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.and()
.addFilterBefore(tokenAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS).authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
public CustomTokenAuthenticationFilter tokenAuthenticationFilter() throws Exception {
final CustomTokenAuthenticationFilter filter = new CustomTokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
// filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customTokenAuthenticationProvider);
}
}
}
The server did not allow 'credentials', but the client sent the DELETE request with 'credentials : init' in the http request header.
The deletion logic ran when the data was present in the DB, but the browser actually received a CORS response. A 404 stats response was received when the data did not exist in the DB.
I think we should check whether CORS is possible first and then execute the actual DELETE logic. However, the server informed us that CORS is not possible after executing DELETE logic.
Why does the server receive a request, but the client receives a CORS response?
My Security settings code is as follows:
#Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("OPTIONS", "HEAD", "GET", "POST", "PUT", "PATCH", "DELETE")
.allowedHeaders("*");
}
}
The following is the Security Config:
#RequiredArgsConstructor
#EnableWebSecurity
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JwtProvider jwtProvider;
private final AuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.formLogin().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPointImpl());
http
.authorizeRequests()
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
...
add a class
#Order(-9999)
public class CorsFilter extends HttpFilter {
#Override
protected void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {
String origin = req.getHeader(HttpHeaders.ORIGIN);
if (StringUtils.hasLength(origin)){
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, x-requested-with, Content-Type, Accept, Authorization");
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
res.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, PUT, OPTIONS, DELETE");
res.addHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma");
res.addHeader(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "60");
}
super.doFilter(req, res, chain);
}
}
I'm trying to implement authentication and authorization using JWT token in SpringBoot REST API.
In my JWTAuthentication class
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res,
FilterChain chain, Authentication auth) throws IOException, ServletException {
String token = Jwts.builder().setSubject(((User) auth.getPrincipal()).getUsername())
.claim("roles", ((User) auth.getPrincipal()).getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET.getBytes()).compact();
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
chain.doFilter(req, res);
System.out.println("Token:"+token);
}
When I test my code by sending by posting the following message to 127.0.0.1:8080/login URL, I see that authentication is successful.
{"username":"admin", "password":"admin"}
And then Spring calls my JWT Authorization class
#Override
protected void doFilterInternal(
HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(SecurityConstants.HEADER_STRING);
if (header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
if (header == null) {
System.out.println("header null");
} else if (!header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
System.out.println("token prefix missing in header");
}
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
It prints the message: "token prefix missing in header"
Although I add the TOKEN_PREFIX in the successfulAuthentication method, it can not find it in the header in doFilterInternal method.
By the way, my security config is like this:
#EnableWebSecurity(debug = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired private UserDetailsService userDetailsService;
#Autowired private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.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(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
I checked the SpringBoot books but could not find a book that describes the inner details of the security framework. Since I did not understand how the framework works, I could not solve the problems by just looking at the blogs. Is there a book that you can suggest describing the details of SpringBoot Security?
Thanks
You set your token after you successfully authenticated the user to the header of
the Http response:
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
The internal JWT filter (from what I understand in your question is called after yours), looks in the Http headers of the request
String header = req.getHeader(SecurityConstants.HEADER_STRING);
and there they are not present.
In general, the second filter should not be active after you authenticated a user and should just return the JWT token to the client. Any subsequent call of the client should then include the JWT token in the Authorization header using Bearer: YourJWTToken for calling e.g. protected APIs.
I have application in SpringMVC with Spring Security. In spring security xml configuration file i enable csrf using <csrf/>. View in html work properly when i add csrf token to link in form but now i want add Controller and made some rest communication. Controller looks like this:
#Controller
public class RestControllerTest {
#RequestMapping(value = "/rest", method = RequestMethod.POST, produces = "application/json")
#ResponseStatus(value = HttpStatus.OK)
public #ResponseBody void setUser(#RequestBody User user){
System.out.println("Witaj " + user.getFirstname() + "!");
}
}
When i try to send JSon with user (using postman):
{
"firstname": "name",
"lastname": "name"
}
I get 404 not found status in postman and WARN : org.springframework.web.servlet.PageNotFound - Request method 'POST' not supported in STS. I know that I have to use csrf token but i don't have idea how to do this.
I have a similar working usecase as yours. This is how things look like in my case.
My Filter class -
public class MyCsrfFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain)
throws Exception {
CsrfToken csrf = (CsrfToken) request.getAttribute(org.springframework.security.web.csrf.CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath(YOUR_DOMAIN_NAME);
cookie.setHttpOnly(true);
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
Below is the security configuration in spring-boot. You can convert it to equivalent xml config file if you need one.
#Configuration
#EnableWebMvcSecurity
public class CustomWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.csrfTokenRepository(getTokenRepo())
.and()
.authorizeRequests()
.antMatchers("/login", "/login**").permitAll()
.and()
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
private CsrfTokenRepository getTokenRepo() {
HttpSessionCsrfTokenRepository repo = new HttpSessionCsrfTokenRepository();
repo.setHeaderName("X-XSRF-TOKEN");
return repo;
}
Hope this helps !
Hi,I build a project with spring boot & spring security. Now, I want to provider the login restfull service for Jquery.ajax({...}); And I want to:
process the login request from HTML page (like the <form> submit).
automatic to check session timeout when HTML page request, redirect timeout to login page.
process the login request from Ajax.
automatic to check the login state when Ajax request.
I coding like this
SecurityConfig
extends from WebSecurityConfigurerAdapter
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
String requestType = request.getHeader("x-requested-with");
if (!StringUtils.isEmpty(requestType)) {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().print("{\"invalid_session\": true}");
response.getWriter().flush();
} else {
response.sendRedirect("/security/login");
}
});
http.authorizeRequests()
.antMatchers("/security/**").permitAll()
.antMatchers("/reader/**").hasRole("READER")
.anyRequest().authenticated()
// session time out
.and().sessionManagement().invalidSessionUrl("/security/session_timeout")
.and().cors()
// login
.and()
.formLogin()
.successHandler(successHandler)
.failureHandler(faildHandler)
.loginPage("/security/login")
.permitAll()
// logout
.and()
.logout().permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(readerRepository::findOne);
}
I have two handlers to process AuthenticationSuccess and AuthenticationFailure.
FailureHandler
extends from SimpleUrlAuthenticationFailureHandler
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
System.out.println("Failed to auth.");
String requestType = request.getHeader("x-requested-with");
if (!StringUtils.isEmpty(requestType)) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().print("{\"success\": false}");
} else {
setDefaultFailureUrl("/security/login?error=true");
super.onAuthenticationFailure(request, response, exception);
}
}
SuccessHandler
extends from SavedRequestAwareAuthenticationSuccessHandler
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
System.out.println("Success to auth.");
String requestType = request.getHeader("x-requested-with");
if (!StringUtils.isEmpty(requestType)) {
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().print("{\"success\": true}");
} else {
setDefaultTargetUrl("/index/index");
setAlwaysUseDefaultTargetUrl(true);
super.onAuthenticationSuccess(request, response, authentication);
}
}
Controller
base RequestMapping is '/security'
#RequestMapping(value = "/login")
public String login(#RequestParam(value = "error", defaultValue = "false") boolean error, Model model) {
model.addAttribute("error", error);
return "login";
}
#RequestMapping("/session_timeout")
public void sessionTimeout(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("session was timeout.");
if (request.getHeader("x-requested-with") != null) {
// handler for ajax
response.getWriter().print("{\"sessionTimeout\": true}");
response.getWriter().close();
} else {
response.sendRedirect("login");
}
}
When I test in page(thymeleaf), All of worked.
but.. when I use Jquery Ajax.
The issure:
When I used Jquery.ajax({}) API to send the request, the request can not be get to server. How to write ajax request with jquery, I tried a lot of Jquery methods, page has no response code in console. Is the spring security not support ajax?
Thanks Fan, I fixed it. I rewrote the login authentication:
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, userReq.getPassword(), userDetails.getAuthorities());
authenticationManager.authenticate(token);
if (token.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(token);
return true;
}
Do auth with AuthenticationManager and it injected from spring.
If success to auth, I will return the sessionid to the client and client saved in cookie, when client do request, the client always stats sessionid at the end of the url requested by ajax.
If failed to auth, I wile return agreed error code.
eg:
$.ajax({
url: 'http://test:port/project/list;jsessionid=' + jessionid,
...
})
But I do not think so is good job like this. It's very troublesome, In the client,I need to check every response code is it right or not for every request. Is any better ways to solve this ensure??
by the way, the Client(Browser + Ajax) and Server(Spring mvc) are separate.