'Double authentication error with JWT Spring Security and Angular 12
I'm trying to get my JWT token from spring security. I can get it from postman, but when I'm making the request from angular, it opens a snackBar requesting credentials.
This is my login service:
import { environment } from './../../environments/environment';
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class LoginService {
url: string = `${environment.apiToken}`;
constructor(private http: HttpClient, private router: Router) { }
logIn(usuario: string, contrasena: string) {
const body = `grant_type=password&username=${encodeURIComponent(usuario)}&password=${encodeURIComponent(contrasena)}`;
console.log(btoa(environment.TOKEN_AUTH_USERNAME + ':' + environment.TOKEN_AUTH_PASSWORD));
console.log(body);
return this.http.post<any>(this.url, body, {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
.set('Authorization', 'Basic ' + btoa(environment.TOKEN_AUTH_USERNAME + ':' + environment.TOKEN_AUTH_PASSWORD))
});
}
logOut() {
if (this.estaLogueado()) {
let token = sessionStorage.getItem(environment.TOKEN_NAME);
this.http.get(`${environment.HOST}/cerrarSesion/anular/${token}`).subscribe(() => {
sessionStorage.clear();
this.router.navigate(['']);
});
}else{
this.router.navigate(['']);
}
}
isLoggedIn(): boolean {
let token = sessionStorage.getItem(environment.TOKEN_NAME);
return token !== null;
}
}
I'm making requests by a proxy, this is my proxy.conf
{
"/token": {
"target": "http://localhost:8080/oauth/token",
"secure": false,
"logLevel":"debug"
}
}
This is the snackbar

When I'm checking the headers and body of the request made by angular, it's the same request from postman.
Authorization headers from angular

Body from angular

Authorization and headers from postman

This is my application.properties in Spring
spring.mvc.throw-exception-if-no-handler-found=true
spring.web.resources.add-mappings=false
spring.jpa.database=postgresql
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost/demo
!<-- Database Credentials -->
spring.datasource.username=postgres
spring.datasource.password=admin
!<-- Security -->
security.oauth2.resource.filter-order=3
security.signing-key = MaYzkSjmkzPC57L
security.encoding-strength=256
security.security-realm= Spring Boot JWT
!<-- jwt -->
security.jwt.client-id=redsocialaappspring
security.jwt.client-secret=redsocial2021
security.jwt.grant-type=password
security.jwt.scope-read=read
security.jwt.scope-write=write
security.jwt.resource-ids=redsocialId
This is my securityConfig class
package com.example.demo;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${security.signing-key}")
private String signingKey;
@Value("${security.encoding-strength}")
private Integer encodingStrength;
@Value("${security.security-realm}")
private String securityRealm;
// Libreria para hashing
@Autowired
private BCryptPasswordEncoder bcrypt;
// Obtiene usuarios, roles y claves de la aplicación
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DataSource dataSource;
// Codificar las contraseñas por hashing
@Bean
public static BCryptPasswordEncoder passwordEnconder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
// Obtener detalles del usuario por medio de una interfaz
@Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bcrypt);
}
// Comportamiento de la seguridad, al ser por servicios es stateless
// Desactivamos tokens csrf -- veitando ataques js dentro de los formularios
// Usamos jwt -Oauth
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.realmName(securityRealm)
.and()
.csrf()
.disable();
}
// Instancia para crear tokens
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey(signingKey);
return converter;
}
// Guardado de tokens
@Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(this.dataSource);
}
// Acción primaria en ejecutarse
@Bean
@Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
defaultTokenServices.setReuseRefreshToken(false);
return defaultTokenServices;
}
}
This is my resourceServerConfig class
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import com.example.demo.exception.AuthExceptionOwn;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
//Traemos la configuración de SecurityConfig
@Autowired
private ResourceServerTokenServices tokenServices;
@Value("${security.jwt.resource-ids}")
private String resourceIds;
//Creación de token y configuración de resourceIds
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception{
resources.resourceId(resourceIds).tokenServices(tokenServices);
}
//URLs y metodos de proteccion
@Override
public void configure(HttpSecurity http) throws Exception{
http
.exceptionHandling().authenticationEntryPoint(new AuthExceptionOwn())
.and()
.requestMatchers()
.and()
.authorizeRequests()
.antMatchers("/amigos/**").authenticated()
.antMatchers("/usuarios/**").permitAll()
.antMatchers("/cerrarSesion/**").permitAll()
.antMatchers("/token/**").permitAll();
}
}
This is my authorizationServer class
package com.example.demo;
import java.util.Arrays;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
//Elaboración del token
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter{
@Value("${security.jwt.client-id}")
private String clientId;
@Value("${security.jwt.client-secret}")
private String clientSecret;
@Value("${security.jwt.grant-type}")
private String grantType;
@Value("${security.jwt.scope-read}")
private String scopeRead;
@Value("${security.jwt.scope-write}")
private String scopeWrite;
@Value("${security.jwt.resource-ids}")
private String resourceIds;
//Se trae la configuración
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter accessTokenConverter;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private BCryptPasswordEncoder bcrypt;
//Grantype genera valores de acceso
//Tiempo de vida del token
@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception{
configurer.inMemory().withClient(clientId).secret(bcrypt.encode(clientSecret))
.authorizedGrantTypes(grantType).scopes(scopeRead, scopeWrite).resourceIds(resourceIds)
.accessTokenValiditySeconds(7200).refreshTokenValiditySeconds(0);
}
//Se genera la cadena de texto de jwt
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
enhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
endpoints.tokenStore(tokenStore).accessTokenConverter(accessTokenConverter)
.tokenEnhancer(enhancerChain).authenticationManager(authenticationManager);
}
}
Solution 1:[1]
I've found the problem, I'm calling /token twice at the end of my angular proxy
The proxy.conf.json endded up like this
{
"/token": {
"target": "http://localhost:8080/oauth",
"secure": false,
"logLevel":"debug"
}
}
Thanks everybody for your help >:[.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | tupapiriki27 |
