2

I am trying to implement JWT Token Based Authentication and Authorization. I am using Spring Boot for backend and Angular 7 as frontend and my job is to complete backend work. Bearer Token is successfully generated in Authentication. And I have added it into Header but when I try to fetch the Header using request.getHeader(HEADER_STRING) it isnull.

So how to use this generated Token for further identifying logged in user OR it is frontend job to identify user after token generation?

I have used custom login page in Spring Security and when I make a request to http://localhost:8080/login it contains two login forms instead of one.

Image

Console

doFilterInternal header null response.getHeader(HEADER_STRING) null
getAuthenticationToken token: null
doFilterInternal authenticationToken : null null
doFilterInternal header null response.getHeader(HEADER_STRING) null
getAuthenticationToken token: null
doFilterInternal authenticationToken : null null
doFilterInternal header null response.getHeader(HEADER_STRING) null
getAuthenticationToken token: null
doFilterInternal authenticationToken : null null
doFilterInternal header null response.getHeader(HEADER_STRING) null
getAuthenticationToken token: null
doFilterInternal authenticationToken : null null
attemptAuthentication  email: rp@gmail.com abcd@A123
2019-03-04 11:33:34.320  INFO 11652 --- [nio-8080-exec-5] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select user0_.id as id1_1_, user0_.branch as branch2_1_, user0_.contact as contact3_1_, user0_.createtime as createti4_1_, user0_.designation as designat5_1_, user0_.email as email6_1_, user0_.expreiance as expreian7_1_, user0_.name as name8_1_, user0_.password as password9_1_, user0_.role as role10_1_, user0_.skypeid as skypeid11_1_, user0_.statusenable as statuse12_1_ from user user0_ where user0_.email=?
loadUserByUsername User [user.toString] Role: ROLE_ADMIN
successfulAuthentication  username rp@gmail.com
successfulAuthentication bearer Token Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJycEBjeWduZXRpbmZvdGVjaC5jb20iLCJleHAiOjE1NTE3NjU4MTR9.9mLS64W6JBS1RqlEKl1Zmjb8YS03E9k92ITkaFmw35JH4ELIua8Tbkzj0r9crDgdQnxm3YvFKAD9lY3cgoQsNw
doFilterInternal header null response.getHeader(HEADER_STRING) null
getAuthenticationToken token: null
doFilterInternal authenticationToken : null null
doFilterInternal header null response.getHeader(HEADER_STRING) null
getAuthenticationToken token: null
doFilterInternal authenticationToken : null null
doFilterInternal header null response.getHeader(HEADER_STRING) null
getAuthenticationToken token: null
doFilterInternal authenticationToken : null null

JWTAuthenticationFilter.java

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private AuthenticationManager authenticationManager;

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

    @Autowired
    CustomUserDetailService customUserDetailService;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        try {
            System.out.println("attemptAuthentication "+" email: "+request.getParameter("email") + " "+request.getParameter("password"));
            //User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
            return this.authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(request.getParameter("email"), request.getParameter("password")));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        String username = ((org.springframework.security.core.userdetails.User) authResult.getPrincipal()).getUsername();
        System.out.println("successfulAuthentication "+" username "+username);

        String token = Jwts
                .builder()
                .setSubject(username)
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .compact();
        String bearerToken = TOKEN_PREFIX + token;
        System.out.println("successfulAuthentication bearer Token "+bearerToken);

        response.getWriter().write(bearerToken);
        response.addHeader(HEADER_STRING, bearerToken);
        response.sendRedirect(SIGN_UP_SUCCESS);
    }
}

JWTAuthorizationFilter.java

