maven clean install and mvn spring-boot:run gives me on POST request 401 (Unauthorized) error

40 views Asked by At

When I run my java spring boot project with "mvn clean package" and "mvn spring-boot:run" I get a POST http://localhost:8080/api/v1/register 401 (Unauthorized) error. While I am running my Java 21 backend project in IntelliJ IDEA with control + R or with run button then I am able to send post request from react frontend on port 3000 to java backend on port 8080 and save the user data to mysql database on port 3306.

I have been googling, reading, trying and struggling to find a working solution that could work through build and also when mysql database is in Docker. Using Java 21 and Spring Boot 3.2.4 Some help would be really appreciated.

WebConfig

package com.cinema.backend.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

import java.util.Arrays;

@Configuration
public class WebConfig {

    private static final long MAX_AGE = 3600L;

    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedOrigin("http://localhost:3000");
        config.setAllowedHeaders(Arrays.asList(
                "Authorization",
                "Cache-Control",
                "Content-Type"));
        config.setAllowedMethods(Arrays.asList(
                HttpMethod.GET.name(),
                HttpMethod.POST.name(),
                HttpMethod.PUT.name(),
                HttpMethod.DELETE.name(),
                HttpMethod.OPTIONS.name()));
        config.setMaxAge(MAX_AGE);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsFilter(source);
    }

}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cinema</groupId>
    <artifactId>backend</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>backend</name>
    <description>Backend Application for Cinema</description>
    <properties>
        <java.version>21</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

controller

package com.cinema.backend.controller;

import com.cinema.backend.models.User;
import com.cinema.backend.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserRepository userRepository;

    @PostMapping("/api/v1/register")
    User newUser(@RequestBody User newUser){
        return userRepository.save(newUser);
    }

}

1

There are 1 answers

0
Bizhan Laripour On

Based on your spring security dependency you have to have a authorization header in your request.

If you dont want to secure your application you can remove this dependency .

If you want to secure your application you must config web security like bellow

package com.dpco.business.security;

import com.dpco.business.exception.CustomException;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
@Configuration
@EnableWebSecurity
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {

//       httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "UNAUTHORIZED");
        throw new CustomException(e.getMessage() , HttpStatus.INTERNAL_SERVER_ERROR);
    }
}
package com.dpco.business.security;

