SpringBoot整合Spring Security实现自定义页面以及验证码登录

文章目录

  • 前言
    • 环境版本说明
  • 后端代码
    • 1、pom.xml
    • 2、SecurityConfig .java Security配置类
    • 3、自定义处理登录成功/失败
      • DefaultAuthenticationSuccessHandler.java
      • DefaultAuthenticationFailureHandler.java
    • 4、自定义登录过滤器 - 验证码验证
    • 5、controller 部分代码
      • HelloController.java
      • VerifyCodeController.java 图片验证码接口
      • AdminController.java
      • UserController.java
  • 前端部分
    • Vue 与 axios 部分代码
    • 效果截图
    • 源码地址

前言

接上文 SpringBoot整合Spring Security

环境版本说明

MongoDB:5.0.6
SpringBoot:2.5.3
JDK:1.8

本文测试使用了 Swagger的增强工具knife4j 进行测试

SpringBoot整合Spring Security实现自定义页面以及验证码登录_第1张图片

后端代码

1、pom.xml

依赖较多 这里全部粘贴出来

 	<dependencies>
     	<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-securityartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-thymeleafartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
		
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-mongodbartifactId>
            <version>2.6.4version>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

        
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger2artifactId>
            <version>2.9.2version>
        dependency>
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger-uiartifactId>
            <version>2.9.2version>
        dependency>
        
        <dependency>
            <groupId>com.github.xiaoymingroupId>
            <artifactId>knife4j-spring-boot-starterartifactId>
            <version>2.0.2version>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-validationartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>junitgroupId>
            <artifactId>junitartifactId>
            <scope>testscope>
        dependency>

        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.79version>
        dependency>
    dependencies>

其中MongoDB 我是用来存储用户登录日志 你们可以删掉或者使用mysql 其中的swagger也是可以去掉的

2、SecurityConfig .java Security配置类

package com.king.security.config;

import com.king.security.config.login.DefaultAuthenticationFailureHandler;
import com.king.security.config.login.DefaultAuthenticationSuccessHandler;
import com.king.security.config.login.LoginFilter;
import com.king.security.service.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


/**
 * @program: springboot
 * @description:
 * @author: King
 * @create: 2022-03-13 08:28
 */
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启方法权限控制
@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserServiceImpl userService;
    @Autowired
    private DefaultAuthenticationSuccessHandler defaultAuthenticationSuccessHandler;
    @Autowired
    private DefaultAuthenticationFailureHandler defaultAuthenticationFailureHandler;
    @Autowired
    LoginFilter loginFilter;

    /**
     * 加密方式
     */
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //配置自己实现的登录认证的service,并设置密码的加密方式()
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder());
    }

    //静态资源配置
    @Override
    public void configure(WebSecurity web) throws Exception {
        //swagger2所需要用到的静态资源,允许访问
        web.ignoring().antMatchers("/swagger/**")
                .antMatchers("/swagger-ui.html")
                .antMatchers("/webjars/**")
                .antMatchers("/v2/**")
                .antMatchers("/v2/api-docs-ext/**")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/doc.html")

                .antMatchers("/lib/**")
                .antMatchers("/layer/**")
                .antMatchers("/layui/**")
                .antMatchers("/layui/css/**")
                .antMatchers("/login.html")
                .antMatchers("/index.html");

    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭跨域保护
        http.csrf().disable();
        //验证码校验
        http.addFilterBefore(loginFilter, UsernamePasswordAuthenticationFilter.class);
        //放行验证码
        http.authorizeRequests().antMatchers("/login_code.png").permitAll();
        // 指定指定要的登录页面
        http.formLogin().loginPage("/login").loginProcessingUrl("/api/user/login.do")
                .successHandler(defaultAuthenticationSuccessHandler)
                .failureHandler(defaultAuthenticationFailureHandler).permitAll();
        http.authorizeRequests().anyRequest().fullyAuthenticated();
       	http.logout().logoutUrl("logout.do");

    }
    
}

