目录
一、SpringSecurity介绍
案例效果:
二、环境准备
2.1 数据库
2.2 项目准备
三、确保项目没问题后开始使用
3.1、Security的过滤链:
3.2、自定义用户名密码登录:
方式1:将用户名密码写在配置文件里
方式2:使用数据库中的用户名、密码进行登录:
第一步:新建一个类CustomerUserDetails实现UserDetails接口
第二步:新建CustomerUserDetailsServiceImpl来实现UserDetailService接口
第三步:配置类中注入bean对象:
3.3、自定义登录:
第一步:自定义登录页面
第二步:定义一个登录接口
第三步:放行登录接口、请求登录接口
第四步:在Service层使用ProviderManager的authenticate()方法进行验证
实现效果:
过程中的一些报错:
认证过程:
一、SpringSecurity介绍
SpringSecurity顾名思义是spring的一个安全框架。拥有认证和授权两大核心功能。
案例效果:
二、环境准备
2.1 数据库
RBAC模型:基于角色的权限控制。通过角色关联用户,角色关联权限的方式间接赋予用户权限。
即一个用户属于多种角色、一个角色有多个权限
主体(subject) 访问资源的时候、通常由分为两种:基于角色控制访问、基于权限控制访问;
故建立五张表:用户表、权限表、角色表、用户角色表、角色权限表;
准备数据:
张三--->管理员、普通用户------>增删改查
李四---->普通用户----->查询
脚本参考文章末尾的传送门
2.2 项目准备
使用mybatis作为持久层、且为了简化开发使用了mybatis-plus、本文着种于集成SpringSecurity,对整合mybatis-plus感兴趣的小伙伴可前往传送门浏览:
springboot整合mybatis-plus
2.3 项目信息
jdk 17
springboot 2.7.0
maven 3.8.6
mysql 8.0.30
导入必要jar包:主要导入:boot-security的整合依赖,其他根据需要导入
<!-- web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- MP 起步依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.0</version> </dependency> <!-- 模板引擎 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.0</version> <!-- <scope>test</scope> --> </dependency> <!--security--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!--thymeleaf模块引擎--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency>
配置文件:application.yml
spring: mvc: pathmatch: matching-strategy: ant_path_matcher server: port: 8080 --- spring: datasource: url: jdbc:mysql://localhost:3308/boot_security?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&AllowPublicKeyRetrieval=True username: root password: root --- mybatis-plus: mapper-locations: mapper/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true
随后使用代码生成工具生成项目结构、随后调整xml文件位置、以及适当删减、随后测试下生成的代码是否可用。主要看有对应三个实体类就可。
@Autowired private TbUserService userService; @Test public void test01(){ userService.list(); }
静态资源准备:
三、确保项目没问题后开始使用
导入security整合依赖后、启动项目访问任何接口,都会被直接被直接拦截,并转发到security提供的登录页面、也就是需要认证一下才能进入首页。默认的用户名是user、密码在控制台。
认证通过后才会访问到目标页面
3.1、Security的过滤链:
目前使用的是Security给的默认用户名和生成的密码。 实际情况是使用tb_user获取真实的用户名和密码;在此之前先了解Security的过滤链;
List<Filter> filterList = context.getBean(DefaultSecurityFilterChain.class).getFilters();
SpringSecurity的过滤链:一共有16个过滤器链
过滤器链的大概流程就是,用户请求过来、先检查用户名密码、没有错、则检查权限,若有对应权限、访问对应的接口、其中只要一步错,就给打回去;
3.2、自定义用户名密码登录:
方式1:将用户名密码写在配置文件里
spring: security: user: name: zs password: 123
方式2:使用数据库中的用户名、密码进行登录:
第一步:新建一个类CustomerUserDetails实现UserDetails接口
实现所有UserDetails的抽象方法并将TbUser【登录对象】 作为CustomerUserDetails的属性。
@Data @NoArgsConstructor @AllArgsConstructor public class CustomerUserDetails implements UserDetails { private TbUser user; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return null; } @Override public String getPassword() { return user.getPassWord(); } @Override public String getUsername() { return user.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; } }
第二步:新建CustomerUserDetailsServiceImpl来实现UserDetailService接口
实现loadUserByUsername方法。
/** * @author Alex */ @Service public class CustomerUserDetailsServiceImpl implements UserDetailsService { @Autowired private TbUserMapper userMapper; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //根据用户名查询用户信息 LambdaQueryWrapper<TbUser> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(TbUser::getUserName,username); TbUser user = userMapper.selectOne(wrapper); //如果查询不到数据就通过抛出异常来给出提示 if(Objects.isNull(user)){ throw new RuntimeException("用户名错误"); } //封装成UserDetails对象返回 return new CustomerUserDetails(user); } }
此时由于数据库中的密码是明文,登录时会报一个错。
There is no PasswordEncoder mapped for the id "null"
因为没有给密码加密:
此时要想继续登录
方式1【不推荐】:将数据库中明文前加{noop}即可
方式2:使用Security默认的加密的工具类BCryptPasswordEncoder将密码加密后存入数据库。再SecurityConfig配置类中注入BCryptPasswordEncoder的bean对象即可。加密方式会自动加盐;
第三步:配置类中注入bean对象:
/** * @author Alex */ @Configuration public class SecurityConfig{ @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
将密码字符串加密,调用encode()将密码加密。将加密后的字符串存入数据库;
@Test public void testPasswordEncoder1(){ String encode = securityConfig.passwordEncoder().encode("123"); System.err.println(encode); } @Test public void testPasswordEncoder(){ BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String encode = passwordEncoder.encode("123"); System.err.println(encode); }
当注入bean对象后,明文前加{noop}就不可用了。
简单提一下解密:
SpringSecurity提供了matches()方法来进行密码匹配,加密本身时不可逆的,解密的原理是将需要解密的字段统过相同的Hash函数得到的字符串到已加密的数据库中进行匹配。
3.3、自定义登录/认证:
第一步:自定义登录页面
第二步:定义一个登录接口
/** * 登录方法、登录成功跳转到首先、 * 否则继续跳转登录页,并给出提示 * @param username * @return */ @PostMapping("/login") public Map<String, String> userLogin(String username, String password){ TbUser loginUser = new TbUser(); loginUser.setUserName(username); loginUser.setPassWord(password); return userService.userLogin(loginUser); }
第三步:放行登录接口、请求登录接口
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() .authorizeRequests() // 允许匿名访问的接口 .antMatchers("/user/login").anonymous() .antMatchers("/toLogin").anonymous() // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); http.formLogin() // 访问登录页面接口 .loginPage("/toLogin") // 执行登录方法接口 .loginProcessingUrl("user/login"); return http.build(); }
第四步:在Service层使用ProviderManager的authenticate()方法进行验证
将封装的Authentication对象 存入SecurityContextHolder
@Override public Map<String, String> userLogin(TbUser loginUser, HttpSession session) { Map<String, String> responseMap = new HashMap<>(2); try { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginUser.getUserName(), loginUser.getPassWord(),null); Authentication authenticate = authenticationManager.authenticate(token); // 存入SecurityContextHolder SecurityContextHolder.getContext().setAuthentication(authenticate); responseMap.put("code","0"); // 存入部分数据到session,方便区分用户、实际情况种可以省略 CustomerUserDetails userDetails = (CustomerUserDetails) authenticate.getPrincipal(); session.setAttribute("userName",userDetails.getUser().getUserName()); return responseMap; }catch (RuntimeException e){ responseMap.put("code","-1"); e.printStackTrace(); return responseMap; } }
此处直接返回map,也可以封装一个返回结果集对象,然后对于Security的 Session Management相关的内容会在后续文章中更新。
前端监听表单提交后发送登录请求:
//登录请求 const url = "user/login"; $.post(url,data,function(response){ console.log(response.code); if(response.code==0){ layer.msg("登录成功",{icon:6,time:1000}, function () { window.location = '/'; }); }else { layer.msg("用户名或密码错误",{icon:5,anim:6}); $("#btn-login").removeAttr("disabled", "disabled").removeClass("layui-btn-disabled"); } });
到这里呢、自定义登录就完成了、看下登录后的跳转的首页
实现效果:
3.3、后端接口授权
3.4、前端按钮隐藏
过程中的一些报错
1、Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
解决:配置文件加上
debug: true
2、No qualifying bean of type 'com.example.demo.mapper.TbUserMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
解决:启动类上加上mapperScan("com......")
@SpringBootApplication @MapperScan("com.example.demo.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
认证过程:
问:springSecurity如何校验用户名、密码和权限?
答:通过一个登录请求的debug得到如下流程
发送一个登录请求----->UsernamePasswordAuthenticationFilter--->通过authenticate()方法认证----->loadUserUsername()方法获得UserDetails对象-->从该对象中拿到密码对比系统中的密码---->给UserDetails对象添加权限并设置到Authentication中,存入SecurityContentHolder中即可。 (完成一个cookie--session的闭环)
传送门:
springboot整合thymeleaf
springboot整合mybatis
springboot整合mybatis-plus
springboot整合shiro实现简单权限控制