import com.dpco.business.dto.LoginDto;
import com.dpco.business.exception.CustomException;
import com.dpco.logger.Logger4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class JwtAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private JwtValidator validator;

    @Autowired
    private Logger4j logger4j;

    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {

    }

    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {

        try {
            JwtAuthenticationToken jwtAuthenticationToken = (JwtAuthenticationToken) usernamePasswordAuthenticationToken;
            String token = jwtAuthenticationToken.getToken();

            LoginDto jwtUser = validator.validate(token);

            List<GrantedAuthority> grantedAuthorities = AuthorityUtils
                    .commaSeparatedStringToAuthorityList(jwtUser.getRole());
            return new JwtUserDetails(jwtUser.getUsername(), jwtUser.getId(),
                    token,
                    grantedAuthorities);
        }catch (Exception ex){
            logger4j.getLogger(ex);
            throw new CustomException(ex.getMessage() , HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @Override
    public boolean supports(Class<?> aClass) {
        try {
            return (JwtAuthenticationToken.class.isAssignableFrom(aClass));
        }catch (Exception ex){
            logger4j.getLogger(ex);
            throw new CustomException(ex.getMessage() , HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}


package com.dpco.business.security;

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;

public class JwtAuthenticationToken extends UsernamePasswordAuthenticationToken{

    private String token;

    public JwtAuthenticationToken(String token) {
        super(null, null);
        this.token = token;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    @Override
    public Object getCredentials() {
        return null;
    }

    @Override
    public Object getPrincipal() {
        return null;
    }
}


package com.dpco.business.security;

import com.dpco.business.exception.CustomException;
import com.dpco.logger.Logger4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthenticationTokenFilter extends AbstractAuthenticationProcessingFilter {

    @Autowired
    private Logger4j logger4j;

    public JwtAuthenticationTokenFilter() {
        super("/member/**");
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
        try {
            String content = httpServletRequest.getHeader("Content-Type");
            System.out.println("--------------------------------");
            System.out.println(content);
            System.out.println("---------------------------------");
            String header = httpServletRequest.getHeader("Authorization");
            if (header == null || !header.startsWith("Bearer ")) {
                throw new CustomException("JWT Token is missing or is not valid", HttpStatus.FORBIDDEN);
            }
            String authenticationToken = header.substring(6);
            JwtAuthenticationToken token = new JwtAuthenticationToken(authenticationToken);
            return getAuthenticationManager().authenticate(token);
        }catch (Exception ex){
            logger4j.getLogger(ex);
            throw new CustomException(ex.getMessage() , HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }


    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        try {
            super.successfulAuthentication(request, response, chain, authResult);
            chain.doFilter(request, response);
        }catch (Exception ex){

            throw new CustomException(ex.getMessage() , HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}


package com.dpco.business.security;

import com.dpco.business.dto.LoginDto;
import com.dpco.business.exception.CustomException;

import com.dpco.logger.Logger4j;
import com.dpco.business.service.MemberService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtGenerator {

    @Autowired
    private MemberService memberService;

    @Autowired
    private Logger4j logger4j;

    @Value("${security.jwt.token.expire-length}") // 1 hour
    private long validityInMilliseconds;

    public String generate(LoginDto loginDto){
        try {
            if (memberService.findByUsernameAndPassword(loginDto.getUsername() , loginDto.getPassword()) != null) {
                Claims claims = Jwts.claims()
                        .setSubject(loginDto.getUsername());
                claims.put("userId", String.valueOf(loginDto.getId()));
                claims.put("role", loginDto.getRole());
                Date validity = new Date(new Date().getTime() + validityInMilliseconds);
                return Jwts.builder()
                        .setClaims(claims)
                        .signWith(SignatureAlgorithm.HS512, "dpco")
                        .setExpiration(validity)
                        .compact();
            } else {
//                throw new CustomException("there is no member with this username and password so it is forbidden", HttpStatus.FORBIDDEN);
                return null;
            }
        }catch (Exception ex){
            logger4j.getLogger(ex);
            throw new CustomException(ex.getMessage() , HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
}


package com.dpco.business.security;


import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtSuccessHandler implements AuthenticationSuccessHandler{
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        System.out.println("Successfully Authentication");
    }
}

package com.dpco.business.security;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

public class JwtUserDetails implements UserDetails {

    private String userName;
    private String token;
    private Long id;
    private Collection<? extends GrantedAuthority> authorities;


    public JwtUserDetails(String userName, long id, String token, List<GrantedAuthority> grantedAuthorities) {

        this.userName = userName;
        this.id = id;
        this.token= token;
        this.authorities = grantedAuthorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return userName;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }


    public String getUserName() {
        return userName;
    }

    public String getToken() {
        return token;
    }


    public Long getId() {
        return id;
    }

}
package com.dpco.business.security;

import com.dpco.business.dto.LoginDto;
import com.dpco.business.exception.CustomException;
import com.dpco.logger.Logger4j;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

@Component
public class JwtValidator {


    private String secret = "dpco";

    @Autowired
    private Logger4j logger4j;

    public LoginDto validate(String token) {

        LoginDto jwtUser = null;
        try {
            Claims body = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();

            jwtUser = new LoginDto();

            jwtUser.setUsername(body.getSubject());
            jwtUser.setId(Long.parseLong((String) body.get("userId")));
            jwtUser.setRole((String) body.get("role"));
        }
        catch (Exception e) {
            logger4j.getLogger(e);
            throw new CustomException("this jwt is not valid" , HttpStatus.FORBIDDEN);
        }

        return jwtUser;
    }
}

And then add this configuration in your config package

package com.dpco.business.config;

import com.dpco.business.exception.CustomException;
import com.dpco.business.security.JwtAuthenticationEntryPoint;
import com.dpco.business.security.JwtAuthenticationProvider;
import com.dpco.business.security.JwtAuthenticationTokenFilter;
import com.dpco.business.security.JwtSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;

import java.util.Collections;

@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@Configuration
public class JwtSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationProvider authenticationProvider;
    @Autowired
    private JwtAuthenticationEntryPoint entryPoint;

    @Bean
    public AuthenticationManager authenticationManager() {
        try {
            return new ProviderManager(Collections.singletonList(authenticationProvider));
        } catch (CustomException ex) {
            throw new CustomException(ex.getMessage(), ex.getStatus());
        }
    }

    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilter() {
        try {
            JwtAuthenticationTokenFilter filter = new JwtAuthenticationTokenFilter();
            filter.setAuthenticationManager(authenticationManager());
            filter.setAuthenticationSuccessHandler(new JwtSuccessHandler());
            return filter;
        } catch (CustomException ex) {
            throw new CustomException(ex.getMessage(), ex.getStatus());
        }
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        try {
            http.csrf().disable()
                    .authorizeRequests().antMatchers("**/member/**").authenticated()
                    .and().authorizeRequests().antMatchers("/v2/api-docs").permitAll()
                    .and()
                    .exceptionHandling().authenticationEntryPoint(entryPoint)
                    .and()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

            http.addFilterBefore(authenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
            http.headers().cacheControl();
        } catch (CustomException ex) {
            throw new CustomException(ex.getMessage(), ex.getStatus());
        }

    }
}

And this is your login controller for getting token


package com.dpco.controller;

import com.dpco.business.dto.LoginDto;
import com.dpco.business.entity.Member;
import com.dpco.business.exception.CustomException;
import com.dpco.business.exception.ResultBody;
import com.dpco.business.security.JwtGenerator;
import com.dpco.business.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/")
@CrossOrigin("*")
public class LoginController {


    @Autowired
    private JwtGenerator jwtGenerator;
    @Autowired
    private MemberService memberService;


    @RequestMapping(path = "/login", method = RequestMethod.POST)
    public ResultBody generate(@RequestBody LoginDto loginDto) throws Exception {
        try {
            String token = jwtGenerator.generate(loginDto);
            System.out.println("--------------------------------------------------");
            System.out.println(token);
            System.out.println("----------------------------------------------------");
            return new ResultBody(token, HttpStatus.OK.value());
        } catch (CustomException ex) {
            throw new CustomException("some thing wrong in login", ex.getStatus());
        }
    }
}

package com.dpco.business.dto;

public class LoginDto {

    private String username;
    private String password;
    private long id;
    private String role;


    public LoginDto(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public LoginDto() {
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setId(long id) {
        this.id = id;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public long getId() {
        return id;
    }

    public String getRole() {
        return role;
    }
}

After that the token yo have got from this api must add to every authorization header