当前位置:   article > 正文

springboot整合spring security + MybatisPlus入门_mybatisplus + spring secur

mybatisplus + spring secur

springboot整合spring security入门

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。
它是用于保护基于Spring的应用程序的实际标准。
Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。
与所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求
springboot对于springSecurity提供了自动化配置方案,可以使用更少的配置来使用springsecurity
而在项目开发中,主要用于对用户的认证和授权
官网:https://spring.io/projects/spring-security

  • 实现流程:

1、数据库表
2、实体类
3、Dao接口
4、自定义密码加密方式(采用MD5加密)
----4.1、MD5加密类
----4.2、创建一个实现类MD5PasswordEncoder实现PasswordEncoder接口中的matches、encode方法
5、项目引入依赖:spring-boot-starter-security
6、在用户登录实现类中实现UserDetailsService接口中的 loadUserByUsername 方法
7、编写配置类
8、测试

1、数据库表

使用数据库完成spring security的功能,需要三张表:
user(我这里用employee):至少需要包括 username、password
role:至少包括 name
user_role(我这里用employee_role)


// employee
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
  `id` int(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '姓名',
  `username` varchar(32) COLLATE utf8_bin NOT NULL COMMENT '用户名',
  `password` varchar(64) COLLATE utf8_bin NOT NULL COMMENT '密码',
  `phone` varchar(11) COLLATE utf8_bin NOT NULL COMMENT '手机号',
  `sex` varchar(2) COLLATE utf8_bin NOT NULL COMMENT '性别',
  `role` varchar(255) COLLATE utf8_bin DEFAULT '1',
  `id_number` varchar(18) COLLATE utf8_bin NOT NULL COMMENT '身份证号',
  `status` int(11) NOT NULL DEFAULT '1' COMMENT '状态 0:禁用,1:正常',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `create_user` bigint(20) NOT NULL COMMENT '创建人',
  `update_user` bigint(20) NOT NULL COMMENT '修改人',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='员工信息';

INSERT INTO `employee` VALUES (1, '管理员', 'admin', 'e10adc3949ba59abbe56e057f20f883e', '13812312319', '1', 'ROLE_admin', '110101199001010047', 1, '2021-05-06 17:20:07', '2022-12-29 17:36:08', 1, 1);
INSERT INTO `employee` VALUES (2, '德佑', 'deyou', 'e10adc3949ba59abbe56e057f20f883e', '13927751499', '1', 'ROLE_vip', '440111120000821011', 1, '2022-12-28 14:25:32', '2022-12-30 12:34:30', 1, 1);
INSERT INTO `employee` VALUES (3, '张三丰', 'zhangsan', 'e10adc3949ba59abbe56e057f20f883e', '13099992222', '1', 'ROLE_vip', '400200199802190213', 1, '2022-12-28 14:26:16', '2022-12-28 14:26:48', 1, 1);

// role
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `remark` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

INSERT INTO `role` VALUES (1, 'ROLE_common', NULL);   // 需要以ROLE_开头
INSERT INTO `role` VALUES (2, 'ROLE_vip', NULL);   // 需要以ROLE_开头


// employee_role
DROP TABLE IF EXISTS `employee_role`;
CREATE TABLE `employee_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `employee_id` int(11) DEFAULT NULL,
  `role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

INSERT INTO `employee_role` VALUES (1, 1, 1);
INSERT INTO `employee_role` VALUES (2, 2, 2);
INSERT INTO `employee_role` VALUES (3, 3, 1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

2、entity实体类

自行根据表 employee、role、employee_role表中的属性设置实体类的属性

3、创建对象实体类的Dao文件

@Mapper
public interface EmployeeDao extends BaseMapper<Employee> {
}


@Mapper
public interface EmployeeRoleDao extends BaseMapper<EmployeeRole> {
}

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4、自定义密码加密方式

采用MD5加密

  • 1、MD5加密类
public class MD5Utils {
	/**
	 * 使用md5的算法进行加密
	 */
	public static String encode(String plainText) {
		byte[] secretBytes = null;
		try {
			secretBytes = MessageDigest.getInstance("md5").digest(
					plainText.getBytes());
		} catch (NoSuchAlgorithmException e) {
			throw new RuntimeException("没有md5这个算法!");
		}
		String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字
		// 如果生成数字未满32位,需要前面补0
		for (int i = 0; i < 32 - md5code.length(); i++) {
			md5code = "0" + md5code;
		}
		return md5code;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 2、创建一个实现类MD5PasswordEncoder实现PasswordEncoder接口中的matches、encode方法
package com.itheima.reggie.encoder;

import com.itheima.reggie.utils.MD5Utils;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @author Deyou Kong
 * @description
 * @date 2023/2/3 3:43 下午
 */

public class MD5PasswordEncoder implements PasswordEncoder {
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return encodedPassword.equals(MD5Utils.encode((String)rawPassword));
    }

    @Override
    public String encode(CharSequence rawPassword) {
        return MD5Utils.encode((String)rawPassword);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

5、项目引入依赖

我这里项目使用的Springboot 版本是2.4.5

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.4.5</version>
        <relativePath />
    </parent>
 <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter</artifactId>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
       <scope>compile</scope>
   </dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
        <!--mysql数据库相关-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.20</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.4.0</version>
        </dependency>
        <!--MySQL读写分离-->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0</version>
        </dependency>
        <!--mysql数据库相关-->
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

6、在用户登录实现类中实现UserDetailsService接口中的 loadUserByUsername 方法

@Slf4j
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeDao, Employee> implements EmployeeService, UserDetailsService {

    @Autowired
    private EmployeeDao employeeDao;

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private EmployeeRoleDao employeeRoleDao;

    @Autowired
    private RoleDao roleDao;
    
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Employee::getUsername, s);
        Employee emp = this.getOne(wrapper);
        if (ObjectUtils.isEmpty(emp)) {
            throw new UsernameNotFoundException("账号或密码错误!");
        }
        if (emp.getStatus().equals(0)){
            throw new UsernameNotFoundException("账号已禁用!");
        }else {
            LambdaQueryWrapper<EmployeeRole> employeeRoleLambdaQueryWrapper = new LambdaQueryWrapper<>();
            employeeRoleLambdaQueryWrapper.eq(EmployeeRole::getEmployeeId, emp.getId());
            List<EmployeeRole> employeeRoles = employeeRoleDao.selectList(employeeRoleLambdaQueryWrapper);
            List<Integer> roleIdList = employeeRoles.stream().map((item) -> {
                return item.getRoleId();
            }).collect(Collectors.toList());
            List<Role> roleList = roleDao.selectBatchIds(roleIdList);
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();

            //遍历当前用户的角色集合组装权限
            for (Role role : roleList) {
                authorities.add(new SimpleGrantedAuthority(role.getName()));
            }
//            List<GrantedAuthority> auths = AuthorityUtils.commaSeparatedStringToAuthorityList("role");
            return new User(emp.getUsername(), emp.getPassword(), authorities);   // 如果用户没有角色会NullPointerException
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

7、编写配置类

创建配置类SecurityConfig 实现 WebSecurityConfigurerAdapter接口

package com.itheima.reggie.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.itheima.reggie.common.R;
import com.itheima.reggie.encoder.MD5PasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;


/**
 * @author Deyou Kong
 * @description security安全认证配置
 * @date 2023/2/3 11:08 上午
 */

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 内存验证方式
     * @param auth
     * @throws Exception

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 密码需要设置编码器
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
        // 使用内容用户
        auth.inMemoryAuthentication().passwordEncoder(encoder)
                .withUser("admin").password(encoder.encode("123456")).roles("admin").and()
                .withUser("lisi").password(encoder.encode("123456")).roles("vip");
    }
     */

    @Autowired
    private UserDetailsService userDetailsService;   // 该接口的方法已经在EmployeeServiceImpl实现类中实现

    /**
     * MySQL验证方式
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

    }

    /**
     * 自定义登录页面
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .loginPage("/backend/page/login/login.html")   //设置登录界面
                .loginProcessingUrl("/employee/login")  //登录界面url
                .defaultSuccessUrl("/backend/index.html")    //默认登录成功界面
                .successHandler((req, resp, authentication) -> {
                    Object principal = authentication.getPrincipal();//获取认证成功的用户对象
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    R<Object> r = R.success("登录成功");
                    Map<String, String> map = new HashMap<>();
                    map.put("username", "admin");
                    r.setData(map);
                    //使用Jackson将对象转换为JSON字符串
                    //out.write(new ObjectMapper().writeValueAsString(principal));//将登录成功的对象基于JSON响应
                    out.write(new ObjectMapper().writeValueAsString(r));
                    out.flush();
                    out.close();
                })
                .failureHandler(
                        (req, resp, e) -> {//根据异常信息判断哪一操作出现错误
                            resp.setContentType("application/json;charset=utf-8");
                            PrintWriter out = resp.getWriter();
                            R<Object> r = R.error("登录失败");
                            out.write(new ObjectMapper().writeValueAsString(r));
                            out.flush();
                            out.close();
                        }
                )
                .permitAll()
                .and().authorizeRequests()      // 哪些资源可以直接访问
                .antMatchers("/backend/api/**", "/backend/images/**", "/backend/js/**", "/backend/styles/**", "/backend/plugins/**","/employee/login").permitAll()    //不做处理
                .anyRequest().authenticated()   //所有请求都可以访问
                .and()
                .logout()
                .logoutUrl("/employee/logout")
                .logoutSuccessHandler((req, resp, authentication) -> {
                    Object principal = authentication.getPrincipal();//获取认证成功的用户对象
                    resp.setContentType("application/json;charset=utf-8");
                    PrintWriter out = resp.getWriter();
                    R<Object> r = R.success("操作成功");
                    //使用Jackson将对象转换为JSON字符串
                    //out.write(new ObjectMapper().writeValueAsString(principal));//将登录成功的对象基于JSON响应
                    out.write(new ObjectMapper().writeValueAsString(r));
                    out.flush();
                    out.close();
                })
                .logoutSuccessUrl("/backend/page/login/login.html")
                .and().csrf().disable();       //关闭CSRF
        http.headers().frameOptions().sameOrigin();
    }


    @Bean
    PasswordEncoder passwordEncoder() {
        return new MD5PasswordEncoder();   // 采用自定义的MD5加密方法
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122

8、测试

最终目录结构
在这里插入图片描述
静态资源目录结构:因为不是在static目录下,需要在WebMvcConfig中配置

@Slf4j
@Configuration
@EnableSwagger2
@EnableKnife4j
public class WebMvcConfig extends WebMvcConfigurationSupport {
    /**
     * 设置静态资源映射
     * @param registry
     */
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry){
        registry.addResourceHandler("/backend/**").addResourceLocations("classpath:/backend/");
        registry.addResourceHandler("/front/**").addResourceLocations("classpath:/front/");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        super.addResourceHandlers(registry);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在这里插入图片描述
启动项目,访问项目的首页,因为没登录,会自动跳转至配置的登录页面
在这里插入图片描述

到这里,mybatisplus + spring security 的简单入门就实现完啦,这里使用的是 spring security 采用的是默认机制session

后续再出一篇采用 jwt token 的整合文章

https://blog.csdn.net/guorui_java/article/details/118229097

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/875370
推荐阅读
相关标签
  

闽ICP备14008679号