SpringSecurity系列——登录流程修改day5-1(源于官网5.7.2版本)

181 篇文章 3 订阅
24 篇文章 31 订阅

前言

技术版本
jdk17
SpringSecurity5.7.2
SpringBoot2.7.2

修改登录用户名密码

注意点

首先我们要知道,在新的SpringSecurity中我们不需要继承WebSecurityConfigurerAdapter,之前在修改登录的用户名密码我们知道应该去修改AuthenticationManager,进行覆盖或重写,但也可以直接已注入Bean的形式改写UserDetailsService如下:

采用基于内存的实现方式

package com.example.test1.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
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.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
public class SpringSecurityConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        inMemoryUserDetailsManager.createUser(User.withUsername("user").password("{noop}12345").roles("admin").build());
        return inMemoryUserDetailsManager;
    }
    

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests(auth -> auth.anyRequest().authenticated())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults())
                .userDetailsService(userDetailsService());
        return http.build();
    }
}

如此一来我们就可以使用自定义的用户名密码登录了
登录用户名:user
密码:12345
在后续新特性中这种方式依然可以使用
在这里插入图片描述

新特性更新讲解(这里我不是很确定,因为我上手的时候就已经是新特性了)

在这里大家注意一个地方就是

在这里插入图片描述
我们来看一下原始的
在这里插入图片描述

在这里插入图片描述
可以发现原始的是重写了方法达到覆盖的效果,直接在HttpSecurity中可以直接设置重写,使得配置更加简单

自定义数据源的认证

解读UserDetails

层次结构

在这里插入图片描述

源码解读

public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

在UserDetsils接口中规范的基本属性:

  1. 用户权限列表:Collection<? extends GrantedAuthority> getAuthorities();
  2. 用户名:String getUsername();
  3. 密码:String getPassword();
  4. 账户是否过期:boolean isAccountNonExpired();
  5. 账户是否被锁定:boolean isAccountNonLocked();
  6. 凭证是否过期:boolean isCredentialsNonExpired();
  7. 是否开启使用:boolean isEnabled();

解读User

实现了UserDetails接口

所属包

package org.springframework.security.core.userdetails;

属性

private static final long serialVersionUID = 570L;
private static final Log logger = LogFactory.getLog(User.class);
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;

除了在UserDetails的属性

  1. serialVersionUID:版本UUID
  2. logger:日志信息

注意password我们要指定加密方式
这也就提醒我们数据库的设计方式

简单数据库设计

User表

在这里插入图片描述

authorities表

在这里插入图片描述

sql文件
-- ----------------------------
-- Table structure for authorities
-- ----------------------------
DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities`  (
  `userId` bigint(20) NOT NULL,
  `authority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '权限角色',
  `expired` tinyint(4) NULL DEFAULT NULL COMMENT '账户过期',
  `tokenExpired` tinyint(4) NULL DEFAULT NULL COMMENT '凭证过期',
  `locaked` tinyint(4) NULL DEFAULT NULL COMMENT '账户锁定',
  UNIQUE INDEX `ix_auth_username`(`userId`, `authority`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `userId` bigint(20) NOT NULL,
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `password` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `enabled` tinyint(1) NOT NULL COMMENT '0表示false1表示true',
  PRIMARY KEY (`userId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

RBAC数据库设计

在这里插入图片描述

sql文件
/*
 Navicat Premium Data Transfer

 Source Server         : mysql_syf
 Source Server Type    : MySQL
 Source Server Version : 80017
 Source Host           : localhost:3306
 Source Schema         : spring_security_test

 Target Server Type    : MySQL
 Target Server Version : 80017
 File Encoding         : 65001

 Date: 24/07/2022 10:18:44
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for authorities
-- ----------------------------
DROP TABLE IF EXISTS `authorities`;
CREATE TABLE `authorities`  (
  `authorityId` bigint(20) NOT NULL,
  `authority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '权限角色',
  `address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`authorityId`) USING BTREE,
  UNIQUE INDEX `ix_auth_username`(`authorityId`, `authority`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of authorities
-- ----------------------------
INSERT INTO `authorities` VALUES (1, '登录', '/login');
INSERT INTO `authorities` VALUES (2, '注册', '/register');
INSERT INTO `authorities` VALUES (3, '获取用户头像', '/getAvatar');

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `roleId` bigint(20) NOT NULL COMMENT '角色ID',
  `roleName` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名',
  `roleAnnotation` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '注释',
  PRIMARY KEY (`roleId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_guest', '游客');
INSERT INTO `role` VALUES (2, 'ROLE_admin', '管理员');
INSERT INTO `role` VALUES (3, 'ROLE_user', '普通用户');

-- ----------------------------
-- Table structure for role_authority_con
-- ----------------------------
DROP TABLE IF EXISTS `role_authority_con`;
CREATE TABLE `role_authority_con`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `authorityId` bigint(20) NULL DEFAULT NULL,
  `roleId` bigint(20) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_authority_con
-- ----------------------------

-- ----------------------------
-- Table structure for user_role_con
-- ----------------------------
DROP TABLE IF EXISTS `user_role_con`;
CREATE TABLE `user_role_con`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userId` bigint(20) NOT NULL,
  `roleId` bigint(20) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role_con
-- ----------------------------
INSERT INTO `user_role_con` VALUES (1, 1, 2);
INSERT INTO `user_role_con` VALUES (2, 1, 3);
INSERT INTO `user_role_con` VALUES (3, 2, 1);

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users`  (
  `userId` bigint(20) NOT NULL,
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `password` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `enabled` tinyint(1) NOT NULL COMMENT '0表示false1表示true',
  `expired` tinyint(1) NULL DEFAULT NULL,
  `tokenExpired` tinyint(1) NULL DEFAULT NULL,
  `locked` tinyint(1) NULL DEFAULT NULL,
  PRIMARY KEY (`userId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES (1, 'user1', '{noop}123', 1, 1, 1, 1);
INSERT INTO `users` VALUES (2, 'user2', '{noop}456', 1, 1, 1, 1);

SET FOREIGN_KEY_CHECKS = 1;

1.编写entity实体类

1.1编写Role,UserRoleCon

Role
package com.example.test1.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "role")
public class Role {
    @TableId(value = "roleId")
    private long roleId;
    @TableField(value = "roleName")
    private String roleName;
    @TableField(value = "roleAnnotation")
    private String roleAnnotation;
}

UserRoleCon
package com.example.test1.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "user_role_con")
public class UserRoleCon {
    private int id;
    @TableField(value = "userId")
    private long userId;
    @TableField(value = "roleId")
    private long roleId;
}

1.2User类实现UserDetails接口(重要)

我们的User实体类要直接实现UserDetails接口完成重写

重要部分

这一部分我们需要基于GrantedAuthority的实现SimpleGrantedAuthority构建出一个用户角色权限集合(可以选择Set、List)

@Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        HashSet<SimpleGrantedAuthority> authorities = new HashSet<>();
        roles.forEach(x->{
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(x.getRoleName());
            authorities.add(simpleGrantedAuthority);
        });
        return authorities;
    }
package com.example.test1.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName(value = "users")
public class Users implements UserDetails {
    @TableId(value = "userId")
    private long userId;

    private String username;
    private String password;
    private boolean enabled;
    private boolean expired;
    @TableField(value = "tokenExpired")
    private boolean tokenExpired;
    private boolean locked;
    @TableField(exist = false)
    private ArrayList<Role> roles = new ArrayList<>();


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        HashSet<SimpleGrantedAuthority> authorities = new HashSet<>();
        roles.forEach(x->{
            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(x.getRoleName());
            authorities.add(simpleGrantedAuthority);
        });
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

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

    public long getUserId() {
        return userId;
    }

    public void setUserId(long userId) {
        this.userId = userId;
    }

    public ArrayList<Role> getRoles() {
        return roles;
    }

    public void setRoles(ArrayList<Role> roles) {
        this.roles = roles;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setExpired(boolean expired) {
        this.expired = expired;
    }

    public void setTokenExpired(boolean tokenExpired) {
        this.tokenExpired = tokenExpired;
    }

    public void setLocked(boolean locked) {
        this.locked = locked;
    }
}

2.编写Mapper

UserMapper

package com.example.test1.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.test1.entity.Users;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<Users> {
}

RoleMapper

package com.example.test1.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.test1.entity.Role;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}

UserRoleConMapper

package com.example.test1.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.test1.entity.UserRoleCon;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserRoleConMapper extends BaseMapper<UserRoleCon> {
}

3.编写UserDetailsServiceImpl实现UserDetailsService接口

重要部分

重写loadUserByUsername方法,要求传入用户的用户名(这里的用户名即是认证表单传入的),通过用户名获取用户的信息,最终得到用户角色信息,即从UserDetailsService中查找UserDetails,用户名密码验证成功则返回UsernamePasswordAuthenticationToken类型,其中就包含了UserDetails和Authorities(流程讲解

@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //获取用户角色
        Users user = this.defineLoadUserByUsername(username);
        if (Objects.isNull(user)) {
            throw new UsernameNotFoundException("用户名无法获取");
        }
        //获取权限信息
        ArrayList<Role> roleArrayList = this.getRoleByUserId(user.getUserId());
        user.setRoles(roleArrayList);
        return user;
    }
package com.example.test1.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.test1.entity.Role;
import com.example.test1.entity.UserRoleCon;
import com.example.test1.entity.Users;
import com.example.test1.mapper.RoleMapper;
import com.example.test1.mapper.UserMapper;
import com.example.test1.mapper.UserRoleConMapper;
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 java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserMapper userMapper;
    private final RoleMapper roleMapper;
    private final UserRoleConMapper userRoleConMapper;

    @Autowired
    public UserDetailsServiceImpl(UserMapper userMapper, RoleMapper roleMapper, UserRoleConMapper userRoleConMapper) {
        this.userMapper = userMapper;
        this.userRoleConMapper = userRoleConMapper;
        this.roleMapper = roleMapper;
    }


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //获取用户角色
        Users user = this.defineLoadUserByUsername(username);
        if (Objects.isNull(user)) {
            throw new UsernameNotFoundException("用户名无法获取");
        }
        //获取权限信息
        ArrayList<Role> roleArrayList = this.getRoleByUserId(user.getUserId());
        user.setRoles(roleArrayList);
        return user;
    }


    public Users defineLoadUserByUsername(String username) {
        LambdaQueryWrapper<Users> usersLambdaQueryWrapper = new LambdaQueryWrapper<>();
        usersLambdaQueryWrapper.eq(Users::getUsername, username).last("limit 1");
        return userMapper.selectOne(usersLambdaQueryWrapper);
    }


    public ArrayList<Role> getRoleByUserId(long userId) {
        //获取roleId
        LambdaQueryWrapper<UserRoleCon> userRoleConLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userRoleConLambdaQueryWrapper.eq(UserRoleCon::getUserId, userId).select(UserRoleCon::getRoleId);
        List<UserRoleCon> userRoleCons = userRoleConMapper.selectList(userRoleConLambdaQueryWrapper);

        ArrayList<Role> roleArrayList = new ArrayList<>();
        for (UserRoleCon userRoleCon : userRoleCons) {
            LambdaQueryWrapper<Role> roleLambdaQueryWrapper = new LambdaQueryWrapper<>();
            roleLambdaQueryWrapper.eq(Role::getRoleId, userRoleCon.getRoleId()).select(Role::getRoleId, Role::getRoleName, Role::getRoleAnnotation).last("limit 1");
            Role role = roleMapper.selectOne(roleLambdaQueryWrapper);
            roleArrayList.add(role);
        }
        return roleArrayList;
    }
}

4.编写SpringSecurityConfig

重要部分

//注入
 private final UserDetailsServiceImpl userDetailsService;
    @Autowired
    public SpringSecurityConfig(UserDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
//使用
http.userDetailsService(userDetailsService);
package com.example.test1.config;


import com.example.test1.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;

import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;

import org.springframework.security.web.SecurityFilterChain;

@EnableWebSecurity
@Configuration
public class SpringSecurityConfig {


    private final UserDetailsServiceImpl userDetailsService;
    @Autowired
    public SpringSecurityConfig(UserDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }


    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        AuthenticationManager authenticationManager = configuration.getAuthenticationManager();
        System.out.println(authenticationManager);
        return configuration.getAuthenticationManager();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .authorizeRequests(auth -> auth.anyRequest().authenticated())
                .formLogin(Customizer.withDefaults())
                .httpBasic(Customizer.withDefaults())
                .userDetailsService(userDetailsService);
        return http.build();
    }


}

5.编写Controller进行测试

package com.example.test1.controller;

import com.example.test1.param.LoginParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class LoginController {

    @GetMapping("/index")
    public String test(){
        return "index....";
    }
}

输入数据库中的用户名密码
在这里插入图片描述

认证错误注意点

  1. 我们需要对Role、User、UserRoleCon三张表进行填充
  2. 需要将User表中的enabled,expired,tokenExpired,locked字段填充为1,表示true(这里大家直接构建的时候要注意,我提供的sql文件是已经填充了数据的)

关于SpringBoot2.7+/Spring5.3+不建议字段注入问题

字段注入问题:

  1. 对象的外部可见性
  2. 可能导致循环依赖
  3. 无法设置注入的对象为final,也无法注入静态变量

不建议字段注入

@Autowired
private UserDetailsServiceImpl userDetailsService;

建议构造注入

 private final UserDetailsServiceImpl userDetailsService;
    @Autowired
    public SpringSecurityConfig(UserDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值