SpringSecurity系列(三) Spring Security 表单登录

导读:本篇文章讲解 SpringSecurity系列(三) Spring Security 表单登录,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

SpringSecurity系列(一) 初识 Spring Security
SpringSecurity系列(二) Spring Security入门

1. 服务端

1.1 依赖

<?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.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.javaboy</groupId>
    <artifactId>vms</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>vms</name>
    <description>vms project for Spring Boot</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF8</project.reporting.outputEncoding>
        <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>5.1.27</version>
        </dependency>
        <!-- druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--pageHelper-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.10</version>
        </dependency>
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.74</version>
        </dependency>
    </dependencies>

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

</project>

1.2 配置文件

server:
  port: 8090
  servlet:
    context-path: /vms
  address:

spring:
  profiles:
    active: dev
  application:
    name: vms
  servlet:
    multipart:
      maxFileSize: 100MB
      maxRequestSize: 100MB

  # 数据源配置
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://127.0.0.1:3306/vms?useUnicode=true&characterEncoding=utf-8&useSSL=false&autoReconnect=true&serverTimezone=UTC
    data-username: root
    data-password: root
    druid:
      # 初始化时建立物理连接的个数,
      initial-size: 5
      # 最小连接池数量
      min-idle: 5
      # 最大连接池数量
      max-active: 20
      # 获取连接时最大等待时间,单位毫秒
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位毫秒
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-borrow: false
      # 归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
      test-on-return: false
      # 是否缓存preparedStatement,也就是PSCache,PSCache对支持游标的数据库性能提升巨大,比如说oracle,在mysql下建议关闭。mysql5.5+建议开启
      pool-prepared-statements: true
      # 当值大于0时poolPreparedStatements会自动修改为true
      max-pool-prepared-statement-per-connection-size: 20
      # 通过别名的方式配置扩展插件: stat:监控统计,wall:防sql注入,log4j:日志
      filters: stat,wall,slf4j
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: true
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

mybatis:
  # 注意:一定要对应mapper映射xml文件的所在路径
  mapper-locations: classpath:/mapper/*Mapper.xml
  # 注意:对应实体类的路径
  type-aliases-package: com.javaboy.vms.entity
  configuration:
    map-underscore-to-camel-case: true

# 日志配置
logging:
  level:
    com.javaboy.vms.mapper: DEBUG

1.3 实体类

package com.javaboy.vms.entity;

import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.Collection;

/**
 * 用户信息(VUser)实体类
 *
 * @author gaoyang
 * @since 2021-04-20 14:26:27
 */
@Getter
@Setter
public class VUser implements Serializable, UserDetails {
    private static final long serialVersionUID = -60957006911784869L;
    /**
     * 主键
     */
    private Integer id;
    /**
     * 姓名
     */
    private String name;
    /**
     * 手机号码
     */
    private String phone;
    /**
     * 住宅电话
     */
    private String telephone;
    /**
     * 联系地址
     */
    private String address;
    /**
     * 是否启用
     */
    private Boolean enabled;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 头像
     */
    private String userface;
    /**
     * 备注
     */
    private String remark;

    /**
     * 为用户赋予角色
     *
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

}

1.4 服务实现类

package com.javaboy.vms.service.impl;

import com.javaboy.vms.mapper.VUserMapper;
import com.javaboy.vms.entity.VUser;
import com.javaboy.vms.service.VUserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

/**
 * 用户信息(VUser)表服务实现类
 *
 * @author gaoyang
 * @since 2021-04-20 14:26:28
 */
@Service("vUserService")
public class VUserServiceImpl implements VUserService, UserDetailsService {
    @Resource
    private VUserMapper vUserMapper;

    /**
     * 根据用户名加载用户对象
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        VUser user = vUserMapper.loadUserByUsername(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名不存在!");
        }
        return user;
    }

}

1.5 xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.javaboy.vms.mapper.VUserMapper">

    <resultMap type="com.javaboy.vms.entity.VUser" id="VUserMap">
        <result property="id" column="id" jdbcType="INTEGER"/>
        <result property="name" column="name" jdbcType="VARCHAR"/>
        <result property="phone" column="phone" jdbcType="VARCHAR"/>
        <result property="telephone" column="telephone" jdbcType="VARCHAR"/>
        <result property="address" column="address" jdbcType="VARCHAR"/>
        <result property="enabled" column="enabled" jdbcType="BOOLEAN"/>
        <result property="username" column="username" jdbcType="VARCHAR"/>
        <result property="password" column="password" jdbcType="VARCHAR"/>
        <result property="userface" column="userface" jdbcType="VARCHAR"/>
        <result property="remark" column="remark" jdbcType="VARCHAR"/>
    </resultMap>

    <!-- 根据用户名查询用户对象 -->
    <select id="loadUserByUsername" resultMap="VUserMap">
        select
            id, name, phone, telephone, address, enabled, username, password, userface, remark
        from vms.v_user
        where username = #{username}
    </select>

</mapper>

1.6 登录项配置

继续完善 SecurityConfig 配置类,重写 configure(HttpSecurity http) 方法:

package com.javaboy.vms.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.javaboy.vms.entity.VUser;
import com.javaboy.vms.service.impl.VUserServiceImpl;
import com.javaboy.vms.util.ResultDTO;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

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

/**
 * @author: gaoyang
 * @date: 2021-04-15 16:35
 * @description: Spring Security 配置类
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private VUserServiceImpl vUserService;

    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(vUserService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/doLogin")
                .usernameParameter("username")
                .passwordParameter("password")
                // 登录成功回调
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        VUser vUser = (VUser) authentication.getPrincipal();
                        vUser.setPassword(null);
                        ResultDTO resultDTO = ResultDTO.success("登录成功", vUser);
                        String s = new ObjectMapper().writeValueAsString(resultDTO);
                        out.write(s);
                        out.flush();
                        out.close();
                    }
                })
                // 登录失败回调
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException exception) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        ResultDTO resultDTO = ResultDTO.error("登录失败");
                        if (exception instanceof LockedException) {
                            resultDTO.setMsg("账户被锁定,请联系管理员!");
                        } else if (exception instanceof CredentialsExpiredException) {
                            resultDTO.setMsg("密码过期,请联系管理员!");
                        } else if (exception instanceof AccountExpiredException) {
                            resultDTO.setMsg("账户过期,请联系管理员!");
                        } else if (exception instanceof DisabledException) {
                            resultDTO.setMsg("账户被禁用,请联系管理员!");
                        } else if (exception instanceof BadCredentialsException) {
                            resultDTO.setMsg("用户名或者密码输入错误,请重新输入!");
                        }
                        out.write(new ObjectMapper().writeValueAsString(resultDTO));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .logout()
                // 登出回调
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        out.write(new ObjectMapper().writeValueAsString(ResultDTO.success("注销成功!")));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()
                .and()
                .csrf().disable()
                // 没有认证时,在这里处理结果,不要重定向
                .exceptionHandling().authenticationEntryPoint(new AuthenticationEntryPoint() {
            @Override
            public void commence(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                resp.setContentType("application/json;charset=utf-8");
                PrintWriter out = resp.getWriter();
                ResultDTO resultDTO = ResultDTO.error("访问失败");
                if (e instanceof InsufficientAuthenticationException) {
                    resultDTO.setMsg("请求失败,请联系管理员!");
                }
                out.write(new ObjectMapper().writeValueAsString(resultDTO));
                out.flush();
                out.close();
            }
        });

    }
}

其中:

  1. loginPage 表示定义登录页。

定义了登录页面为 /login 的时候,Spring Security 也会帮我们自动注册一个 /login 的接口,这个接口是 POST 请求,用来处理登录逻辑。

  1. loginProcessingUrl 指定登录接口地址。
  2. usernameParameter、passwordParameter 登录参数配置。

登录表单中的参数默认是 username 和 password,如果需要改变登录参数的字段名只需在这里配置即可。

  1. and 方法表示结束当前标签,上下文回到HttpSecurity,开启新一轮的配置。
  2. permitAll 表示登录相关的页面/接口不要被拦截。

1.7 登录接口

这里登录不成功返回提示信息。

package com.javaboy.vms.controller;

import com.javaboy.vms.util.ResultDTO;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author: gaoyang
 * @date: 2021-04-20 13:23
 * @description:
 */
@RestController
public class Login {

    /**
     * 未登录返回提示信息
     * @return
     */
    @RequestMapping("/login")
    public ResultDTO<Void> login(){
        return ResultDTO.error("尚未登录,请登录");
    }

}

1.8 登录测试

在这里插入图片描述

1. 客户端

web 端代码就不贴了,源码:
https://gitee.com/king-high/vms-master.git

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/5423.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!