Spring Security 使用 JSON 格式登陆

Spring Security 使用 JSON 格式登陆

博客: www.lxiaocode.com ,获取更好的阅读体验。

在 Spring Security 中默认的登陆是通过表单的形式进行的。但是在前后端分离的项目中很少会使用表单的形式登陆。大多数情况是由前端调用登陆接口,登陆后则会返回 JSON 格式的响应告诉前端是否成功,根据返回进行跳转页面或其他操作就可以由前端来进行判断了。

那接下来就看一看在 Spring Secuirty 中如何使用 JSON 格式登陆吧!

本文配套的示例源码: https://github.com/lxiaocode/spring-security-examples

你将会学到什么

  1. Spring Security 的默认配置。
  2. Spring Security 是如何处理认证异常的?
  3. Spring Security 是如何获取用户输入参数的?
  4. 自定义身份验证过滤器,实现 JSON 格式的登陆。
  5. 将自定义的过滤器配置到 Spring Security。
  6. Spring Security 在登陆后会进行什么操作?
  7. 自定义登陆 成功/失败 处理器。

1. Spring Security 的默认配置

1.1 创建 Spring Security 项目

首先需要创建一个 Spring Security 的项目。你可以使用 Spring Initializr 进行创建,也可以使用 Maven 进行创建。因为以后可能还会继续写关于 Spring Security 相关的示例,所以本文配套的源码是使用 Maven 创建的一个多模块项目,以后的示例都会放到这个项目中。

创建项目之后添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 这是一个 Json 工具库,之后会用到 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

编写一个用于测试的接口:

@SpringBootApplication
@RestController
@RequestMapping("/")
public class Application {
   
   
    // 程序启动方法
    public static void main(String[] args) {
   
        SpringApplication.run(Application.class);
    }

    // 用于测试 Spring Security 的接口
    @GetMapping("")
    public String index(){
   
        return "index.html";
    }
}
1.2 Spring Security 默认配置
  • 启动默认配置后,该配置会创建一个名为 springSecurityFilterChain 的 Servlet 过滤器 bean。这个 bean 负责应用程序中所有的安全性(保护应用程序URL,验证提交的用户名和密码,重定向到登录表单等)。
  • 使用用户名和随机生成的密码创建一个 UserDetailsService bean,并记录到控制台。
  • springSecurityFilterChain 注册过滤器。
1.2.1 默认配置实现的功能

虽然 Spring Security 的默认配置不多,但却实现了很多功能:

  • 需要通过身份验证才能与应用程序进行交互。
  • 为你生成一个默认的登陆表单。
  • 让用户使用 user 用户名和密码通过基于表单的身份验证(再前面的示例中,密码为 8e557245-73e2-4286-969a-ff57fe326336)。
  • 使用 BCrypt 保护密码的储存。
  • 允许用户注销。
  • 预防 CSRF 攻击。
  • Session Fixation 保护。
  • Security Header 集成。
  • Servlet API方法集成。
    • HttpServletRequest#getRemoteUser()
    • HttpServletRequest.html#getUserPrincipal()
    • HttpServletRequest.html#isUserInRole(java.lang.String)
    • HttpServletRequest.html#login(java.lang.String, java.lang.String)
    • HttpServletRequest.html#logout()
1.2.2 默认的 Spring Security 项目

如果你看不懂上面的内容,那么你就当作都是废话。

现在启动项目,你会发现在控制中打印了一串字符:

Using generated security password: cb102f8a-8286-496b-92c0-d36989e55987

很明显这是 Spring Security 为我们自动生成密码,每次启动项目这个密码都会重新生成。有了密码,那么用户名时什么呢?没错就是 “user”。

为什么默认的用户名是 “user” 呢?这个密码又是在哪里生成的呢?这些问题我都会在以后的文章中解释,所以请密切关注我的博客(www.lxiaocode.com)或者公众号(lxiao学习日记)。😄

