02 spring security 自定义用户认证流程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了02 spring security 自定义用户认证流程相关的知识,希望对你有一定的参考价值。

1. 自定义登录页面

(1)首先在static目录下面创建login.html

       注意: springboot项目默认可以访问resources/resources, resources/staic, resources/public目录下面的静态文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<form action="/auth/login" method="post">
    用户名:<input type="text" name="username">
    <br/>&emsp;码:<input type="password" name="password">
    <br/>
    <input type="submit" value="登录">
</form>
</body>
</html>

 

(2) 在spring securiy 配置类中做如下配置

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                // 指定自定义登录页面
                .loginPage("/login.html")
                // 登录url
                .loginProcessingUrl("/auth/login")
                .and()
                .authorizeRequests()
                // 添加一个url匹配器,如果匹配到login.html,就授权
                .antMatchers("/login.html").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                // 关闭spring security默认的防csrf攻击
                .csrf().disable();
    }

(3) 测试

 

(4) 存在的问题

 <1> 作为可以复用的登录模块,我们应该提供个性化的登录页面,也就是说不能写死只跳转到login.html。

    此问题比较好解决,使用可配置的登录页面,默认使用login.html即可。

 <2> 请求跳转到login.html登录页面,貌似没有什么问题,但作为restful风格的接口,一般响应的都是json数据格式,尤其是app请求。

    解决思想: 用户发起数据请求 --> security判断是否需要身份认证 -----> 跳转到一个自定义的controller方法 ------> 在该方法内判断是否是html发起的请求,如果是,就跳转到login.html,如果不是,响应一个json格式的数据,说明错误信息。

 

自定义Controller

@Slf4j
@RestController
public class LoginController {

    /**
     * 请求缓存
     */
    private RequestCache requestCache = new HttpSessionRequestCache();

    /**
     * 重定向工具类
     */
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    /**
     * 如果配置的登录页就使用配置的登录面,否则使用默认的登录页面
     */
//    @Value("${xxxx:defaultLoginPage}")
//    private String standardLoginPage;
    private String standardLoginPage = "/login.html";  // 登录页

    /**
     * 用户身份认证方法
     */
    @GetMapping("/user/auth")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)  // 返回状态
    public ResponseData login(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            log.info("请求是:" + targetUrl);
            // 如果请求是以html结尾
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, standardLoginPage);
            }
        }
        return new ResponseData("该请求需要登录,js拿到我的响应数据后,是否需要跳转到登录页面你自己看着办吧?");
    }
}

 

spring security给该controller的login方法授权

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                // 先进controller中去
                .loginPage("/user/auth")
                // 指定自定义登录页面
                .loginPage("/login.html")
                // 登录url
                .loginProcessingUrl("/auth/login")
                .and()
                .authorizeRequests()
                // 该controller需要授权
                .antMatchers("/user/auth").permitAll()
                // 添加一个url匹配器,如果匹配到login.html,就授权
                .antMatchers("/login.html").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                // 关闭spring security默认的防csrf攻击
                .csrf().disable();
    }

这样子就行了!!!

   

2.  自定义登录成功处理(返回json)

 (1)实现AuthenticationSuccessHandler.java

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;
    /**
     * Called when a user has been successfully authenticated.
     * @param request
     * @param response
     * @param authentication
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("登录成功!!!");
        // 将登录成功的信息写到前端
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.getWriter().write(objectMapper.writeValueAsString(authentication));

    }
}

 

(2)修改security 配置类

    @Autowired
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                // 先进controller中去
                .loginPage("/user/auth")
                // 指定自定义登录页面
                .loginPage("/login.html")
                // 登录url
                .loginProcessingUrl("/auth/login")
                .successHandler(myAuthenticationSuccessHandler)
                .and()
                .authorizeRequests()
                // 该controller需要授权
                .antMatchers("/user/auth").permitAll()
                // 添加一个url匹配器,如果匹配到login.html,就授权
                .antMatchers("/login.html").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                // 关闭spring security默认的防csrf攻击
                .csrf().disable();
    }

 

(3)测试

 

 说明: authentication对象中包含的信息,会因为登录方式的不同而发生改变

 

3. 自定义登录失败处理(返回json)

  实现AuthenticationFailureHandler.java 接口即可,跟登录成败处理配置一样。

 

4. 自定义登录成功处理逻辑

 以上的登录成功或失败的返回的都是json,但是在某些情况下,就是存在着登录成功或者失败进行页面跳转(spring security默认的处理方式),那么这种返回json的方式就不合适了。 所以,我们应该做得更灵活,做成可配置的。

 对于登录成功逻辑而言只需要对MyAuthenticationSuccessHandler.java稍做修改就行,代码如下所示:

/**
 * SavedRequestAwareAuthenticationSuccessHandler spring security 默认的成功处理器
 */
@Slf4j
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 配置的登录方式
     */
//    @Value("${xxx:默认方式}")
    private String loginType = "JSON";
    /**
     * Called when a user has been successfully authenticated.
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("登录成功!!!");

        // 如果配置的登录方式是JSON,就返回json数据
        if ("JSON".equals(loginType)) {
            // 将登录成功的信息写到前端
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        } else {  // 否则就使用默认的跳转方式
            super.onAuthenticationSuccess(request,response,authentication);
        }
    }
}

 

5. 自定义登录失败处理逻辑

 同登录成功类似,具体代码如下:

 

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Slf4j
@Component
public class MySimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 配置的登录方式
     */
//    @Value("${xxx:默认方式}")
    private String loginType = "JSON";
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("登录失败!!!");

        // 如果配置的登录方式是JSON,就返回json数据
        if ("JSON".equals(loginType)) {
            // 将登录成功的信息写到前端
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            response.getWriter().write(objectMapper.writeValueAsString(exception));
        } else {  // 否则就使用默认的跳转方式,跳转到一个错误页面
            super.onAuthenticationFailure(request,response,exception);
        }
    }
}

 

 @Autowired
    private MySimpleUrlAuthenticationFailureHandler mySimpleUrlAuthenticationFailureHandler;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                // 先进controller中去
                .loginPage("/user/auth")
                // 指定自定义登录页面
                .loginPage("/login.html")
                // 登录url
                .loginProcessingUrl("/auth/login")
                .successHandler(myAuthenticationSuccessHandler)
                .failureHandler(mySimpleUrlAuthenticationFailureHandler)
                .and()
                .authorizeRequests()
                // 该controller需要授权
                .antMatchers("/user/auth").permitAll()
                // 添加一个url匹配器,如果匹配到login.html,就授权
                .antMatchers("/login.html").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                // 关闭spring security默认的防csrf攻击
                .csrf().disable();
    }

 

以上是关于02 spring security 自定义用户认证流程的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security - 来自 JWT 的自定义主体

可以使用 Spring Security 实时加载自定义配置文件吗?

Spring security - 自定义保持活动控制器

来自自定义表的 Spring Security 身份验证

使用 OAuth2 自定义来自 Spring Security 的身份验证错误

Spring Security自定义用户认证