2020 年 11 月 1 日

IT Skills 波林

Polin WEI – 資訊工作者的技術手札

Spring Boot 與 Vue + Axios 跨域訪問(CORS)並使用 JWT 解決 302(OPTIONS) 問題

3 min read
cors-options

Spring Boot 與 Axios 跨域訪問(CORS)並使用 JWT 解決 302(OPTIONS) 問題

 

續上篇 Spring Boot Vue Axios 實現前後端分離的跨域訪問(CORS)  ,我們已經可以跨域訪問(CORS),現在來利用 JSON Web Token (JWT) 來完成一頁式的認證 。

cors-options

當 request method 為 OPTIONS (302) 時,使用 response.setStatus(HttpServletResponse.SC_OK); 回應前端;其它狀況時,則先讀取 JWT 的 Token 判斷登入帳號的權限。

public class CorsFilter implements Filter {
 private final Logger logger = LoggerFactory.getLogger(this.getClass());

 @Autowired
 JwtTokenUtil jwtTokenUtil;
 @Value("${jwt.header}")
 private String tokenHeader;
 @Autowired
 private UserDetailsService userDetailsService;
 @Autowired 
 AppUserDetailsService appUserDetailsService;
 @Override
 
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  
  boolean isEnable = false;
  String allowOrigin = "http://localhost:8080"; 
  String username = null;
  String authToken = null;
  
  
  HttpServletResponse response = (HttpServletResponse) servletResponse;
  HttpServletRequest request= (HttpServletRequest) servletRequest;
  
  // addAllowedOrigin 不能設定為* 因為與 allowCredential 衝突
  response.setHeader("Access-Control-Allow-Origin", allowOrigin);
  response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
  response.setHeader("Access-Control-Max-Age", "3600");
  response.setHeader("Access-Control-Allow-Headers", "*");        
  response.setHeader("Access-Control-Allow-Credentials", "true");           
  response.setHeader("Access-Control-Expose-Headers", "Authorization"); 
  
  final String requestHeader = request.getHeader("Authorization");
  
  if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
   authToken = requestHeader.substring(7);
   try {
    username = jwtTokenUtil.getUsernameFromToken(authToken);
   } catch (IllegalArgumentException e) {
    logger.error("an error occured during getting username from token", e);
   } catch (ExpiredJwtException e) {                
    logger.warn("the token is expired and not valid anymore", e);
   }
  }
     
  if (HttpMethod.OPTIONS.name().equalsIgnoreCase(request.getMethod()) ) {
   response.setStatus(HttpServletResponse.SC_OK);
  } else {
   if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
    authToken = requestHeader.substring(7);
    try {
     username = jwtTokenUtil.getUsernameFromToken(authToken);
    } catch (IllegalArgumentException e) {
     logger.error("an error occured during getting username from token", e);
    } catch (ExpiredJwtException e) {               
     logger.warn("the token is expired and not valid anymore", e);
    }
    logger.debug("checking authentication for user '{}'", username);
    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
     logger.debug("security context was null, so authorizating user");
     UserDetails userDetails = appUserDetailsService.loadUserByUsername(username);
     if (jwtTokenUtil.validateToken(authToken, userDetails)) {
      UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
      authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
      logger.info("authorizated user '{}', setting security context", username);
      SecurityContextHolder.getContext().setAuthentication(authentication);                            
      
     }
    }               
   }
   filterChain.doFilter(servletRequest, servletResponse);
  }
    }
}

 

要將 CSRF 的 Token 停止,並加入 JWT 的 filter : jwtAuthorizationTokenFilter.java ,與 跨域訪問(CORS)的 filter: CorsFilter.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
 
    @Autowired
    AppsAuthenticationSuccessHandler appsAuthenticationSuccessHandler;
    @Autowired
    AppsAuthenticationFailureHandler appsAuthenticationFailureHandler;
    @Autowired
    AppsLogoutSuccessHandler appsLogoutSuccessHandler;
    @Autowired
    AppUserDetailsService appUserDetailsService;    
    @Autowired
    JwtTokenUtil jwtTokenUtil;
    @Value("${jwt.header}")
    private String tokenHeader;
    @Value("${jwt.route.authentication.path}")
    private String jwtAuthenticationPath;
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
 auth
 .userDetailsService(appUserDetailsService)
 .passwordEncoder(new BCryptPasswordEncoder());
    }
 
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
  httpSecurity
   // CSRF Token
   .csrf().disable()
   .authorizeRequests()
    .antMatchers("/user/password/update")
    .hasAuthority("CHANGE_PASSWORD_PRIVILEGE")
    .antMatchers("/", "/register","/login/failure" ,"/user/password/forgot","/user/password/change/**","/user/register/**").permitAll() //首頁不需認證 認證機制設置     
    .anyRequest().authenticated()           // 除了以上的 URL 外, 都需要認證才可以訪問
    .and()                  
   .formLogin()
    .loginPage("/login")                    // 認證頁面指向頁頁                 
    .failureHandler(appsAuthenticationFailureHandler)                   
    .defaultSuccessUrl("/auth/home")                    
    .permitAll()
    .successHandler(appsAuthenticationSuccessHandler)
    .and()
   .logout()
    .logoutSuccessHandler(appsLogoutSuccessHandler)
    .deleteCookies("JSESSIONID")
    .and()
   .sessionManagement()
    .maximumSessions(1).sessionRegistry(sessionRegistry()); 
  
  JwtAuthorizationTokenFilter jwtAuthorizationTokenFilter = new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
  httpSecurity.addFilterBefore(jwtAuthorizationTokenFilter, SessionManagementFilter.class);
  //adds your custom CorsFilter     
  httpSecurity.addFilterBefore(corsFilter(), ConcurrentSessionFilter.class);
 }
    
 @Override
 public void configure(WebSecurity web) throws Exception {
  //allow anonymous resource requests
  web.ignoring().antMatchers("/fonts/**","/css/**","/js/**","/webjars/**");     
  // for vue CORS       
  web.ignoring().antMatchers(jwtAuthenticationPath);

 }
 @Bean
 public SessionRegistry sessionRegistry() {
     return new SessionRegistryImpl();
 }
 
 /**/
 @Bean
    CorsFilter corsFilter() {
        CorsFilter filter = new CorsFilter();
        return filter;
    }
 
 /**
  * JWT 使用 for AuthenticationRestController.class 
  */
 @Bean    
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

 

在前端的 axios 送出 request 前, 將 JWT Token 放在 header即可

// doing something with the request
let jwtToken = localStorage.getItem("jwtToken");
axios.interceptors.request.use(
  config => {
    if (jwtToken){
      config.headers["Authorization"] = "Bearer " + jwtToken ;
      config.headers["X-REQUEST-TYPE"] = "axios";  
    }
    return config;
  },
  (request) => {
    // do something with request meta data, configuration, etc
    // dont forget to return request object, otherwise your app will get no answer
    return request;
  }
);

 

 

Copyright © All rights reserved. | Newsphere by AF themes.