赞
踩
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、测试
使用数据库完成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);
自行根据表 employee、role、employee_role表中的属性设置实体类的属性
@Mapper
public interface EmployeeDao extends BaseMapper<Employee> {
}
@Mapper
public interface EmployeeRoleDao extends BaseMapper<EmployeeRole> {
}
@Mapper
public interface RoleDao extends BaseMapper<Role> {
}
采用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; } }
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); } }
我这里项目使用的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数据库相关-->
@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 } } }
创建配置类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加密方法 } }
最终目录结构
静态资源目录结构:因为不是在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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。