3、自定义处理登录成功/失败

DefaultAuthenticationSuccessHandler.java

自定义处理登录成功

package com.king.security.config.login;

import com.alibaba.fastjson.JSON;
import com.king.security.entity.User;
import com.king.security.entity.UserLog;
import com.king.security.mapper.UserLogMapper;
import com.king.security.vo.ResultObj;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

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

/**
 * @program: springboot
 * @description: 登录成功
 * @author: King
 * @create: 2022-03-13 08:41
 */
@Component
public class DefaultAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    UserLogMapper userLogMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication)
            throws IOException {
        User user = (User) authentication.getPrincipal();
        UserLog log = UserLog.builder().uid(user.getId()).name(user.getName()).ip(getIpAddress(request)).build();
        userLogMapper.save(log);
        logger.info("----login in succcess----");
        logger.info(log.toString());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(ResultObj.ok("登录成功!")));
    }

	//获取IP地址
    public static String getIpAddress(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

}

DefaultAuthenticationFailureHandler.java

自定义处理登录失败

package com.king.security.config.login;

import com.alibaba.fastjson.JSON;
import com.king.security.vo.ResultObj;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.*;
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;

/**
 * @program: springboot
 * @description:
 * @author: King
 * @create: 2022-03-13 08:43
 */
@Component
public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, 
                                        AuthenticationException e) throws IOException,
            ServletException {
        logger.info("login in failure : " + e.getMessage());
        ResultObj resultObj = new ResultObj();
        if (e instanceof LockedException) {
            resultObj = ResultObj.error("账户被锁定,登录失败!");
        } else if (e instanceof BadCredentialsException) {
            resultObj = ResultObj.error("账户名或密码输入错误,登录失败!");
        } else if (e instanceof DisabledException) {
            resultObj = ResultObj.error("账户被禁用,登录失败!");
        } else if (e instanceof AccountExpiredException) {
            resultObj = ResultObj.error("账户已过期,登录失败!");
        } else if (e instanceof CredentialsExpiredException) {
            resultObj = ResultObj.error("账户已过期,登录失败!");
        } else {
            resultObj = ResultObj.error(e.getMessage());
        }
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(JSON.toJSONString(resultObj));
    }




}

}

4、自定义登录过滤器 - 验证码验证

package com.king.security.config.login;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

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

/**
 * @program: springboot
 * @description: 验证码拦截器
 * @author: King
 * @create: 2022-03-12 06:16
 */
@Component
public class LoginFilter extends OncePerRequestFilter {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Autowired
    private DefaultAuthenticationFailureHandler authenticationFailureHandler;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        //如果是 登录请求 则执行
        if ((request.getMethod().equalsIgnoreCase("post") &&
                request.getRequestURI().endsWith("login.do"))) {
            try {
                validate(request, response);
            } catch (ValidateCodeException e) {
                //调用错误处理器,最终调用自己的
                authenticationFailureHandler.onAuthenticationFailure(request, response, e);
                return;//结束方法,不再调用过滤器链
            }
        }
        chain.doFilter(request, response);
    }

    public void validate(HttpServletRequest request, HttpServletResponse response) throws ValidateCodeException {
        //请求中传来的验证码
        String code = request.getParameter("login_code");
        //session 存储的验证码
        String session_Code = (String) request.getSession().getAttribute("login_code");
        logger.info("用户输入验证码:{}========session中存的验证码:{}", code, session_Code);
        if (StringUtils.isEmpty(code)) {
            throw new ValidateCodeException("验证码不能为空!");
        }
        if (StringUtils.isEmpty(session_Code)) {
            throw new ValidateCodeException("验证码已经失效!");
        }
        if (!session_Code.equalsIgnoreCase(code)) {
            throw new ValidateCodeException("验证码输入错误!");
        }

    }


    /**
     * 验证码错误异常,继承spring security的认证异常
     */
    public static class ValidateCodeException extends AuthenticationException {
        /**
         * @Fields serialVersionUID : TODO
         */
        private static final long serialVersionUID = 1L;

        public ValidateCodeException(String msg) {
            super(msg);
        }
    }
}

