(三)Spring Security 自定義登錄認證


Spring Security自定義登錄認證校驗用戶名、密碼,自定義密碼加密方式,以及在前后端分離的情況下認證失敗或成功處理返回json格式數據等

Spring Security 自定義登錄認證處理

基本環境
  1. spring-boot 2.4.1   (最新版本,如有問題,后期會調整)
  2. mybatis-plus 2.2.0   (沒有采用最新版本,后期會弄個簡單的代碼生成器)
  3. mysql
  4. maven項目

數據庫用戶信息表t_sys_user   本人采用的是5.6還是5.7版本來的,不是8.0

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_sys_user
-- ----------------------------
DROP TABLE IF EXISTS `t_sys_user`;
CREATE TABLE `t_sys_user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵ID',
  `username` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '賬號',
  `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登錄密碼',
  `nick_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '昵稱',
  `sex` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性別 0:男 1:女',
  `phone` varchar(11) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '手機號碼',
  `email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '郵箱',
  `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '頭像',
  `flag` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '狀態',
  `salt` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '鹽值',
  `token` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'token',
  `qq_oppen_id` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT 'QQ 第三方登錄Oppen_ID唯一標識',
  `pwd` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '明文密碼',
  `gmt_create` datetime(0) NULL DEFAULT NULL COMMENT '創建時間',
  `gmt_modified` datetime(0) NULL DEFAULT NULL COMMENT '更新時間',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系統管理-用戶基礎信息表' ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- Records of t_sys_user
-- ----------------------------
INSERT INTO `t_sys_user` VALUES (1, 'admin', '97ba1ef7f148b2aec1c61303a7d88d0967825495', '鄭某某', '0', '15183303003', '10086@qq.com', 'http://qzapp.qlogo.cn/qzapp/101536330/86F96F92387D69BD7659C4EC3CD6BD69/100', '1', 'zhengqing', '20820cef877355ad636b9e938c533e5cc1152e4f', '', '123456', '2019-05-05 16:09:06', '2019-10-23 17:26:30');
INSERT INTO `t_sys_user` VALUES (2, 'test', '97ba1ef7f148b2aec1c61303a7d88d0967825495', '測試號', '0', '10000', '10000@qq.com', 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif', '1', 'zhengqing', '2425fb04b4bcb140e05d22d46baa9c257ceed879', NULL, '123456', '2019-05-05 16:15:06', '2019-10-23 16:56:38');

SET FOREIGN_KEY_CHECKS = 1;

到此步需要使用的pom.xml文件

<properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.4.1</spring-boot.version>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!-- Spring Security依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- mybatis-plus begin =================================== -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>2.2.0</version>
        </dependency>

        <!-- ========================= 數據庫相關 ========================== -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- 阿里數據庫連接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.18</version>
        </dependency>

        <!-- swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.6.1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mapstruct</groupId>
                    <artifactId>mapstruct</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- swagger-ui -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.6.1</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-bean-validators</artifactId>
            <version>2.6.1</version>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- 阿里FastJson轉換工具依賴 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.13</version>
        </dependency>

        <!-- AOP依賴 【注:系統日記需要此依賴】 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!-- Hibernate Validator提供的注解進行參數校驗 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.18.Final</version>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

yml的配置

server:
  port: 8083

spring:
  application:
    name: security
  # 配置數據源
  datasource:
    # MySQL在高版本需要指明是否進行SSL連接 解決則加上 &useSSL=false
    url: jdbc:mysql://127.0.0.1:3306/white_jotter?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=UTC
    name: white_jotter
    username: root
    password: 123456
    # mysql5連接驅動
    driverClassName: com.mysql.cj.jdbc.Driver
    maxActive: 20
    initialSize: 1
    maxWait: 60000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 'x'
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    maxOpenPreparedStatements: 20
  security:
    user:
      name: admin  # 用戶名
      password: 123456  # 密碼

# 關閉安全驗證
management:
  security:
    enabled: false

# mybatis-plus相關配置
mybatis-plus:
  # xml掃描,多個目錄用逗號或者分號分隔(告訴 Mapper 所對應的 XML 文件位置)
  mapper-locations: classpath:cn/com/sercurity/cyy/**/mapper/xml/*Mapper.xml
  # 以下配置均有默認值,可以不設置
  global-config:
    #主鍵類型  0:"數據庫ID自增", 1:"用戶輸入ID",2:"全局唯一ID (數字類型唯一ID)", 3:"全局唯一ID UUID";
    id-type: 0
    #字段策略 0:"忽略判斷",1:"非 NULL 判斷"),2:"非空判斷"
    field-strategy: 2
    #駝峰下划線轉換
    db-column-underline: true
    #刷新mapper 調試神器
    refresh-mapper: false
    #數據庫大寫下划線轉換
    #capital-mode: true
    #序列接口實現類配置
    #key-generator: com.baomidou.springboot.xxx
    #邏輯刪除配置
    #logic-delete-value: 0 # 邏輯已刪除值(默認為 1)
    #logic-not-delete-value: 1 # 邏輯未刪除值(默認為 0)
    #自定義填充策略接口實現
    #    meta-object-handler: xxxxxx
    #自定義SQL注入器
    #sql-injector: com.baomidou.springboot.xxx
  configuration:
    # 是否開啟自動駝峰命名規則映射:從數據庫列名到Java屬性駝峰命名的類似映射
    map-underscore-to-camel-case: true
    cache-enabled: false
    # 如果查詢結果中包含空值的列,則 MyBatis 在映射的時候,不會映射這個字段
    #    call-setters-on-nulls: true
    # 這個配置會將執行的sql打印出來,在開發或測試的時候可以用
    #    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # 解決oracle更新數據為null時無法轉換報錯,mysql不會出現此情況
    jdbc-type-for-null: 'null'

cyy:
  swagger:
    title: 測試項目demo接口文檔
    description: 測試項目demo接口文檔
    version: 1.0.0
    termsOfServiceUrl:
    contactName:
    contactUrl:
    contactEmail:
    license:
    licenseUrl:
  #安全認證
  auth:
    # token過期時間(分鍾)
    tokenExpireTime: 60
    # 用戶選擇保存登錄狀態對應token過期時間(天)
    saveLoginTime: 7
    # 限制用戶登陸錯誤次數(次)
    loginTimeLimit: 10
    # 錯誤超過次數后多少分鍾后才能繼續登錄(分鍾)
    loginAfterTime: 10
    ignoreUrls:
      - /login
      - /api/system/user/getCurrentUserInfo
      - /index
      - /logout
      - /swagger-ui.html
      #      - /swagger-resources/**
      - /swagger-resources/configuration/ui
      - /swagger-resources
      - /v2/api-docs
      - /swagger-resources/configuration/security
      #      - /swagger/**
      #      - /**/v2/api-docs
      - /**/*.js
      - /**/*.css
      - /**/*.png
      - /**/*.ico

 

接下來的代碼可能會有些亂,在這里貼一下項目目錄吧,方便大家查看

 

t_sys_user表的實體類

import cn.com.sercurity.cyy.common.entity.BaseEntity;
import cn.com.sercurity.cyy.common.validator.Create;
import cn.com.sercurity.cyy.common.validator.FieldRepeatValidator;
import cn.com.sercurity.cyy.common.validator.Update;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import java.io.Serializable;

/**
 * <p>
 * user實體類
 * </p>
 *
 * @author cyy
 * @since 2020-12-30 13:34
 */
/**
 * <p>  系統管理-用戶基礎信息表 </p>
 *
 * @author : cyy
 * @date : 2020/12/30 15:40
 */
@Data
@ApiModel(description = "系統管理-用戶基礎信息表")
@TableName("t_sys_user")
/**
 * 對注解分組的排序,可以通脫他判斷先后順序
 * @GroupSequence({FieldRepeatValidator.class,NotNull.class, Default.class})
 */
@FieldRepeatValidator(field = "username", message = "賬號重復,請重新輸入賬號!")
public class User extends BaseEntity<User> {

    private static final long serialVersionUID = 1L;

    /**
     * 主鍵ID  groups:標識在更新的時候才能驗證非空
     */
    @ApiModelProperty(value = "主鍵ID")
    @TableId(value="id", type= IdType.AUTO)
    @NotNull(message = "用戶id不能為空", groups={Update.class})
    private Integer id;
    /**
     * 賬號
     */
    @ApiModelProperty(value = "賬號")
    @TableField("username")
    @NotBlank(message = "賬號不能為空", groups = {Create.class, Update.class})
    @Length(max = 100, message = "賬號不能超過100個字符")
    @Pattern(regexp = "^[\\u4E00-\\u9FA5A-Za-z0-9\\*]*$", message = "賬號限制:最多100字符,包含文字、字母和數字")
    private String username;
    /**
     * 登錄密碼
     */
    @ApiModelProperty(value = "登錄密碼")
    @TableField("password")
    private String password;
    /**
     * 明文密碼 - QQ第三方授權登錄時用
     */
    @ApiModelProperty(value = "明文密碼")
    @TableField("pwd")
    @NotBlank(message = "密碼不能為空")
//    @FieldRepeatValidator(className = "com.zhengqing.modules.system.entity.User", field = "pwd", message = "密碼重復!")
//    @FieldRepeatValidator(className = "com.zhengqing.modules.system.entity.User", field = "pwd", message = "密碼重復!",groups={FieldRepeatValidator.class})
    private String pwd;
    /**
     * 昵稱
     */
    @ApiModelProperty(value = "昵稱")
    @TableField("nick_name")
    @NotBlank(message = "昵稱不能為空")
    private String nickName;
    /**
     * 性別 0:男 1:女
     */
    @ApiModelProperty(value = "性別 0:男 1:女")
    @TableField("sex")
    private String sex;
    /**
     * 手機號碼
     */
    @ApiModelProperty(value = "手機號碼")
    @TableField("phone")
    @NotBlank(message = "手機號不能為空")
    @Pattern(regexp = "^[1][3,4,5,6,7,8,9][0-9]{9}$", message = "手機號格式有誤")
    private String phone;
    /**
     * 郵箱
     */
    @ApiModelProperty(value = "郵箱")
    @TableField("email")
    @NotBlank(message = "聯系郵箱不能為空")
    @Email(message = "郵箱格式不對")
    private String email;
    /**
     * 頭像
     */
    @ApiModelProperty(value = "頭像")
    @TableField("avatar")
    private String avatar;
    /**
     * 狀態
     */
    @ApiModelProperty(value = "狀態")
    @TableField("flag")
    private String flag;
    /**
     * 鹽值
     */
    @ApiModelProperty(value = "鹽值")
    @TableField("salt")
    private String salt;
    /**
     * token
     */
    @ApiModelProperty(value = "token")
    @TableField("token")
    private String token;

    @ApiModelProperty(value = "QQ 第三方登錄Oppen_ID唯一標識")
    @TableField("qq_oppen_id")
    private String qqOppenId;

    @Override
    protected Serializable pkVal() {
        return this.id;
    }

}
import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.enums.FieldFill;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

import java.util.Date;

@Getter
@Setter
public abstract class BaseEntity<T extends Model> extends BaseAddEntity<T> {
    /**
     * 修改時間 - 過去分詞表示被動更新
     */
    @ApiModelProperty(value = "修改時間")
    @TableField(value = "gmt_modified", fill = FieldFill.INSERT_UPDATE)
    private Date gmtModified;

}
import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableField;
import com.baomidou.mybatisplus.enums.FieldFill;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;

import javax.validation.constraints.Past;
import java.util.Date;

@Getter
@Setter
public abstract class BaseAddEntity<T extends Model> extends Model<T>{
    /**
     * 創建日期 - 現在時表示主動創建
     */
    @ApiModelProperty(value = "創建日期")
    @TableField(value = "gmt_create", fill = FieldFill.INSERT)
    @Past(message = "創建時間必須是過去時間")
    private Date gmtCreate;
}
import javax.validation.groups.Default;

/**
 *  <p> 使用groups的校驗 </p>
 *
 * @description : 同一個對象要復用,比如UserDTO在更新時候要校驗userId,在保存的時候不需要校驗userId,在兩種情況下都要校驗username,那就用上groups了
 * 在需要校驗的地方@Validated聲明校驗組 ` update(@RequestBody @Validated(Update.class) UserDTO userDTO) `
 * 在DTO中的字段上定義好groups = {}的分組類型     `  @NotNull(message = "用戶id不能為空", groups = Update.class)       或  groups = {Create.class, Update.class}
 *                                              private Long userId; `
 *  【注】注意:在聲明分組的時候盡量加上 extend javax.validation.groups.Default 否則,在你聲明@Validated(Update.class)的時候,就會出現你在默認沒添加groups = {}的時候的校驗組@Email(message = "郵箱格式不對"),會不去校驗,因為默認的校驗組是groups = {Default.class}.*/
public interface Create extends Default {
}
import javax.validation.groups.Default;

/**
 *  <p> 使用groups的校驗 </p>
 *
 * @description : 同一個對象要復用,比如UserDTO在更新時候要校驗userId,在保存的時候不需要校驗userId,在兩種情況下都要校驗username,那就用上groups了
 * 在需要校驗的地方@Validated聲明校驗組 ` update(@RequestBody @Validated(Update.class) UserDTO userDTO) `
 * 在DTO中的字段上定義好groups = {}的分組類型     `  @NotNull(message = "用戶id不能為空", groups = Update.class)       或  groups = {Create.class, Update.class}
 *                                              private Long userId; `
 *  【注】注意:在聲明分組的時候盡量加上 extend javax.validation.groups.Default 否則,在你聲明@Validated(Update.class)的時候,就會出現你在默認沒添加groups = {}的時候的校驗組@Email(message = "郵箱格式不對"),會不去校驗,因為默認的校驗組是groups = {Default.class}*/
public interface Update extends Default{
}
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 *  <p> 自定義字段對應數據庫內容重復校驗 注解 </p>
 *
 */
// 元注解: 給其他普通的標簽進行解釋說明 【@Retention、@Documented、@Target、@Inherited、@Repeatable】
@Documented
/**
 * 指明生命周期:
 *      RetentionPolicy.SOURCE 注解只在源碼階段保留,在編譯器進行編譯時它將被丟棄忽視。
 *      RetentionPolicy.CLASS 注解只被保留到編譯進行的時候,它並不會被加載到 JVM 中。
 *      RetentionPolicy.RUNTIME 注解可以保留到程序運行的時候,它會被加載進入到 JVM 中,所以在程序運行時可以獲取到它們。
 */
@Retention(RetentionPolicy.RUNTIME)
/**
 * 指定注解運用的地方:
 *      ElementType.ANNOTATION_TYPE 可以給一個注解進行注解
 *      ElementType.CONSTRUCTOR 可以給構造方法進行注解
 *      ElementType.FIELD 可以給屬性進行注解
 *      ElementType.LOCAL_VARIABLE 可以給局部變量進行注解
 *      ElementType.METHOD 可以給方法進行注解
 *      ElementType.PACKAGE 可以給一個包進行注解
 *      ElementType.PARAMETER 可以給一個方法內的參數進行注解
 *      ElementType.TYPE 可以給一個類型進行注解,比如類、接口、枚舉
 *      @Repeatable(LinkVals.class)(可重復注解同一字段,或者類,java1.8后支持)
 *      @author : cyy
 */
@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
@Constraint(validatedBy = FieldRepeatValidatorClass.class)
public @interface FieldRepeatValidator {

    /**
     * 實體類id字段 - 默認為id (該值可無)
     * @return
     * @author : cyy
     */
    String id() default "id";;

    /**
     * 注解屬性 - 對應校驗字段
     * @return
     * @author : cyy
     */
    String field();

    /**
     * 默認錯誤提示信息
     * @return
     * @author : cyy
     */
    String message() default "字段內容重復!";

    Class<?>[] groups() default {};
    Class<? extends Payload>[]  payload() default {};
}

 

關於實體類所涉及到的代碼如上,如果有缺少部分,請留言,會補充的

1、Security 核心配置類  配置用戶密碼校驗過濾器

import cn.com.sercurity.cyy.config.security.filter.AdminAuthenticationProcessingFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 用戶密碼校驗過濾器
     */
    private final AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter;

    public SecurityConfig(AdminAuthenticationProcessingFilter adminAuthenticationProcessingFilter) {
        this.adminAuthenticationProcessingFilter = adminAuthenticationProcessingFilter;
    }

    /**
     * 權限配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.antMatcher("/**").authorizeRequests();

        // 禁用CSRF 開啟跨域
        http.csrf().disable().cors();

        // 登錄處理 - 前后端一體的情況下
//        registry.and().formLogin().loginPage("/login").defaultSuccessUrl("/").permitAll()
//                // 自定義登陸用戶名和密碼屬性名,默認為 username和password
//                .usernameParameter("username").passwordParameter("password")
//                // 異常處理
//                .failureUrl("/login/error").permitAll()
//                // 退出登錄
//                .and().logout().permitAll();

        // 標識只能在 服務器本地ip[127.0.0.1或localhost] 訪問`/home`接口,其他ip地址無法訪問
        registry.antMatchers("/home").hasIpAddress("127.0.0.1");
        // 允許匿名的url - 可理解為放行接口 - 多個接口使用,分割
        registry.antMatchers("/login", "/index").permitAll();
        // OPTIONS(選項):查找適用於一個特定網址資源的通訊選擇。 在不需執行具體的涉及數據傳輸的動作情況下, 允許客戶端來確定與資源相關的選項以及 / 或者要求, 或是一個服務器的性能
        registry.antMatchers(HttpMethod.OPTIONS, "/**").denyAll();
        // 自動登錄 - cookie儲存方式
        registry.and().rememberMe();
        // 其余所有請求都需要認證
        registry.anyRequest().authenticated();
        // 防止iframe 造成跨域
        registry.and().headers().frameOptions().disable();

        // 自定義過濾器認證用戶名密碼
        http.addFilterAt(adminAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

2、自定義用戶密碼校驗過濾器

import cn.com.sercurity.cyy.common.util.Constants;
import cn.com.sercurity.cyy.common.util.MultiReadHttpServletRequest;
import cn.com.sercurity.cyy.config.security.login.AdminAuthenticationFailureHandler;
import cn.com.sercurity.cyy.config.security.login.AdminAuthenticationSuccessHandler;
import cn.com.sercurity.cyy.config.security.login.CusAuthenticationManager;
import cn.com.sercurity.cyy.user.entity.User;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
public class AdminAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {

    /**
     * @param authenticationManager:             認證管理器
     * @param adminAuthenticationSuccessHandler: 認證成功處理
     * @param adminAuthenticationFailureHandler: 認證失敗處理
     */
    public AdminAuthenticationProcessingFilter(CusAuthenticationManager authenticationManager, AdminAuthenticationSuccessHandler adminAuthenticationSuccessHandler, AdminAuthenticationFailureHandler adminAuthenticationFailureHandler) {
        super(new AntPathRequestMatcher("/login", "POST"));
        this.setAuthenticationManager(authenticationManager);
        this.setAuthenticationSuccessHandler(adminAuthenticationSuccessHandler);
        this.setAuthenticationFailureHandler(adminAuthenticationFailureHandler);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (request.getContentType() == null || !request.getContentType().contains(Constants.REQUEST_HEADERS_CONTENT_TYPE)) {
            throw new AuthenticationServiceException("請求頭類型不支持: " + request.getContentType());
        }

        UsernamePasswordAuthenticationToken authRequest;
        try {
            MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);
            // 將前端傳遞的數據轉換成jsonBean數據格式
            User user = JSONObject.parseObject(wrappedRequest.getBodyJsonStrByJson(wrappedRequest), User.class);
            authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), null);
            authRequest.setDetails(authenticationDetailsSource.buildDetails(wrappedRequest));
        } catch (Exception e) {
            throw new AuthenticationServiceException(e.getMessage());
        }
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

3、自定義認證管理器

import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderNotFoundException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import java.util.Objects;

@Component
public class CusAuthenticationManager implements AuthenticationManager {

    private final AdminAuthenticationProvider adminAuthenticationProvider;

    public CusAuthenticationManager(AdminAuthenticationProvider adminAuthenticationProvider) {
        this.adminAuthenticationProvider = adminAuthenticationProvider;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Authentication result = adminAuthenticationProvider.authenticate(authentication);
        if (Objects.nonNull(result)) {
            return result;
        }
        throw new ProviderNotFoundException("Authentication failed!");
    }
}
認證成功處理
import cn.com.sercurity.cyy.common.dto.ApiResult;
import cn.com.sercurity.cyy.common.util.ResponseUtils;
import cn.com.sercurity.cyy.config.security.dto.SecurityUser;
import cn.com.sercurity.cyy.user.entity.User;
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;

@Component
public class AdminAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication auth) throws IOException, ServletException {
        User user = new User();
        SecurityUser securityUser = ((SecurityUser) auth.getPrincipal());
        user.setToken(securityUser.getCurrentUserInfo().getToken());
        ResponseUtils.out(response, ApiResult.ok("登錄成功!", user));
    }
}
認證失敗處理 - 前后端分離情況下返回json數據格式
import cn.com.sercurity.cyy.common.dto.ApiResult;
import cn.com.sercurity.cyy.common.util.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
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;

@Slf4j
@Component
public class AdminAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
        ApiResult result;
        if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
            result = ApiResult.fail(e.getMessage());
        } else if (e instanceof LockedException) {
            result = ApiResult.fail("賬戶被鎖定,請聯系管理員!");
        } else if (e instanceof CredentialsExpiredException) {
            result = ApiResult.fail("證書過期,請聯系管理員!");
        } else if (e instanceof AccountExpiredException) {
            result = ApiResult.fail("賬戶過期,請聯系管理員!");
        } else if (e instanceof DisabledException) {
            result = ApiResult.fail("賬戶被禁用,請聯系管理員!");
        } else {
            log.error("登錄失敗:", e);
            result = ApiResult.fail("登錄失敗!");
        }
        ResponseUtils.out(response, result);
    }

}

全局常用變量

import java.util.HashMap;
import java.util.Map;

public class Constants {

    /**
     * 接口url
     */
    public static Map<String,String> URL_MAPPING_MAP = new HashMap<>();

    /**
     *  獲取項目根目錄
     */
    public static String PROJECT_ROOT_DIRECTORY = System.getProperty("user.dir");

    /**
     * 密碼加密相關
     */
    public static String SALT = "cyy";
    public static final int HASH_ITERATIONS = 1;

    /**
     * 請求頭 - token
     */
    public static final String REQUEST_HEADER = "X-Token";

    /**
     * 請求頭類型:
     * application/x-www-form-urlencoded : form表單格式
     * application/json : json格式
     */
    public static final String REQUEST_HEADERS_CONTENT_TYPE = "application/json";

    /**
     * 登錄者角色
     */
    public static final String ROLE_LOGIN = "role_login";

}
多次讀寫BODY用HTTP REQUEST - 解決流只能讀一次問題
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
import java.nio.charset.Charset;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

@Slf4j
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {

    private final byte[] body;

    public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        body = getBodyString(request).getBytes(Charset.forName("UTF-8"));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream bais = new ByteArrayInputStream(body);

        return new ServletInputStream() {

            @Override
            public int read() throws IOException {
                return bais.read();
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }

    /**
     * 獲取請求Body
     *
     * @param request
     * @return
     */
    private String getBodyString(ServletRequest request) {
        StringBuilder sb = new StringBuilder();
        InputStream inputStream = null;
        BufferedReader reader = null;
        try {
            inputStream = request.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));
            String line = "";
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sb.toString();
    }

    /**
     * 將前端請求的表單數據轉換成json字符串 - 前后端一體的情況下使用
     * @param request:
     * @return: java.lang.String
     */
    public String getBodyJsonStrByForm(ServletRequest request){
        Map<String, Object> bodyMap = new HashMap<>(16);
        try {
            // 參數定義
            String paraName = null;
            // 獲取請求參數並轉換
            Enumeration<String> e = request.getParameterNames();
            while (e.hasMoreElements()) {
                paraName = e.nextElement();
                bodyMap.put(paraName, request.getParameter(paraName));
            }
        } catch(Exception e) {
            log.error("請求參數轉換錯誤!",e);
        }
        return JSONObject.toJSONString(bodyMap);
    }

    /**
     * 將前端傳遞的json數據轉換成json字符串 - 前后端分離的情況下使用
     * @param request:
     * @return: java.lang.String
     */
    public String getBodyJsonStrByJson(ServletRequest request){
        StringBuffer json = new StringBuffer();
        String line = null;
        try {
            BufferedReader reader = request.getReader();
            while((line = reader.readLine()) != null) {
                json.append(line);
            }
        }
        catch(Exception e) {
            log.error("請求參數轉換錯誤!",e);
        }
        return json.toString();
    }

}
 API返回參數
import cn.com.sercurity.cyy.common.enumeration.ResultCode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@ApiModel(value = "API返回參數")
public class ApiResult {
    /**
     * 消息內容
     */
    @ApiModelProperty(value = "響應消息", required = false)
    private String message;

    /**
     * 響應碼:參考`ResultCode`
     */
    @ApiModelProperty(value = "響應碼", required = true)
    private Integer code;

    /**
     * 響應中的數據
     */
    @ApiModelProperty(value = "響應數據", required = false)
    private Object data;

    /***
     * 過期
     *
     * @param message:
*/
    public static ApiResult expired(String message) {
        return new ApiResult(ResultCode.UN_LOGIN.getCode(), message, null);
    }

    public static ApiResult fail(String message) {
        return new ApiResult(ResultCode.FAILURE.getCode(), message, null);
    }

    /***
     * 自定義錯誤返回碼
     *
     * @param code
     * @param message:
*/
    public static ApiResult fail(Integer code, String message) {
        return new ApiResult(code, message, null);
    }

    public static ApiResult ok(String message) {
        return new ApiResult(ResultCode.SUCCESS.getCode(), message, null);
    }

    public static ApiResult ok() {
        return new ApiResult(ResultCode.SUCCESS.getCode(), "OK", null);
    }

    public static ApiResult build(Integer code, String msg, Object data) {
        return new ApiResult(ResultCode.SUCCESS.getCode(), msg, data);
    }

    public static ApiResult ok(String message, Object data) {
        return new ApiResult(ResultCode.SUCCESS.getCode(), message, data);
    }

    /**
     * 自定義返回碼
     */
    public static ApiResult ok(Integer code, String message) {
        return new ApiResult(code, message);
    }

    /**
     * 自定義
     *
     * @param code:驗證碼
     * @param message:返回消息內容
     * @param data:返回數據
*/
    public static ApiResult ok(Integer code, String message, Object data) {
        return new ApiResult(code, message, data);
    }

    public ApiResult() { }

    public static ApiResult build(Integer code, String msg) {
        return new ApiResult(code, msg, null);
    }

    public ApiResult(Integer code, String msg, Object data) {
        this.code = code;
        this.message = msg;
        this.data = data;
    }

    public ApiResult(Object data) {
        this.code = ResultCode.SUCCESS.getCode();
        this.message = "OK";
        this.data = data;
    }

    public ApiResult(String message) {
        this(ResultCode.SUCCESS.getCode(), message, null);
    }

    public ApiResult(String message, Integer code) {
        this.message = message;
        this.code = code;
    }

    public ApiResult(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}
響應碼枚舉
public enum ResultCode {

    //成功
    SUCCESS( 200, "SUCCESS" ),
    //失敗
    FAILURE( 400, "FAILURE" ),
    // 未登錄
    UN_LOGIN( 401, "未登錄" ),
    //未認證(簽名錯誤、token錯誤)
    UNAUTHORIZED( 403, "未認證或Token失效" ),
    //未通過認證
    USER_UNAUTHORIZED( 402, "用戶名或密碼不正確" ),
    //接口不存在
    NOT_FOUND( 404, "接口不存在" ),
    //服務器內部錯誤
    INTERNAL_SERVER_ERROR( 500, "服務器內部錯誤" );

    private int code;
    private String desc;

    ResultCode(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }
}
使用response輸出JSON
import cn.com.sercurity.cyy.common.dto.ApiResult;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
public class ResponseUtils {

    /**
     * 使用response輸出JSON
     *
     * @param response
     * @param result
     */
    public static void out(ServletResponse response, ApiResult result) {
        PrintWriter out = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            out = response.getWriter();
            out.println(JSON.toJSONString(result));
        } catch (Exception e) {
            log.error(e + "輸出JSON出錯");
        } finally {
            if (out != null) {
                out.flush();
                out.close();
            }
        }
    }

    /**
     * 響應內容
     * @param httpServletResponse
     * @param msg
     * @param status
     */
    public static void getResponse(HttpServletResponse httpServletResponse, String msg, Integer status){
        PrintWriter writer = null;
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try {
            writer = httpServletResponse.getWriter();
            writer.print(JSONObject.toJSONString(new ApiResult(status,msg,null)));
        } catch (IOException e) {
            log.error("響應報錯", e.getMessage());
        } finally {
            if (writer != null){
                writer.close();
            }
        }
    }

}

4、自定義認證處理

import cn.com.sercurity.cyy.common.util.PasswordUtils;
import cn.com.sercurity.cyy.config.security.dto.SecurityUser;
import cn.com.sercurity.cyy.config.security.service.impl.UserDetailsServiceImpl;
import cn.com.sercurity.cyy.user.entity.User;
import cn.com.sercurity.cyy.user.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

@Component
public class AdminAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    UserDetailsServiceImpl userDetailsService;
    @Autowired
    private UserMapper userMapper;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        // 獲取前端表單中輸入后返回的用戶名、密碼
        String userName = (String) authentication.getPrincipal();
        String password = (String) authentication.getCredentials();

        SecurityUser userInfo = (SecurityUser) userDetailsService.loadUserByUsername(userName);

        boolean isValid = PasswordUtils.isValidPassword(password, userInfo.getPassword(), userInfo.getCurrentUserInfo().getSalt());
        // 驗證密碼
        if (!isValid) {
            throw new BadCredentialsException("密碼錯誤!");
        }

        // 前后端分離情況下 處理邏輯...
        // 更新登錄令牌 - 之后訪問系統其它接口直接通過token認證用戶權限...
        String token = PasswordUtils.encodePassword(System.currentTimeMillis() + userInfo.getCurrentUserInfo().getSalt(), userInfo.getCurrentUserInfo().getSalt());
        User user = userMapper.selectById(userInfo.getCurrentUserInfo().getId());
        user.setToken(token);
        userMapper.updateById(user);
        userInfo.getCurrentUserInfo().setToken(token);
        return new UsernamePasswordAuthenticationToken(userInfo, password, userInfo.getAuthorities());
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

加密處理

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.codec.Hex;

import java.security.MessageDigest;

@Slf4j
public class PasswordUtils {

    /**
     * 校驗密碼是否一致
     *
     * @param password: 前端傳過來的密碼
     * @param hashedPassword:數據庫中儲存加密過后的密碼
     * @param salt:鹽值
     * @return
     */
    public static boolean isValidPassword(String password, String hashedPassword, String salt) {
        return hashedPassword.equalsIgnoreCase(encodePassword(password, salt));
    }

    /**
     * 通過SHA1對密碼進行編碼
     *
     * @param password:密碼
     * @param salt:鹽值
     * @return
     */
    public static String encodePassword(String password, String salt) {
        String encodedPassword;
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            if (salt != null) {
                digest.reset();
                digest.update(salt.getBytes());
            }
            byte[] hashed = digest.digest(password.getBytes());
            int iterations = Constants.HASH_ITERATIONS - 1;
            for (int i = 0; i < iterations; ++i) {
                digest.reset();
                hashed = digest.digest(hashed);
            }
            encodedPassword = new String(Hex.encode(hashed));
        } catch (Exception e) {
            log.error("驗證密碼異常:", e);
            return null;
        }
        return encodedPassword;
    }
}

安全認證用戶詳情

import cn.com.sercurity.cyy.user.entity.User;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

@Data
@Slf4j
public class SecurityUser implements UserDetails {
    /**
     * 當前登錄用戶
     */
    private transient User currentUserInfo;

    public SecurityUser() {
    }

    public SecurityUser(User user) {
        if (user != null) {
            this.currentUserInfo = user;
        }
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority("admin");
        authorities.add(authority);
        return authorities;
    }

    @Override
    public String getPassword() {
        return currentUserInfo.getPassword();
    }

    @Override
    public String getUsername() {
        return currentUserInfo.getUsername();
    }

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

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

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

    @Override
    public boolean isEnabled() {
        return true;
    }
}
自定義userDetailsService - 認證用戶詳情
import cn.com.sercurity.cyy.config.security.dto.SecurityUser;
import cn.com.sercurity.cyy.user.entity.User;
import cn.com.sercurity.cyy.user.mapper.UserMapper;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import org.springframework.beans.factory.annotation.Autowired;
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 org.springframework.util.CollectionUtils;

import java.util.List;

@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    /***
     * 根據賬號獲取用戶信息
     * @param username:
     * @return: org.springframework.security.core.userdetails.UserDetails
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 從數據庫中取出用戶信息
        List<User> userList = userMapper.selectList(new EntityWrapper<User>().eq("username", username));
        User user;
        // 判斷用戶是否存在
        if (!CollectionUtils.isEmpty(userList)){
            user = userList.get(0);
        } else {
            throw new UsernameNotFoundException("用戶名不存在!");
        }
        // 返回UserDetails實現類
        return new SecurityUser(user);
    }
}
import cn.com.sercurity.cyy.user.entity.User;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;

/**
 *
 * <p> 系統管理-用戶基礎信息表 Mapper 接口 </p>
 **/
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

前端頁面

這里2個簡單的html頁面模擬前后端分離情況下登陸處理場景

1、登陸頁

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<h1>Spring Security</h1>
<form method="post" action="" onsubmit="return false">
    <div>
        用戶名:<input type="text" name="username" id="username">
    </div>
    <div>
        密碼:<input type="password" name="password" id="password">
    </div>
    <div>
        <!--        <label><input type="checkbox" name="remember-me" id="remember-me"/>自動登錄</label>-->
        <button onclick="login()">登陸</button>
    </div>
</form>
</body>
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
    function login() {
        var username = document.getElementById("username").value;
        var password = document.getElementById("password").value;
        // var rememberMe = document.getElementById("remember-me").value;
        $.ajax({
            async: false,
            type: "POST",
            dataType: "json",
            url: '/login',
            contentType: "application/json",
            data: JSON.stringify({
                "username": username,
                "password": password
                // "remember-me": rememberMe
            }),
            success: function (result) {
                console.log(result)
                if (result.code == 200) {
                    alert("登陸成功");
                    window.location.href = "../home.html";
                } else {
                    alert(result.message)
                }
            }
        });
    }
</script>
</html>
2、首頁

home.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>您好,登陸成功</h3>
<button onclick="window.location.href='/logout'">退出登錄</button>
</body>
</html>

測試接口

import cn.com.sercurity.cyy.common.dto.ApiResult;
import cn.com.sercurity.cyy.common.util.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@RestController
public class IndexController {

    @GetMapping("/")
    public ModelAndView showHome() {
        return new ModelAndView("home.html");
    }

    @GetMapping("/index")
    public String index() {
        return "Hello World ~";
    }

    @GetMapping("/login")
    public ModelAndView login() {
        return new ModelAndView("login.html");
    }

    @GetMapping("/home")
    public String home() {
        String name = SecurityContextHolder.getContext().getAuthentication().getName();
        log.info("登陸人:" + name);
        return "Hello~ " + name;
    }

    @GetMapping(value ="/admin")
    // 訪問路徑`/admin` 具有`crud`權限
    @PreAuthorize("hasPermission('/admin','crud')")
    public String admin() {
        return "Hello~ 管理員";
    }

    @GetMapping("/test")
//    @PreAuthorize("hasPermission('/test','t')")
    public String test() {
        return "Hello~ 測試權限訪問接口";
    }

    /**
     * 登錄異常處理 - 前后端一體的情況下
     * @param request
     * @param response
     */
    @RequestMapping("/login/error")
    public void loginError(HttpServletRequest request, HttpServletResponse response) {
        AuthenticationException e = (AuthenticationException) request.getSession().getAttribute("SPRING_SECURITY_LAST_EXCEPTION");
        log.error(e.getMessage());
        ResponseUtils.out(response, ApiResult.fail(e.getMessage()));
    }
}
使用response輸出JSON
import cn.com.sercurity.cyy.common.dto.ApiResult;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Slf4j
public class ResponseUtils {

    /**
     * 使用response輸出JSON
     *
     * @param response
     * @param result
     */
    public static void out(ServletResponse response, ApiResult result) {
        PrintWriter out = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            out = response.getWriter();
            out.println(JSON.toJSONString(result));
        } catch (Exception e) {
            log.error(e + "輸出JSON出錯");
        } finally {
            if (out != null) {
                out.flush();
                out.close();
            }
        }
    }

    /**
     * 響應內容
     * @param httpServletResponse
     * @param msg
     * @param status
     */
    public static void getResponse(HttpServletResponse httpServletResponse, String msg, Integer status){
        PrintWriter writer = null;
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        try {
            writer = httpServletResponse.getWriter();
            writer.print(JSONObject.toJSONString(new ApiResult(status,msg,null)));
        } catch (IOException e) {
            log.error("響應報錯", e.getMessage());
        } finally {
            if (writer != null){
                writer.close();
            }
        }
    }

}

測試訪問效果

數據庫賬號:admin 密碼:123456

1. 輸入錯誤用戶名提示該用戶不存在

 

2. 輸入錯誤密碼提示密碼錯誤

 

 3. 輸入正確用戶名和賬號,提示登陸成功,然后跳轉到首頁

 

 

 

 登陸成功后即可正常訪問其他接口,如果是未登錄情況下將訪問不了

 

總結

  1. Spring Security核心配置類中設置自定義的用戶密碼校驗過濾器(AdminAuthenticationProcessingFilter)
  2. 在自定義的用戶密碼校驗過濾器中配置認證管理器(CusAuthenticationManager)認證成功處理(AdminAuthenticationSuccessHandler)認證失敗處理(AdminAuthenticationFailureHandler)
  3. 在自定義的認證管理器中配置自定義的認證處理(AdminAuthenticationProvider)
  4. 然后就是在認證處理中實現自己的相應業務邏輯等
 
如果缺少什么了,歡迎大家留言,我會及時補充的



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM