Spring Boot Security + Vue 登录成功后重定向无 Access-Control-Allow-Origin 问题解决办法

需要明确:

已正确配置跨域资源共享(CORS)(不然也不可能看到登录成功了)。

已正确配置了跨站请求伪造(CSRF)(登录时需要携带 _csrf.token)。

全部使用 POST 提交的数据。


本文相关知识:

MD5 加密与Spring Security 加密介绍。


  1. 注册时,使用 post 注册,已成功。
  2. 登录时,已经登录成功,效果如下:

     
  3. 前台查看axios响应:
    说明:
    登录成功与失败,都是响应 JSON 数据。
    这里登录成功后,进行了重定向,导致无 Access-Control-Allow-Origin。

    附上登录失败的响应:
    注:全部使用 POST:

     

问题说明

出现这种情况,毕竟是少数(其他请求GET/POST等全都正常,目前只发现 Spring Boot Security 登录成功后重定向时出现这种问题)。

响应 JSON 数据解决办法有两种思路:

  1. 登录成功后进行重定向时,携带 Access-Control-Allow-Origin。
  2. 登录成功后,不进行重定向,直接响应结果(我这里登录结果全部响应的是JSON数据)。

from 提交数据进行跳转:

  1. 跳转时在响应中添加 Header 即可。

​​​​解决方案:

在 Spring Boot Security 中配置登录成功后,进行自定义处理,接口为:

package org.springframework.security.web.authentication;

import java.io.IOException;

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

import org.springframework.security.core.Authentication;

/**
 * Strategy used to handle a successful user authentication.
 * <p>
 * Implementations can do whatever they want but typical behaviour would be to control the
 * navigation to the subsequent destination (using a redirect or a forward). For example,
 * after a user has logged in by submitting a login form, the application needs to decide
 * where they should be redirected to afterwards (see
 * {@link AbstractAuthenticationProcessingFilter} and subclasses). Other logic may also be
 * included if required.
 *
 * @author Luke Taylor
 * @since 3.0
 */
public interface AuthenticationSuccessHandler {

	/**
	 * Called when a user has been successfully authenticated.
	 *
	 * @param request the request which caused the successful authentication
	 * @param response the response
	 * @param authentication the <tt>Authentication</tt> object which was created during
	 * the authentication process.
	 */
	void onAuthenticationSuccess(HttpServletRequest request,
			HttpServletResponse response, Authentication authentication)
			throws IOException, ServletException;

}

通过实现该接口的 onAuthenticationSuccess 方法进行操作。

解决方案一

直接响应 JSON 数据:

代码说明:

在请求(HttpServletRequest request)中获取你需要的Header。

package cn.com.xuxiaowei.passport.handler;

import com.alibaba.fastjson.JSON;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 用于处理成功用户身份验证的策略。
 *
 * @author xuxiaowei
 * @since 0.0.1
 */
public class LoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        Map<String, Object> map = new HashMap<>(4);
        Map<String, Object> data = new HashMap<>(4);
        map.put("data", data);

        map.put("code", 0);
        map.put("msg", "登录成功");

        String accessControlAllowOrigin = request.getHeader("Access-Control-Allow-Origin");

        response.addHeader("Access-Control-Allow-Origin", accessControlAllowOrigin);
        response.addHeader("Access-Control-Allow-Credentials", "true");

        response.setContentType("text/json;charset=UTF-8");
        response.getWriter().println(JSON.toJSON(map));
        response.setStatus(HttpServletResponse.SC_OK);
        response.flushBuffer();
    }

}

在 WebSecurityConfigurerAdapter 中配置:

两种方案都需要此配置:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 其他代码省略

        // 授权登录成功后处理
        http.formLogin().successHandler(loginAuthenticationSuccessHandler());
    }


    @Bean
    LoginAuthenticationSuccessHandler loginAuthenticationSuccessHandler() {
        return new LoginAuthenticationSuccessHandler();
    }

响应结果:

解决方案二

package cn.com.xuxiaowei.passport.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 用于处理成功用户身份验证的策略。
 *
 * @author xuxiaowei
 * @since 0.0.1
 */
public class LoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    /**
     * 注意:
     * 不可设置:response.addHeader("Access-Control-Allow-Credentials", "true");
     * 否则响应中的的 Access-Control-Allow-Credentials 为:true,true
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        String accessControlAllowOrigin = request.getHeader("Access-Control-Allow-Origin");

        response.addHeader("Access-Control-Allow-Origin", accessControlAllowOrigin);

        request.getRequestDispatcher("/login/success.do").forward(request, response);
    }

}

注意
不可设置:response.addHeader("Access-Control-Allow-Credentials", "true");
否则响应中的的 Access-Control-Allow-Credentials 为:true,true;
因为已经存在了 Access-Control-Allow-Credentials 为:true。

 虽然有警告,但是这是时数据正常响应的: 

不设置 Access-Control-Allow-Credentials 就对了。

在 WebSecurityConfigurerAdapter 中配置同方案一。

效果相同。

解决方案三

如果你使用的是from提交数据进行跳转的话:

package cn.com.xuxiaowei.passport.handler;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 用于处理成功用户身份验证的策略。
 *
 * @author xuxiaowei
 * @since 0.0.1
 */
public class LoginAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        String accessControlAllowOrigin = request.getHeader("Access-Control-Allow-Origin");

        response.addHeader("Access-Control-Allow-Origin", accessControlAllowOrigin);
        response.addHeader("Access-Control-Allow-Credentials", "true");

        super.onAuthenticationSuccess(request, response, authentication);
    }

}

相关知识补充

Spring Security 密码加密

/**
 * Spring Security 密码加密相关测试
 *
 * @author xuxiaowei
 * @since 0.0.1
 */
public class SpringSecurityTests {

    /**
     * 用来测试的密码
     */
    private final String password = "https://xuxiaowei.blog.csdn.net";

    @Test
    public void passwordEncoder() {

        // 密码编辑器
        PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

        System.err.println("加密前的密码:" + password);

        String passwordPncode1 = passwordEncoder.encode(password);
        String passwordPncode2 = passwordEncoder.encode(password);

        System.err.println("第一次加密:" + passwordPncode1);
        System.err.println("第二次加密:" + passwordPncode2);

        boolean matches1 = passwordEncoder.matches(password, passwordPncode1);
        boolean matches2 = passwordEncoder.matches(password, passwordPncode2);

        System.err.println("第一次加密密码比较:" + matches1);
        System.err.println("第二次加密密码比较:" + matches2);

    }

}

测试结果:

加密前的密码:https://xuxiaowei.blog.csdn.net
第一次加密:{bcrypt}$2a$10$z.E5k6q6MHG9iVN/zRM0FOknS748k5b8gvGp/8njgLjDFZ9GyvcVK
第二次加密:{bcrypt}$2a$10$yosn9iar2ARMikmxLFDy6uYHyEu02DD8NOh/nMgOZJcjKQnWV9EdW
第一次加密密码比较:true
第二次加密密码比较:true

说明:

每次加密,即使是相同的密码,加密后的结果也不会相同。

MD5密码加密

import org.junit.Test;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * MD5 密码解密测试
 *
 * @author xuxiaowei
 * @since 0.0.1
 */
public class Md5EncodeTests {

    /**
     * 用来测试的密码
     */
    private final String password = "123456";

    @Test
    public void md5() {

        System.err.println("加密前的密码:" + password);

        String passwordEncode1 = md5Encode(password);
        String passwordEncode2 = md5Encode(password);

        System.err.println("第一次加密:" + passwordEncode1);
        System.err.println("第二次加密:" + passwordEncode2);

        boolean equals1 = passwordEncode1.equals(md5Encode(password));
        boolean equals2 = passwordEncode2.equals(md5Encode(password));

        System.err.println("第一次加密密码比较:" + equals1);
        System.err.println("第二次加密密码比较:" + equals2);
    }

    /**
     * MD5 加密
     *
     * @param text 需要加密的字符串
     * @return 返回 MD5 加密后的字符串
     */
    private String md5Encode(String text) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(text.getBytes());
            byte[] bytes = messageDigest.digest();
            int i;
            StringBuilder buf = new StringBuilder();
            for (byte b : bytes) {
                i = b;
                i = i < 0 ? i + 256 : i;
                if (i < 16) {
                    buf.append("0");
                }
                buf.append(Integer.toHexString(i));
            }
            return buf.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

}

测试结果:

加密前的密码:123456
第一次加密:e10adc3949ba59abbe56e057f20f883e
第二次加密:e10adc3949ba59abbe56e057f20f883e
第一次加密密码比较:true
第二次加密密码比较:true

说明:

MD5加密,针对同一密码(资源)加密结果每次运行都是一样的。所以出于安全考虑,密码加密不推荐使用 MD5,安全系数低,有泄漏的风险。

若你使用的是简易密码,如:123456,或者两人使用相同的密码,当密码保管不慎泄漏时,后果很严重。

更不要在多个账户中使用同一个密码。

发布了94 篇原创文章 · 获赞 32 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_32596527/article/details/96472948
今日推荐