Spring Security 实战(三) 自定义登录成功后的处理逻辑

1,912 阅读4分钟

开篇

上一篇我们说到如何在 spring security自定义登录处理逻辑,这一篇我们来讲一下如何自定义登录成功后的处理逻辑。

正文

先来回顾下默认情况下,登录成功过后spring security 会帮我们做些什么:

  • 未登录的情况下,我们直接访问应用中的资源,页面会自动跳转到登录页;

  • 当登录成功后,页面会自动重定向到我登录前请求的 url。

如何更改 Spring Security 的默认行为

比如, 如果我们想在登录成功后,响应一个如下的 json 响应信息给前端.

{"code":200,"message":"操作成功","data":"登录成功"}

步骤 1

首先复制上一节的项目工程 spring-security-02,重命名为 Spring-security-03。

maven 依赖不需要改变,如下所示:

    <dependencies>
	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
	
        <dependency>
            <groupId>org.springframework.boot</groupId>                           <artifactId>spring-boot-starter-web</artifactId> 
	</dependency>

    </dependencies>

步骤2

定义登录成功后的处理类 GoAuthenticationSuccessHandler 和 登陆失败后的处理类 GoAuthenticationFailureHandler

/**
  * 自定义 登录成功 处理类
  */

public class GoAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

	@Autowired
	private ObjectMapper objectMapper;
    
    /**
	  * {"code":200,"message":"操作成功","data":"登录成功"}
	  * @paramrequest
	  * @paramresponse
	  * @param authentication
	  * @throws IOException
	  * @throws ServletException
	  */
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
					throws IOException, ServletException {
		response.setHeader("Content-Type", "application/json;charset=utf-8");
		response.getWriter().print(objectMapper.writeValueAsString(CommonResult.success("登录成功")));
		response.getWriter().flush();
	}
}

package com.example.demo.config.handler;

import com.example.demo.common.CommonResult;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

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

/**
 * 登录失败处理类
 */
@Component
public class GoAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * {"code":500,"message":"登录失败:Bad credentials","data":null}
     * @param request
     * @param response
     * @param exception
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setHeader("Content-Type", "application/json;charset=utf-8");
        response.getWriter().print(objectMapper.writeValueAsString(CommonResult.failed("登录失败:" + exception.getMessage())));
        response.getWriter().flush();
    }
}

步骤3

WebSecurityConfig 配置类中,注入上面自定义的处理类 :

@Autowired
private GoAuthenticationSuccessHandler successHandler;

@Autowired
private GoAuthenticationFailureHandler failureHandler;

步骤4

protected void configure(HttpSecurity http) 方法中,追加如下代码:

	// 这些是本来就有的
	http.formLogin()
	.loginPage("/loginPage.html")// 自定义登录页
	.loginProcessingUrl("/form/login")// 自定义登录 action, 名字随便起
	// 以下是新增的
	.successHandler(successHandler)// 自定义登录成功处理类
	.failureHandler(failureHandler);// 自定义登录失败处理类

小结

从上面的代码中我们可以看到,新增了两个配置,在 successHandlerfailureHandler 方法中分别注入了一个处理.

我们着重看下 GoAuthenticationSuccessHandler 处理类, 通过重写 AuthenticationSuccessHandler 的方法,响应了一段 json 给前端。

failureHandler 是登录失败时做一些处理,在这里我们也会响应登录失败的 message 给前端。

这些响应结果可以根据实际需求自定义。

扩展

回顾一下,之前如果用户没有登录直接访问我们的应用资源,会自动跳转到登录页,如果登录成功后,去访问没有权限的 url,会给我们一段英文提示,大致意思就是没有权限。

这些也是可以定制的。比如当我们没有登录时,给客户端提示 “用户未登录”,当我们没有权限时,提示客户端 “用户没有权限”

实现步骤如下:

步骤1

定义两个处理类 GoAuthenticationEntryPoint 和 GoAccessDeniedHandler

`

/**
 * 自定义 未登录 或者 token 失效 处理类
 */
@Component
public class GoAuthenticationEntryPoint implements AuthenticationEntryPoint {

	@Autowired
	private ObjectMapper objectMapper;

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
		response.setCharacterEncoding("UTF-8");
		response.setContentType("application/json");
		response.getWriter().println(objectMapper.writeValueAsString(CommonResult.unauthorized(authException.getMessage())));
		response.getWriter().flush();
	}
}

/**
 * 自定义没有访问权限处理类
 */
@Component
public class GoAccessDeniedHandler implements AccessDeniedHandler {

	@Autowired
	private ObjectMapper objectMapper;

	/**
	 * @param request
	 * @param response
	 * @param e
	 * @throws IOException
	 * @throws ServletException
	 *
	 * @return {"code":403,"message":"没有相关权限","data":"Access is denied"}
	 */
	@Override
	public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
		response.setHeader("Content-Type", "application/json;charset=utf-8");
		response.getWriter().print(objectMapper.writeValueAsString(CommonResult.forbidden(e.getMessage())));
		response.getWriter().flush();
	}
}

`

步骤2

将自定义的两个处理类注入到 WebSecurityConfig 类中

	@Autowired
    private GoAccessDeniedHandler accessDeniedHandler;

    @Autowired
    private GoAuthenticationEntryPoint entryPoint;

步骤3

打开 WebSecurityConfig 配置类,在 configure 方法中追加如下代码:


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 此处省略一部分代码
        http.exceptionHandling()
            .accessDeniedHandler(accessDeniedHandler)// 用户没有访问权限处理器
            .authenticationEntryPoint(entryPoint);// 用户没有登录处理器
    }

总结

上面我们总共定义了四个处理类,分别作用于以下四种情况发生之后:

  1. 当我们登录成功后。
  2. 当我们登录失败后。
  3. 当我们没有登录而去访问资源时。
  4. 当我们访问没有权限的资源时。

我们可以根据实际情况选择性去定义这些处理类,根据具体需求去定义处理逻辑。详细代码可以参考本文 源代码.