springsecurity开发基于表单的认证--个性化用户认证流程

自定义登录页面        http.formLogin().loginPage("/signin.html") 
自定义登录成功处理     AuthenticationSuccessHandler
自定义登录失败处理     AuthenticationFailHandler

最终实现的效果是,当访问html页面时会跳转登录页面,并自定义登录成功处理以及自定义登录失败的梳理,而当访问的是非html页面请求时则抛出异常

1. 新建项目

使用idea中的spring工具创建并引入springbootspringsecruity

最终的pom.xml代码如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo-spring-secruity</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo-spring-secruity</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <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>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

编写SimpleResponse用于处理输出异常信息的接口

package com.example.demospringsecruity.support;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

/**
 *  一个简单的响应对象
 * @author john
 * @date 2020/1/6 - 16:23
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class SimpleResponse {
    private Object content;
}

2. 配置自定义登录

通过继承WebSecurityConfigurerAdapter实现自定义配置

package com.example.demospringsecruity.config;

import com.example.demospringsecruity.handle.MyAuthenticationFailureHandler;
import com.example.demospringsecruity.handle.MyAuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author john
 * @date 2020/1/6 - 10:07
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    @Autowired
    MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    //手动将PasswordEncoder注入到ioc容器中
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 表单登录
        http.formLogin()
                .loginPage("/auth/require")     //设置登录路由
                .loginProcessingUrl("/auth/form")  //设置登录处理url
                .successHandler(myAuthenticationSuccessHandler) // 设置自定义成功处理器
                .failureHandler(myAuthenticationFailureHandler) // 设置自定义失败处理器
                .and()
                .authorizeRequests()     // 身份认证设置
                .antMatchers("/signin.html").permitAll() //该路由不需要身份认账
                .antMatchers("/auth/*").permitAll() //该路由不需要身份认账
                .anyRequest()       //其他的路由均需要身份认证
                .authenticated()
                .and()
                .csrf()
                .disable();   //先禁用防止跨站脚本攻击的csrf token
    }

}

通过实现UserDetailsService接口来实现自定义用户认证逻辑

package com.example.demospringsecruity.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @author john
 * @date 2020/1/6 - 10:32
 */
@Component
@Slf4j
public class MyUserDetailsService implements UserDetailsService {
    //这里可以注入mapper或者repository的dao对象来实现数据校验逻辑操作
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("用户名:" + username);
        //这里密码应该从数据库中取出,暂时先使用加密生成
        String password = passwordEncoder.encode("123456");
        return new User(username,
                password,
                true,                 // 账户是否可用
                true,        // 账户是否过期
                true,     // 密码是否过期
                true,        //账户是否被锁定
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin") //授权集合
        );
    }
}

编写一个自定义的登录页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/auth/form" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2">
                <button type="submit">登录</button>
            </td>
        </tr>
    </table>
</form>
</body>
</html>

3. 编写访问逻辑

当访问html页面时会跳转登录页面,而当访问的是非html页面请求时则抛出异常

package com.example.demospringsecruity.controller;

import com.example.demospringsecruity.support.SimpleResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * @author john
 * @date 2020/1/6 - 16:03
 */
@RestController
@Slf4j
public class BrowserSecruityController {

    private RequestCache requestCache = new HttpSessionRequestCache();

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    //当需要身份认证时,跳转到这---如果是html页面请求跳转到登录页面,否则会被当成ajax异步请求,抛出异常
    @RequestMapping("/auth/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public SimpleResponse requireAuth(HttpServletRequest request, HttpServletResponse response) throws IOException {
        //获取前一次的跳转请求
        SavedRequest savedRequest = requestCache.getRequest(request, response);
        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            log.info("引发跳转请求的url是:" + targetUrl);
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, "/signin.html");
            }
        }

        return new SimpleResponse("访问的服务需要身份认证,请引导用户到登录页");
    }
}

4. 编写自定义登录成功处理

package com.example.demospringsecruity.handle;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;

/**
 * 自定义登录成功处理
 *
 * @author john
 * @date 2020/1/6 - 17:20
 */
@Component
@Slf4j
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("登录成功");
        //这里处理登录成功后就会json输出authentication里面的数据
        response.setContentType("application/json;charset-UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(authentication));
    }
}

5. 编写自定义登录失败处理

package com.example.demospringsecruity.handle;

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.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;

/**
 * 自定义登录失败处理
 *
 * @author john
 * @date 2020/1/6 - 18:43
 */
@Component
@Slf4j
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        log.info("登录失败");
        //这里处理登录失败后就会输出错误信息
        response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
        response.setContentType("application/json;charset-UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(e));
    }
}

6. 测试

6.1 测试访问<http://localhost:8081/aa>

6.2 测试访问<http://localhost:8081/index.html>

登录成功

登录失败

7. 代码资源

链接:https://share.weiyun.com/50cUdga 密码:k2r9ie

原文地址:https://www.cnblogs.com/ifme/p/12157999.html