public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    private final CustomUserDetailService customUserDetailService;

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager, CustomUserDetailService customUserDetailService) {
        super(authenticationManager);
        this.customUserDetailService = customUserDetailService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {

        String header = request.getHeader(HEADER_STRING);

        System.out.println("doFilterInternal header "+header+ " response.getHeader(HEADER_STRING) "+response.getHeader(HEADER_STRING));
        if (header == null || !header.startsWith(TOKEN_PREFIX)) {
            chain.doFilter(request, response);
        }
            UsernamePasswordAuthenticationToken authenticationToken = getAuthenticationToken(request);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            response.addHeader(header, SIGN_UP_URL);
            System.out.println("doFilterInternal authenticationToken : "+authenticationToken+ " "+response.getHeader(SIGN_UP_URL));
            chain.doFilter(request, response);


    }

    private UsernamePasswordAuthenticationToken getAuthenticationToken(HttpServletRequest request) {
        String token = request.getHeader(HEADER_STRING);
        System.out.println("getAuthenticationToken token: "+token);
        if (token == null) return null;
        String username = Jwts.parser().setSigningKey(SECRET)
                .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                .getBody()
                .getSubject();
        UserDetails userDetails = customUserDetailService.loadUserByUsername(username);
        System.out.println("getAuthenticationToken userDetails "+userDetails.toString()+ " userDetails.getAuthorities() "+userDetails.getAuthorities());
        return username != null ?
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()) : null;
    }
}

CustomUserDetailService.java

@Component
public class CustomUserDetailService implements UserDetailsService {
    private final UserRepository userRepository;

    @Autowired
    public CustomUserDetailService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(username);

                if(user==null) 
                    {
                        new UsernameNotFoundException("User not found");
                        return null;    
                    }
                else
                {
                    System.out.println("loadUserByUsername "+user.toString()+" Role: "+user.getRole());
                    List<GrantedAuthority> authorityListAdmin = AuthorityUtils.createAuthorityList("ROLE_USER", "ROLE_ADMIN");
                    List<GrantedAuthority> authorityListUser = AuthorityUtils.createAuthorityList("ROLE_USER");
                    return new org.springframework.security.core.userdetails.User
                            (user.getEmail(), user.getPassword(), user.getRole()=="ADMIN" ? authorityListAdmin : authorityListUser);

                }
    }
}

SecuriyConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer{

     @Autowired
     private CustomUserDetailService customUserDetailService;


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

     @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(customUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
        }


    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
        registry.addViewController("/failure").setViewName("failure");
        registry.addViewController("/403").setViewName("403");
    }

        @Override
        protected void configure(HttpSecurity http) throws Exception{

             http
             .csrf().disable()
             .authorizeRequests().antMatchers("/login","/home","/failure").permitAll()
             .antMatchers(HttpMethod.POST,"/admin/**").permitAll()//hasRole("ADMIN") 
             .antMatchers(HttpMethod.PUT,"/admin/**").hasRole("ADMIN")
             .antMatchers(HttpMethod.GET,"/admin/**").hasRole("ADMIN")
             .antMatchers(HttpMethod.GET,"/user/**").hasAnyRole("ADMIN","USER")
             .antMatchers(HttpMethod.POST,"/user/**").hasAnyRole("ADMIN","USER")
             .anyRequest().authenticated()
             .and()
             .addFilter(new JWTAuthenticationFilter(authenticationManager()))
             .addFilter(new JWTAuthorizationFilter(authenticationManager(), customUserDetailService))
             .exceptionHandling().accessDeniedPage("/403")
             .and()
             .formLogin()
             .loginPage("/login")
             .loginProcessingUrl("/login")
             .usernameParameter("email")
             .passwordParameter("password")
             .defaultSuccessUrl("/home",true)
             .failureUrl("/failure")
             .and()
             .logout().logoutUrl("/logout").permitAll();

    }

        public SecurityConfig(UserDetailsService userDetailsService) {
            super();
            this.customUserDetailService = customUserDetailService;
        }       
}   

SecurityConstants.java

