How to secure spring boot data rest security based on logged in user? - spring

I have a spring data rest micro-service, with following entities
User
Laptop
Specs
User has list of laptops, and each one has it's own specs. Now the point is, user can log in through another micro-service, and get a JWT. Then, he creates a laptop for himself, and adds tech specs to the laptop.
Here's the call for adding the specs to the laptop
curl -i -X PUT http://localhost:8080/laptop/1/specs -H "Content-Type:text/uri-list" -d "http://localhost:8080/specs/1"
I can get my user's id from the JWT in my security filter, so how can I validate that the resource "belongs" to the user? How do I make sure that the user doesn't update other user's laptop specs? This is just the showcase, in fact number of models is around 30, so is there a dynamic solution to verify the user/resource association?
Here's the code of my security filter
public class JWTAuthenticationFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
Authentication authentication = JWTAuthenticationService.getAuthentication((HttpServletRequest) request);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}
JWT validator
public static Authentication getAuthentication(HttpServletRequest request) {
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
String authToken = authHeader != null ? authHeader.replace(TOKEN_PREFIX, "") : null;
if (authToken != null) {
Claims claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(authToken)
.getBody();
String username = claims.getSubject();
#SuppressWarnings("unchecked") final List<String> authoritiesClaim = (List<String>) claims.get(AUTHORITIES);
final List<SimpleGrantedAuthority> authorities = authoritiesClaim
.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
return username != null ?
new UsernamePasswordAuthenticationToken(username, null, authorities) :
null;
}
return null;
}
and web security configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}

Related

403 for all protected routes using spring security