5、controller 部分代码

HelloController.java

package com.king.security.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @program: springboot
 * @description:
 * @author: King
 * @create: 2022-03-02 00:34
 */
@Controller
public class HelloController {
    @RequestMapping("login")
    public String openLogin() {
        return "login.html";
    }
}

VerifyCodeController.java 图片验证码接口

这里就是生成一个图片验证码传到前端 可以看看我之前的文章

package com.king.security.controller;

import com.king.security.util.VerifyCodeGen;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController()
@Api(value = "图形验证码接口", tags = "图形验证码接口")
public class VerifyCodeController {

    @ApiOperation(value = "获取登录图形验证码", tags = "图形验证码接口")
    @GetMapping(value = "/login_code.png")
    public void login(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        HttpSession session = req.getSession();
        session.setAttribute("login_code", VerifyCodeGen.outputImage(resp));
    }
}

AdminController.java

这是一个测试类 其中@PreAuthorize 表示只有 ADMIN 权限的可以访问此接口

package com.king.security.controller;

import io.swagger.annotations.Api;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: springboot
 * @description:
 * @author: King
 * @create: 2022-03-02 13:00
 */
@RestController
@RequestMapping("/api/admin")
@PreAuthorize("hasAuthority('ADMIN')")
@Api(value = "管理员操作接口",tags = "管理员操作接口")
public class AdminController {
    @GetMapping("/hello")
    public String hello(){
        return "Hello Admin!";
    }
}

UserController.java

同理 这里只有 USER 或者 ADMIN 权限可以访问

package com.king.security.controller;


import com.king.security.vo.ResultObj;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

/**
 * @program: springboot
 * @description:
 * @author: King
 * @create: 2022-03-02 13:03
 */
@RestController
@RequestMapping("/api/user")
@PreAuthorize("hasAnyRole('ADMIN','USER')")
@Api(value = "用户操作接口",tags = "用户操作接口")
public class UserController {
    @GetMapping("/hello")
    public String hello() {
        return "Hello User!";
    }

    /**
     * 获得用户登录信息
     *
     * @return
     */
    @ApiOperation(value = "获得用户登录信息", httpMethod = "GET")
    @GetMapping("/info")
    public Object info() {
        return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }

主要代码就这样全部代码可以在文末获取

前端部分

前端 使用了一些框架 所以代码有点多就不全部粘贴出来了,文章末尾发源码地址

Vue 与 axios 部分代码

 const app = new Vue({
        el: "#main",
        data: {
            name: "",
            password: "",
            validateCode: ""
        },
        methods: {
            login: function () {
                let url = "/api/user/login.do";
                let param = new URLSearchParams()
                param.append('username', this.name)
                param.append('password', this.password)
                param.append('login_code', this.validateCode);
                console.log(this.validateCode)
                axios({
                    method: 'post',
                    url: url,
                    data: param
                }).then(function (res) {
                    if (res.data.code === 1) {
                        alertLayer(res.data.msg,"index.html")
                    } else {
                        alertMy(res.data.msg)
                    }
                    console.log(res);
                }).catch(function (error) {
                    console.log(error);
                });


            }
        }
    });

效果截图

SpringBoot整合Spring Security实现自定义页面以及验证码登录_第2张图片
SpringBoot整合Spring Security实现自定义页面以及验证码登录_第3张图片
SpringBoot整合Spring Security实现自定义页面以及验证码登录_第4张图片
在这里插入图片描述

源码地址

Github https://github.com/KingJin-web/springboot

Gitee https://gitee.com/KingJin-web/springboot

你可能感兴趣的:(springboot,spring,boot,spring,java)