public class SecurityConstants {
    static final String SECRET = "Romil";
    static final String TOKEN_PREFIX = "Bearer ";
    static final String HEADER_STRING = "Authorization";
    static final String SIGN_UP_URL = "/login";
    static final String SIGN_UP_SUCCESS = "/home";
    static final long EXPIRATION_TIME = 86400000L;
}
Romil Patel
  • 12,879
  • 7
  • 47
  • 76
  • i see this line **response.sendRedirect(SIGN_UP_SUCCESS);** meaning the context is lost and the request, response objects are new with the redirect – resatz Mar 04 '19 at 08:10
  • @resatz Thank you for the response. if I don't use response.sendRedirect(SIGN_UP_SUCCESS); then generated token is downloaded automatically in the system and login page stay as it is. – Romil Patel Mar 04 '19 at 08:35
  • You can try using forward instead of redirect more details here https://stackoverflow.com/questions/2047122/requestdispatcher-forward-vs-httpservletresponse-sendredirect – resatz Mar 04 '19 at 08:42
  • @resatz RequestDispatcher will hold the current req, res with it and forwards to next URL. I have used RequestDispatcher rd = request.getRequestDispatcher(SIGN_UP_SUCCESS); rd.forward(request, response); and it again stays to the same page. console prints Request method 'POST' not supported – Romil Patel Mar 04 '19 at 09:35
  • You are setting the authorization header on the client right? Before sending the request to the server. – Merv Mar 05 '19 at 00:57
  • Btw why pass customeruserdetails to authorization filter? The idea behind JWT is to be stateless. Any information needed should be passed in the token. But ok. And normally the authorization filter should be added before the usernamepasswordauthenticationfilter. Try to use addfilterbefore. This is also unnecessary if im not mistaken public SecurityConfig(UserDetailsService userDetailsService) { super(); this.customUserDetailService = customUserDetailService; } – Merv Mar 05 '19 at 01:04
  • @Merv I generate the token on successful authentication Please see the SecurityCofing I have added addFilter Before loginForm. Please guide me more on this – Romil Patel Mar 05 '19 at 05:57
  • I have added SessionCreationPolicy.STATELESS, http.addFilterBefore(new JWTAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class) .addFilter(new JWTAuthorizationFilter(authenticationManager(), customUserDetailService)); as you have told but the same issue is there. When I use RequestDispatcher console print POST is not supported and i have also removed super()... that was not used anywhere. Looking for your help. Thank you – Romil Patel Mar 05 '19 at 06:00
  • Do I need to send user credentials in token as payload? and how to get token whenever user try to access resources – Romil Patel Mar 05 '19 at 06:01
  • You should add the http.addFilterBefore(new JWTAuthorizationFilter(.......) Not authentication filter that will make sure to check if token is available in other words if the user is already logged in before logging in again. – Merv Mar 05 '19 at 07:52

1 Answers1

3

How to Pass Token?

  • Generate the Token in Spring Boot On Successful Login send token as Response to front-end
  • Create AuthInterceptor in Angular that which will set the Token on every further request.

Notes:

  1. We also have to set WebConfig file according to our requirements.

    • allowedMethods
    • allowedHeaders
    • exposedHeaders
    • maxAge etc..
  2. For the request in which we don't need to Authenticate and Authorize the user we can add that APIs in ignoring().antMatchers("").

  @Override
      public void configure(WebSecurity web) throws Exception {
          web
            .ignoring()
              .antMatchers("/userlogin/")
              .antMatchers("/forgetPassword");
      }

Why Access-Control-Expose-Headers?

Access-Control-Expose-Headers (optional) - The XMLHttpRequest object has a getResponseHeader() method that returns the value of a particular response header. During a CORS request, the getResponseHeader() method can only access simple response headers. Simple response headers are defined as follows:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

If you want clients to be able to access other headers, you have to use the Access-Control-Expose-Headers header. The value of this header is a comma-delimited list of response headers you want to expose to the client.

response.setHeader("Access-Control-Expose-Headers", "Authorization");

AuthInterceptor

import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = window.localStorage.getItem('tokenKey'); // you probably want to store it in localStorage or something


    if (!token) {
      return next.handle(req);
    }

    const req1 = req.clone({
      headers: req.headers.set('Authorization', `${token}`),
    });

    return next.handle(req1);
  }

}
Romil Patel
  • 12,879
  • 7
  • 47
  • 76