简体   繁体   中英

Not able to read authorization header in response of spring boot in angular 5 application

I am getting my hands dirty by building an application using the spring framework and Angular 5.

I have the backend of the application ready using spring boot, spring rest and spring security. For the purpose of this application, I have used spring JWT tokens for the purpose of authorization.

I have tested the backend services using postman and they are working fine without any glitch.

Now when I try to integrate the application to the front end (Angular 5), I have been facing CORS issue as well as I am unable to access the response headers in in the service calls happening from the front end angular application.

I have already set CORS to Expose headers in response in WebSecurity file.

I am able to see in the response a token carrying the Authorization token in Chrome, but still unable to access.

Any help would be appreciated.

Thanks!

Can anyone suggest what should be the right way to set the CORS in the spring boot application. My current application looks as below.

User.java

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;


@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    private String username;

    private String password;

    public long getId() {
        return id;
    }

    public String getUsername() {
        return username;
    }

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

    public String getPassword() {
        return password;
    }

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

}

UserController.java

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.app.mycompany.TasksApp.model.User;
import com.app.mycompany.TasksApp.repository.UserRepository;

@RestController
@RequestMapping("/users")
public class UserController {

    private UserRepository userRepository;

    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public UserController(UserRepository userRepository,
                          BCryptPasswordEncoder bCryptPasswordEncoder) {

        this.userRepository = userRepository;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    @PostMapping("/sign-up")
    public void signUp(@RequestBody User user) {
        user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
        userRepository.save(user);
    }

}

UserRepository.java

import org.springframework.data.jpa.repository.JpaRepository;

import com.app.mycompany.TasksApp.model.User;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

UserDetailsServiceImpl.java

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.app.mycompany.TasksApp.model.User;
import com.app.mycompany.TasksApp.repository.UserRepository;
import org.springframework.stereotype.Service;

import static java.util.Collections.emptyList;


@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), emptyList());
    }

}

WebSecurity.java

import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.servlet.config.annotation.CorsRegistry;

import static com.app.mycompany.TasksApp.security.SecurityConstants.SIGN_UP_URL;

import java.util.Arrays;

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {

    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
                .anyRequest().authenticated()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .and().cors().and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()));
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST", "OPTIONS", "PUT", "DELETE"));
        configuration.setAllowCredentials(true);
        configuration.setExposedHeaders(Arrays.asList("Content-type","Authorization"));
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    }

}

JWTAuthenticationFilter.java

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;

import com.app.mycompany.TasksApp.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;

import static com.app.mycompany.TasksApp.security.SecurityConstants.EXPIRATION_TIME;
import static com.app.mycompany.TasksApp.security.SecurityConstants.HEADER_STRING;
import static com.app.mycompany.TasksApp.security.SecurityConstants.SECRET;
import static com.app.mycompany.TasksApp.security.SecurityConstants.TOKEN_PREFIX;

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException {
        try {
            User creds = new ObjectMapper()
                    .readValue(req.getInputStream(), User.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            creds.getUsername(),
                            creds.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) throws IOException, ServletException {
        String token = Jwts.builder()
                .setSubject(((org.springframework.security.core.userdetails.User) auth.getPrincipal()).getUsername())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
        res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
    }

}

JWTAuthorizationFilter.java

import java.io.IOException;
import java.util.ArrayList;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import io.jsonwebtoken.Jwts;

import static com.app.mycompany.TasksApp.security.SecurityConstants.EXPIRATION_TIME;
import static com.app.mycompany.TasksApp.security.SecurityConstants.HEADER_STRING;
import static com.app.mycompany.TasksApp.security.SecurityConstants.SECRET;
import static com.app.mycompany.TasksApp.security.SecurityConstants.TOKEN_PREFIX;

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    public JWTAuthorizationFilter(AuthenticationManager authManager) {
        super(authManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                    HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException {
        String header = req.getHeader(HEADER_STRING);
        if (header == null || !header.startsWith(TOKEN_PREFIX)) {
            chain.doFilter(req, res);
            return;
        }
        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        if (token != null) {
            // parse the token.
            String user = Jwts.parser()
                    .setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                    .getBody()
                    .getSubject();
            if (user != null) {
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            }
            return null;
        }
        return null;
    }

}

Angular UI Service call

I have changed the api to use the HttpClient method as well and observe the response. Still I am unable to access the headers in response.

authenticate(credentials, callback) {

    const AuthorizationHeader = credentials ? 'Basic ' + btoa(credentials.username + ':' + credentials.password) : '';

    const serviceheaders = new HttpHeaders({'Content-Type': 'application/json', 'Authorization' : AuthorizationHeader});

    // const headers = new HttpHeaders({
    //   'Content-Type': 'application/json', 'Authorization' : AuthorizationHeader
    // });

    const username = credentials ? credentials.username : '';
    const password = credentials ? credentials.password : '';

    this.http.post<HttpResponse<any>>('http://localhost:8080/login', JSON.stringify({username: username, password: password})
          , {headers: serviceheaders, observe: 'response', responseType: 'json'})
                  .subscribe( (response: HttpResponse<any>) => {
                    console.log('Headers only: ' + response.headers);
                    console.log('Response Headers : ' + response.headers.get('Authorization'));
                    const token = response.headers.get('Authorization');
                    if (token !== null) {
                      this.authenticated = true;
                    }
                    return callback && callback();
    });

  }

I know I'm late but I had a similar problem with React + Spring Boot. It was solved by adding "config.setExposedHeaders(Arrays.asList("Authorization"))" in my CORS configuration:

@Bean
CorsConfigurationSource corsConfigurationSource() {

    final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    source.registerCorsConfiguration("/**", config.applyPermitDefaultValues());
    config.setExposedHeaders(Arrays.asList("Authorization"));

    return source;
}

In my frontend I did the following:

export default function ({ history }) {
   const [login, setLogin] = useState('');
   const [password, setpassword] = useState('');

   async function handleLogin(e) {
       e.preventDefault();

       const response = await Api.post('/login', {
           login, senha: password
       });

       console.log(response.headers);
       if(response.headers['authorization']){
           history.push("/menu");
       }
}

As you can see, the "Authorization" header comes in lower case.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM