I need to create "Remember me"-provided REST service. My app should receive JSON with login data, authenticate user and make it possible for app to remember the user. I've written some code snippet with few mocked #RequestMapping's and simple Spring security config, and, however, application authenticates user (because of successfulAuthentication() Filter's method invocation). But when I'm trying to send request to the secured url even after login action, it returns 401 code. I know, this is quite obvious that new request creates a new session, but is there any way to perform "remember me" behaviour without sending login info in each Request's body? Here is some of my code:
package com.checkpoint.aimer.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
public class RestSecurityFilter extends AbstractAuthenticationProcessingFilter{
public RestSecurityFilter(String url, AuthenticationManager m) {
super(url);
this.setAuthenticationManager(m);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException,
IOException, ServletException {
HttpSession session = request.getSession();
Authentication auth = this.getAuthenticationManager().authenticate(new UsernamePasswordAuthenticationToken("roman", "sawawluha"));
SecurityContextHolder.getContext().setAuthentication(auth);
return auth;
}
}
Security configuration:
package com.checkpoint.aimer.security;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public RestSecurityFilter restSecurity() throws Exception {
RestSecurityFilter filter = new RestSecurityFilter("/auth/login_test", authenticationManagerBean());
return filter;
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public UserDetailsService usr() {
return new UserSecurityService();
}
#Override
public void configure(HttpSecurity http) throws Exception{
http
.httpBasic().authenticationEntryPoint(new AuthenticationEntryPoint() {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException arg2) throws IOException, ServletException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Oops");
}
}).and()
.addFilterBefore(restSecurity(),BasicAuthenticationFilter.class )
.rememberMe().rememberMeServices(new TokenBasedRememberMeServices("key",usr()) ).and()
.authorizeRequests()
.antMatchers("/**").hasRole("USER")
.antMatchers("/auth/**").anonymous()
.and()
.csrf().disable()
.logout().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception{
auth
.userDetailsService(usr());
}
}
Do you have any ideas?
Remember me features are mostly implemented based on cookies. You can store some authentication token into cookie. I believe you can even use Session Cookies for this.
BUT REMEMBER:
You have to use HTTPS all the time
Use HttpOnly cookie attribute all the time
Use Secure cookie attribute all the time
Because cookie is send to client with every request, you need to make sure it's send via secure channel, and not accessible to cross site attacks.
Related
i have so much trouble to identify why Spring security is not working on my project.
There is my code :
import com.corp.myproject.config.security.filters.JwtAuthorizationFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(new JwtAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.formLogin().loginPage("http://localhost:8081/login/openidProvider");
http.authorizeRequests().anyRequest().authenticated();
http.csrf().disable();
}
}
But I can access to my backend without any authentication.
The expected result is :
If a user don't have a authorization jwt, Spring security redirects him to the custom login url to login in with Openid
When the user logs in, he is redirected to my backend with a jwt
Every requests to my backend will be filtred by my JwtAuthorizationFilter
EDIT : Adding the JwtAuthorizationFilter's code :
import com.corp.myproject.config.security.JwtTokenUtil;
import com.corp.myproject.config.security.UserFromToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
public class JwtAuthorizationFilter extends OncePerRequestFilter {
JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authorizationToken = request.getHeader("Authorization");
if (authorizationToken != null && authorizationToken.startsWith("Bearer ")) {
String jwt = authorizationToken.substring(7); // Ignore 'Bearer '
String error = "";
if (!jwtTokenUtil.isTokenIntegritySafe(jwt)) {
error = "Token jwt is not valid";
}
if (!jwtTokenUtil.validateToken(jwt)) {
error = "Token jwt is expired";
}
UserFromToken userFromToken = jwtTokenUtil.getUserFromToken(jwt);
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (String r : userFromToken.getRoles()) {
authorities.add(new SimpleGrantedAuthority(r));
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userFromToken.getName(), null, authorities);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
if (error == "") {
filterChain.doFilter(request, response);
} else {
response.setHeader("error-message", error);
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
} else {
response.setHeader("error-message", "User not authorized");
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
}
I am currently working on a spring boot application, and I've got a handler which takes the HttpServletRequest as an argument.
I was wondering, is it possible to invoke a bean that - provided the session cookie - can return the information of who made the request? (e.g. username)
In the end I found this way to make the code work in the desired way.
package org.my.package;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.csrf.InvalidCsrfTokenException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.springframework.security.web.context.HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY;
#Slf4j
#Configuration
public class CsrfDeniedHandlerConfig {
#AllArgsConstructor
static class CsrfDeniedHandler implements AccessDeniedHandler {
private final AccessDeniedHandler accessDeniedHandler;
#Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
if (accessDeniedException.getClass().equals(InvalidCsrfTokenException.class)) {
SecurityContextImpl securityContext = (SecurityContextImpl) request.getSession().getAttribute(SPRING_SECURITY_CONTEXT_KEY);
User user = (User) securityContext.getAuthentication().getPrincipal();
log.error("Invalid CSRF token request from {}: {}", user.getUsername().toUpperCase(), accessDeniedException.getMessage());
}
accessDeniedHandler.handle(request, response, accessDeniedException);
}
}
#Bean
public AccessDeniedHandler csrfDeniedHandler() {
return new CsrfDeniedHandler(new AccessDeniedHandlerImpl());
}
}
I have a problem with the config about spring-security. So I have made some configuration so far, and I am able to use all API with **GET. But none of the rest API like Delete-PUT-Post.
And for this I am getting an error like below:
The error is 403.
So my configuration is in two classes :
CorsFilter.java
package com.example.rest.webservices.restfulwebservices.basic.auth;
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;
public class CorsFilter implements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException
{
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request = (HttpServletRequest) servletRequest;
// if (request.getMethod().equals("OPTIONS"))
// {
response.setHeader("Access-Control-Allow-Origin", "http://localhost:4200");
response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"Content-Type, Authorization, Content-Length, X-Requested-With");
response.addHeader("Access-Control-Allow-Credentials", "true");
// }
filterChain.doFilter(servletRequest, servletResponse);
}
#Override
public void destroy()
{
}
}
and the second class is :
SpringSecurityConfigurationBasicAuth
package com.example.rest.webservices.restfulwebservices.basic.auth;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.session.SessionManagementFilter;
#Configuration
#EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
#Bean
CorsFilter corsFilter() {
CorsFilter filter = new CorsFilter();
return filter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(corsFilter(), SessionManagementFilter.class);
//http.cors();
http .csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
http.authorizeRequests()
.antMatchers("/**").permitAll()
.anyRequest().authenticated()
.and()
// .formLogin().and()
.httpBasic();
}
}
My controller is as below:
ItemController
package com.example.rest.webservices.restfulwebservices.todo;
import java.net.URI;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
#CrossOrigin(origins="http://localhost:4200")
#RestController
public class ItemController {
#Autowired
private ItemService itemService;
#GetMapping(path = "/users/{username}/items")
public List<Item> getAllToDosList(#PathVariable String username){
return itemService.findAll(username);
}
#GetMapping(path = "/users/{username}/item/{id}")
public Item getItem(#PathVariable String username, #PathVariable Integer id){
return itemService.findById(id);
}
#PutMapping("/users/{username}/item/{id}")
public ResponseEntity<Item> updateItem(#PathVariable String username,
#PathVariable Integer id, #RequestBody Item item ){
Item updateditem = itemService.saveItem(item);
return new ResponseEntity<Item>(updateditem, HttpStatus.OK);
}
#PostMapping("/users/{username}/item")
public ResponseEntity<Void> addItem(#PathVariable String username, #RequestBody Item item ){
Item createdItem = itemService.saveItem(item);
URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(createdItem.getId()).toUri();
return ResponseEntity.created(uri).build();
}
#DeleteMapping(path = "/users/{username}/item/{id}")
public ResponseEntity<Void> removeToDosFromList(#PathVariable String username,
#PathVariable Integer id){
Item todo = itemService.deleteToDoById(id);
if (todo != null)
{
return ResponseEntity.noContent().build();
}
return ResponseEntity.notFound().build();
}
}
And so far this work only for the GET API, Please have a look in the classes, maybe you have more ideas than me, as I have a lack of experience.
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
You are passing OPTIONS method in the argument which is for specifying which method to allow, only OPTIONS type request will be allowed, use GET if you want to allow get requests. If you want to allow all request types just pass "/**" as the argument, with out specifying any method type.
I think the question here is:
do you want to autenticate the user to call some of the paths?
If the answer is no, you should remove this line .anyRequest().authenticated()
if the answer is yes you should specify the urls you want to be authenticated and define the authentication method and make the call with a proper Authorization header
I'm am trying to set up an application that has an Angular front-end and a Maven/Spring Boot backend and have set up my first REST controller. My issue is that when I send a GET HTTP request to the backend from my Angular IDE it returns an error stating:
"Access to XMLHttpRequest at 'http://localhost:8080/api/getData' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource."
I'm confused as I have set up the "doFilter" to accept all requests from any origin so it shouldn't be throwing this error. My code follows:
My APIController:
package com.SSCCoursework.controller;
import com.SSCCoursework.Model.SharePrice;
import com.SSCCoursework.Model.Shares;
import com.SSCCoursework.Model.SharesList;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#CrossOrigin (origins = "http://localhost:4200", maxAge = 3600)
#RestController
public class ApiController
{
File Shares_File = new File("Shares_Data.xml");
ArrayList<Shares> shareList = new ArrayList<Shares>();
#RequestMapping(value="/api/getData", produces="application/JSON")
public Object getData()
{
Shares share1 = new Shares();
SharePrice share1_2 = new SharePrice();
share1.setCompanyName("test");
share1.setCompanySymbol("test");
share1.setNumOfShares(123);
Date date = new Date();
share1.setLastShareUpdate(date);
share1_2.setCurrency("dollar");
share1_2.setValue(12345f);
share1.setSharePrice(share1_2);
shareList.add(share1);
SharesList sharelist = new SharesList();
sharelist.setBookList(shareList);
return share1;
}
}
My SimpleCORSFilter:
package com.SSCCoursework.Security;
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.springframework.stereotype.Component;
#Component
public class SimpleCORSFilter implements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
HttpServletResponse res = (HttpServletResponse) response;
HttpServletRequest req = (HttpServletRequest) request;
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Accept-Encoding, Accept-Language, Host, Referer, Connection, User-Agent, authorization, sw-useragent, sw-version");
if (req.getMethod().equals("OPTIONS"))
{
res.setStatus(HttpServletResponse.SC_OK);
return;
}
filterChain.doFilter(request, response);
}
#Override
public void destroy()
{
}
}
My Angular code is just trying to use a GET method (this.httpClient.get('http://localhost:8080/api/getData') to print the data to the browser console but the error is preventing it from working. Am I missing a step in my backend?
you can easily define a global cors config just by adding in your main application class where you start your spring boot app
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080")
.allowedMethods("PUT", "DELETE", "GET", "POST");
}
};
}
for more details take a look here
I wrote a custom Failure Handler in my Spring app
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler{
#Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
DefaultRedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
request.setAttribute("X-Http-Method-Override", "POST");
response.setHeader("X-Http-Method-Override", "POST");
redirectStrategy.sendRedirect(request, response, "/login");
}
}
What i'm trying to do is : in case of the failure login, i want to redirect to another page /login but with the POST method not GET method.
I tried to add attribute and setHeader but couldn't get what i want.
No!!! you cant't have a post redirect without a hack. for reference you can have a look at
https://softwareengineering.stackexchange.com/questions/99894/why-doesnt-http-have-post-redirect