Spring Boot 與 Axios 跨域訪問(CORS)並使用 JWT 解決 302(OPTIONS) 問題
續上篇 Spring Boot Vue Axios 實現前後端分離的跨域訪問(CORS) ,我們已經可以跨域訪問(CORS),現在來利用 JSON Web Token (JWT) 來完成一頁式的認證 。
當 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; } );
你必須 登入 才能發表評論。