然后访问我们刚刚编写的测试接口:http://localhost:8080/。你会发现会被重定向到 Spring Security 提供的默认登陆页面(/login)。默认的登陆页面和登陆接口的 URL 都是 “/login”,登陆页面为 GET 请求,登陆接口为 POST 请求。

在登陆表单中根据 Spring Security 提供的默认用户名和生成的密码就可以登陆了,登陆成功后会跳转到你之前访问的接口上。

以上就是 Spring Security 默认配置为我们提供的功能,就是一个典型的基于表单的登陆功能。

2. 认证异常处理

在默认的 Spring Security 登陆流程中,如果你在未登陆的情况下会被重定向到登陆页面。但在前后端分离的项目中,后端是没有登陆页面的,更不可能重定向到登陆页面。通常的做法是返回一串 JSON 格式的信息提示前端该用户没有登陆,由前端为用户跳转到登陆页面。

2.1 默认的认证异常处理

Spring Security 默认的认证异常处理在 LoginUrlAuthenticationEntryPoint 中执行,该类实现了 AuthenticationEntryPoint 接口。这个接口就是用于处理认证异常的:

public interface AuthenticationEntryPoint {
   

    // 当用户未认证时,会进入这个方法进行处理
	void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException;
}

实现类中的 commence() 方法:

public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint,
		InitializingBean {
   
            
                // 省略其他方法和字段... 

	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException, ServletException {
   

		String redirectUrl = null;

        // useForward 默认为 false
		if (useForward) {
   

			if (forceHttps && "http".equals(request.getScheme())) {
   
				redirectUrl = buildHttpsRedirectUrlForRequest(request);
			}

			if (redirectUrl == null) {
   
				String loginForm = determineUrlToUseForThisRequest(request, response,
						authException);

				if (logger.isDebugEnabled()) {
   
					logger.debug("Server side forward to: " + loginForm);
				}

				RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
				dispatcher.forward(request, response);
				return;
			}
		}
		else {
   
			redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
		}
		redirectStrategy.sendRedirect(request, response, redirectUrl);
	}
}

从上面的实现方法中可以看出,请求会被重定向到登陆页面。

所以我们主要的思路就是提供一个自定义的 AuthenticationEntryPoint 接口实现类,然后替换掉默认的 LoginUrlAuthenticationEntryPoint

2.2 自定义认证处理异常

我们知道认证异常处理是由 AuthenticationEntryPoint 接口提供的,所以我们只需实现它即可:

public class JsonAuthenticationEntryPoint implements AuthenticationEntryPoint {
   

    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
            throws IOException, ServletException {
   

        // JSON 信息
        Map<String, Object> map = new HashMap<String, Object>(3);
        map.put("code", 401);
        map.put("message", "尚未登陆");
        map.put("data", authException.getMessage());

        JSONObject json = new JSONObject(map);

        // 将 JSON 信息写入响应
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter out = null;

        try {
   
            out = response.getWriter();
            out.append(json.toString());
            out.flush();
        }catch (Exception e){
   

        }finally {
   
            if (out != null){
   
                out.close();
            }
        }
    }
}
2.3 配置认证异常处理

实现认证异常处理完成之后,我们要将它覆盖掉默认的认证异常处理。这时我们需要一个 Spring Security 配置类:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   
        // 所有请求都需要身份验证,关闭 CSRF
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .csrf().disable();

        // 配置认证异常处理
        // 因为 AuthenticationEntryPoint 是函数式接口(只有一个方法的接口),
        // 所以我们可以使用 Lambda 表达式进行实现,之前的类可以删除了。
        // 如果不使用 Lambda 表达式,就直接传入一个实现类的实例既可。
        http.exceptionHandling().authenticationEntryPoint((request, response, authException) -> {
   
            // JSON 信息
            Map<String, Object> map = new HashMap<String, Object>(3);
            map.put("code", 401);
            map.put("message", "尚未登陆");
            map.put("data", authException.getMessage());

            JSONObject json = new JSONObject(map);

            // 将 JSON 信息写入响应
            response.setCharacterEncoding("UTF-8");
            response.setContentType
  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值