I started a new spring 3.0.2 project with spring security and I'm trying to create a register/login rest API for now as I am a begineer.
I managed to get a this working but after when my user is authentified I have a 3rd controller that will display information. However, I am always getting 403 response.
I am using the JWT token library to manage request here are some piece of code of my project.
my configuration for filtering request as you can only 2 endpoints are free to visit and I want all the rest to be locked to authentificated users only.
private final Filter tokenAuthentificationFilter;
private final AuthenticationProvider authentificationProvider;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf()
.and()
.authorizeHttpRequests()
.requestMatchers("/api/v1/auth/**", "/api/v1/test-controller")
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authentificationProvider)
.addFilterBefore(tokenAuthentificationFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
if I'm not connected the /api/v1/auth and /test-controller works correctly but when I'm connected I have a "protected" endpoint /protected and it returns 403
below it's my tokenAuthentificationFilter class
#Component
#RequiredArgsConstructor
public class TokenAuthentificationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
#Override
protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain
) throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
String authToken;
String userEmail;
if(authorizationHeader == null || !authorizationHeader.startsWith("Bearer")){
filterChain.doFilter(request, response);
return;
}
authToken = authorizationHeader.substring(7);
userEmail = jwtService.extractEmail(authToken);// TODO extract userEmail from JWT Token;
if(userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
if(jwtService.isTokenValid(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
No log error in my console
code seems fine to me but this might not be the one causing problem, try putting in a few print statements inside your code. See the flow of requests from filters to endpoint. Then try hitting the protected endpoint, it should give you an idea where's the request is blocked. Then try to work from there, that's how i solved most of spring security problems.

Redirect to original URL after successful authentication in Spring Security

I have the following security configuration class in a Spring Cloud Gateway application. This gateway acts as an OAuth2 client handling the user authentication. After a successful authentication, I'd like to redirect to the URL of the single-page application where the user originally came from.
Example
If the user was on http://localhost:8093/profile then this should be the redirect URL.
Currently I only use a hardcoded value which works for testing purposes. Is there a way to get the "original URL" and use it as a redirection URL?
#Configuration
#EnableWebFluxSecurity
public class SecurityConfiguration {
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity httpSecurity) {
httpSecurity
.csrf().disable()
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2Login()
// Use original URL here?
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("http://localhost:8093"))
.and()
.exceptionHandling().authenticationEntryPoint(new HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.oauth2ResourceServer().jwt();
return httpSecurity.build();
}
}
You can try below provide the combination to Achieve what you are looking for:
First of all you need to create your Authentication Success Handler:
public class MySimpleUrlAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
protected Log logger = LogFactory.getLog(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
#Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
Then handle Method implementation:
protected void handle(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException {
//This will provide you last URL
String targetUrl = request.getHeader("referer");
if (response.isCommitted()) {
logger.debug(
"Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
Just an FYI:
Note: the HTTP referer is a client-controlled value and can thus be spoofed to something entirely different or even removed. This value should not be used for any critical operation.
Maybe it's too late, but I had the same problem like you. Has Jayesh said, you need to create a class "Authentication Success Handler" to add some logic and redirection after a sucessfull Oauth2 authentication.
But this new class , instead of extending SimpleUrlAuthenticationSucessHandler, must extends SavedRequestAwareAuthenticationSucessHandler and override the method onAuthenticationSucess().
public class OAuth2LoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
#Autowired
private UserService userService;
#Autowired
private MessageSource messageSource;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();
User existingUser = userService.findByUsername(oAuth2User.getEmail());
if (existingUser != null) {
// update of user with providerId and authenticationProvider if not already done
log.info(messageSource.getMessage("global.existing-user.oauth2-authenticated",
new Object[] { existingUser }, LocaleContextHolder.getLocale()));
if (existingUser.getAuthenticationProvider() == AuthProvider.LOCAL) {
userService.updateUserFromOAuth2Authentication(oAuth2User, existingUser);
} else if ((!Objects.equals(existingUser.getIdProvider(), oAuth2User.getproviderId())
|| existingUser.getAuthenticationProvider() != oAuth2User.getClientProvider())) {
throw new OAuth2AuthenticationException("a problem occured with Oauth2Authentication!");
}
} else {
// creation of new user
log.info(messageSource.getMessage("global.not-existing-user.oauth2-authenticated",
new Object[] { "createdUser" }, LocaleContextHolder.getLocale()));
userService.saveUserFromOAuth2Authentication(oAuth2User);
}
super.onAuthenticationSuccess(request, response, authentication);
}
}
In your configuration class for security, you just have to call the method successHandler()for Oauth2Login to use your new class "authentication success handler" without of course, using method defaultSucessUrl() , like this
http.oauth2Login()
.loginPage("/app/login")
.userInfoEndpoint()
.userService(oauth2UserService)
.and()
.successHandler(oAuth2LoginSuccessHandler)
;
Sorry for my bad english, i found this solution just after reading this article https://www.baeldung.com/spring-security-redirect-login

[[Edited]] JWT spring security: How to have roles be read by .hasRole() or .hasAuthority(), where they look at the user roles

I am trying to add an authorization to the spring actuator service using roles in the HTTP config but it doesn't work and the response is "forbidden 403" which means the user is unauthorized.
So My question is where exactly the .hasRole() finds the signed in user roles when using JWT token
Here is the config method in the class which extends WebSecurityConfigurerAdapter class
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new JwtTokenVerifier(jwtConfig), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/auth/**").permitAll()
.antMatchers("/actuator/**").hasRole("ACTUATOR")
.anyRequest()
.authenticated()
.and()
.formLogin().disable();
}
Here I put the roles in the JWT Token
public String generateJwtToken(UserDetailsImpl userDetails) {
Map<String, Object> claims = new HashMap<>();
Set<String> Userroles = new HashSet<>();
Role r1 = new Role();
r1.setDescription("ROLE_ACTUATOR");
r1.setId(1L);
Set<Role> roles = new HashSet<>();
roles.add(r1);
for(Role role:roles){
Userroles.add(role.getDescription());
}
claims.put("Roles",Userroles.toArray());
claims.put("userId", userDetails.getId());
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(java.sql.Date.valueOf(LocalDate.now().plusDays(getTokenExpirationAfterDays())))
.signWith(Keys.hmacShaKeyFor(getSecretKey().getBytes())).compact();
}
So what is wrong or missing? Thanks in advance
Here is an update of the JWT custom filter:
public class JwtTokenVerifier extends OncePerRequestFilter {
private JwtConfig jwtConfig;
public JwtTokenVerifier(JwtConfig jwtConfig) {
super();
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null || authorizationHeader.isEmpty() || !authorizationHeader.startsWith(jwtConfig.getTokenPrefix())) {
String requestParam = request.getParameter("token");
if (requestParam != null && !requestParam.isEmpty() && requestParam.startsWith(jwtConfig.getTokenPrefix())) {
authorizationHeader = requestParam;
} else {
filterChain.doFilter(request, response);
return;
}
}
try {
if (jwtConfig.validateJwtToken(authorizationHeader)) {
String username = jwtConfig.getUserNameFromJwtToken(authorizationHeader);
Authentication auth = new UsernamePasswordAuthenticationToken(username, new WebAuthenticationDetailsSource().buildDetails(request), null);
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
e.printStackTrace();
response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
}
filterChain.doFilter(request, response);
}
}
In the WebSecurityConfigurerAdapter extention, I see ACTUATOR, but in the user details definition I see ROLE_ACTUATOR. Is this a mismatch?
Thanks to #SteveRiesenberg, as said in the chat "if roles are not handled by your custom JWT filter, then that is the issue. Roles (authorities) must be populated when the authentication is performed."
so since the roles are not handled in the filter, they are not passed through the security chain.
The code of the filter is edited as follows:
UserDetailsImpl userDetails = (UserDetailsImpl) userDetailsService.loadUserByUsername(username);
Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
Authentication auth = new UsernamePasswordAuthenticationToken(username, new WebAuthenticationDetailsSource().buildDetails(request), authorities);
Where the authorities are passed to the UsernamePasswordAuthenticationToken and UserDetailsImpl is the implementation of UserDetails class provided by spring

Mock spring security roles through the header with JWT

Is possible to make a very simple JWT example with Spring Security.
I want to illustrate how roles can be used, without setting up the all of the spring security.
Basically I want to use postman with a jwt token which contain the roles. I don't want to setup user login. Is possible?
You can create JWTFilter class and extends GenericFilterBean then
override to doFilter.You can resolve jwt token in this method and jwt
is valid then you can get claim
List roles =
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(Jwt).getClaims("roles");
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest);
if (StringUtils.hasText(jwt)) {
if (this.tokenProvider.validateToken(jwt)) {
}
}
filterChain.doFilter(servletRequest, servletResponse);
}catch(Exception ex){
handleException((HttpServletResponse) servletResponse,ex);
}
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(JWTConfigurer.AUTHENTICATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
String jwt = bearerToken.substring(7, bearerToken.length());
return jwt;
}
String jwt = request.getParameter(JWTConfigurer.AUTHENTICATION_TOKEN);
if (StringUtils.hasText(jwt)) {
return jwt;
}
return null;
}

How to do basic authentication using cookies in spring security?

I am securing my REST api using Basic-Auth. On correct credentials passed by user, a controller is responsible for sending a httpOnly and secure cookie in response.
#GetMapping
#ResponseStatus(value=HttpStatus.OK)
public void loginUser( final HttpServletRequest request ,final HttpServletResponse response) throws UnsupportedEncodingException {
setAuthCookieToResonse(request,response);
}
private void setAuthCookieToResonse(final HttpServletRequest request ,final HttpServletResponse response) throws UnsupportedEncodingException {
String cookieKey = "auth";
String cookieValue = request.getHeader("Authorization");
if (cookieValue != null) {
Cookie cookie = new Cookie(cookieKey, cookieValue);
cookie.setHttpOnly(true);
cookie.setSecure(true);
response.addCookie(cookie);
}
}
So, now with each request a cookie is being sent by the browser, which will contain Basic-Auth details. But the problem is, how do the spring security extract those credentials from that cookie?
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {// #formatter:off
httpSecurity
.cors()
.and().authorizeRequests()
.antMatchers("/signup/**").permitAll()
.anyRequest().authenticated()
.and().httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable()
;
}
My guess was:
To add a filter before BasicAuthenticationFilter.class and extract the credentials from cookie and than add those credentials to the HttpServletRequest's Authorizaton header which is going to be passed to spring-security layer. But the problem is, HttpServletRequest doesn't have API to add headers.
What would be the right way to implement this?
I made this working after following this blog (archived). But I would love to hear other solutions, especially using some spring configuration itself. Spring is a very matured framework, it must(should) have something to handle this common problem.
Since, the HttpServletRequest don't have any method to add the new headers, I need to create a custom class which can add new headers to the request, this can be achived by HttpServletRequestWrapper. Here is the implementation.
public final class MutableHttpServletRequest extends HttpServletRequestWrapper {
// holds custom header and value mapping
private final Map<String, String> customHeaders;
public MutableHttpServletRequest(HttpServletRequest request) {
super(request);
this.customHeaders = new HashMap<String, String>();
}
public void putHeader(String name, String value) {
this.customHeaders.put(name, value);
}
public String getHeader(String name) {
// check the custom headers first
String headerValue = customHeaders.get(name);
if (headerValue != null) {
return headerValue;
}
// else return from into the original wrapped object
return ((HttpServletRequest) getRequest()).getHeader(name);
}
public Enumeration<String> getHeaderNames() {
// create a set of the custom header names
Set<String> set = new HashSet<String>(customHeaders.keySet());
// now add the headers from the wrapped request object
Enumeration<String> e = ((HttpServletRequest) getRequest()).getHeaderNames();
while (e.hasMoreElements()) {
// add the names of the request headers into the list
String n = e.nextElement();
set.add(n);
}
// create an enumeration from the set and return
return Collections.enumeration(set);
}
}
The filter which checks for the cookie, before the Spring-secuirty:
public class CheckAuthCookieFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(getClass());
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
MutableHttpServletRequest mutableRequest = new MutableHttpServletRequest(httpServletRequest);
Cookie[] cookies = httpServletRequest.getCookies();
if (cookies != null && cookies.length > 0) {
for (Cookie cookie : cookies) {
logger.debug(cookie.getName() + " : " + cookie.getValue());
if (cookie.getName().equalsIgnoreCase("auth")) {
mutableRequest.putHeader("Authorization", URLDecoder.decode(cookie.getValue(), "utf-8"));
}
}
}
chain.doFilter(mutableRequest, response);
}
}
and finally the security configuration:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {// #formatter:off
httpSecurity
.cors()
.and().authorizeRequests()
.antMatchers("/signup/**").permitAll()
.anyRequest().authenticated()
.and().httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().csrf().disable()
;
httpSecurity.addFilterBefore(new CheckAuthCookieFilter(), BasicAuthenticationFilter.class);
}
My custom filter will run before the Spring's BasicAuthenticationFilter.If there is a cookie present with name auth(which the application created on successful login), than that's the cookie which holds the basic auth credentials. The credentials are extracted from that, and added to the header of request. Then the BasicAuthenticationFilter will run and look for the Authorization and proceed with its normal flow.

Resources