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);
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> {
}
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;
}
}
- 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);
}
}
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数据库相关-->
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
}
}
}
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加密方法
}
}
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);
}
}
启动项目,访问项目的首页,因为没登录,会自动跳转至配置的登录页面
到这里,mybatisplus + spring security 的简单入门就实现完啦,这里使用的是 spring security 采用的是默认机制session
后续再出一篇采用 jwt token 的整合文章
https://blog.csdn.net/guorui_java/article/details/118229097