赞
踩
SpringSecurity的核心包括认证和授权两个部分。
认证
认证过程主要是实现AuthenticationManager, AuthenticationManager最重要的实现类是ProviderManager, 通过ProviderManager,可以管理AuthenticationProvider, 每个AuthenticationProvider都对应一种认证方式, 当所有认证方式不支持时,调用ProviderManager的parent认证。
SpringSecurity提供的认证其实是一种接口,他允许你自定义认证方式,这种设计更像是一种规范设计。
授权
AccessDecisionVoter 是一个投票器,投票器会检查用户是否具备应有的角色,进而投出赞成、反对或者弃权票;
AccessDecisionManager 则是一个决策器,来决定此次访问是否被允许。
过滤链
开发者所见到的 Spring Security 提供的功能,都是通过这些过滤器来实现的,这些过滤器按照既定的优先级排列,最终形成一个过滤器链。
需要注意的是,默认过滤器并不是直接放在 Web 项目的原生过滤器链中,而是通过一个FilterChainProxy 来统一管理。Spring Security 中的过滤器链通过 FilterChainProxy 嵌入到 Web项目的原生过滤器链中,如下图所示。
在将SpringSecurity整合到Springboot之前,我们需要知道整体的流程以便于我们更好的理解验证过程。
spring-boot-starter-security
依赖SecurityConfig
配置文件UserDetailsService
第2点中,又可以拆分为登录、登出、错误处理、自动登录、过滤器等配置,如果完全不配置,SpringSecurity也会为你提供一套默认配置,从引入依赖开始就能够正常工作了。
第3点中,重写UserDetailsService.loadUserByUsername(String username)方法可以让你自定义验证的过程。如验证时,你希望通过查询MySQL数据库,如果数据库中存在该用户并且密码验证正确,就返回SpringSecurity中的User对象,这些过程都可以自定义实现。
在pom.xml文件中引入依赖后,SpringSecurity在Springboot启动时就能开始正常工作了。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
如果什么都不配置,那么该配置文件的内容应该这样的,剩下的事情SpringSecurity会帮你设置默认配置。
package com.example.demo.config.security;
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { }
}
如果想要自定义配置,如自定义登录界面、自定义验证过程,或者想要整合一些工具,如Jwt,这个时候需要在SecurityFilterChain
中配置。
比较简单的配置:
package com.example.demo.config.security; @Configuration public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { // 登录页为/login.html,请求/login路径验证登录信息,成功后重定向到/index.html页 httpSecurity.formLogin() .loginProcessingUrl("/login") .loginPage("/login.html") .defaultSuccessUrl("/index.html"); // 允许登录页和登录的url不需要验证即可访问,因为那个时候还没有登录信息 httpSecurity.authorizeRequests() .antMatchers("/login*").permitAll() .anyRequest().authenticated(); httpSecurity.csrf().disable(); return httpSecurity.build(); } }
更全的配置:
其中,SpringSecurity整合JwtToken可以看另一篇文章。
package com.example.demo.config.security; /** * @Author : HuangJiajian * @create 2022/10/18 17:49 */ @Configuration public class SecurityConfig { @Autowired UserDetailServiceImpl userDetailService; @Autowired PersistentTokenRepository persistentTokenRepository; @Autowired private JwtFilter jwtFilter; @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { // 登录,自定义登录界面为/login.html,通过请求/login路径验证身份 httpSecurity.formLogin() .loginProcessingUrl("/login") .loginPage("/login.html") .successHandler(new AuthenticationSuccessConfig()) .failureHandler(new AuthenticationFailConfig()); // 退出,自定义退出成功时的操作,如响应的数据 httpSecurity.logout() .logoutUrl("/logout") .logoutSuccessHandler(new LogoutSuccessConfig()); // 过滤器,除了/test/*和/login*的url请求不需要验证身份外,其他的请求都需要身份验证 httpSecurity.authorizeRequests() .antMatchers("/test/*").permitAll() .antMatchers("/login*").permitAll() .anyRequest().authenticated(); // 错误处理,自定义认证失败的错误处理,如403返回权限不足等 httpSecurity.exceptionHandling() .accessDeniedHandler(new AccessDeniedConfig()) .authenticationEntryPoint(new AuthenticationEntryPointConfig()); // 整合JwtToken验证身份 httpSecurity.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); httpSecurity.csrf().disable(); return httpSecurity.build(); } @Bean public PasswordEncoder getPasswordEncoder() { // 选择非对称加密为SpringSecurity的加密方法 return new BCryptPasswordEncoder(); } }
Spring Security 中定义了 UserDetails 接口来规范开发者自定义的用户对象,这样方便一些旧系统、用户表已经固定的系统集成到 Spring Security 认证体系中。而负责提供用户数据源的接口是 UserDetailsService, UserDetailsService 中只有一个查询用户的方法,那就是loadUserByUsername。
换句话说,我们可以使用UserDetailsService.loadUserByUsername(String username)
方法来自定义查询用户的过程。这部分的代码,只需要关心loadUserByUsername
的自定义逻辑即可。注意这里的User类是来自于SpringSecurity的,而非自己在Entity中定义的User类。
package com.example.demo.service.impl; /** * @Author : HuangJiajian * @create 2022/10/19 10:03 */ @Service @Component public class UserDetailServiceImpl implements UserDetailsService { @Autowired UserMapper userMapper; @Autowired UserRoleMapper userRoleMapper; @Autowired RoleMapper roleMapper; @Autowired private StringRedisTemplate stringRedisTemplate; /** * @Author HJJ * @Date 2022-12-27 9:43 * @Params String username * @Return User * @Description SpringSecurity方法,验证并获取用户权限信息,并将User数据写入Redis缓存 */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { String cacheUser = stringRedisTemplate.opsForValue().get("username::" + username); // 如果有缓存,直接返回,如果没有缓存,否则需要查询数据库并写入Redis缓存 if (!Objects.isNull(cacheUser)) { return JSON.parseObject(cacheUser, UserDetails.class); } else { UserEntity userEntity = getUserEntityByUsername(username); User user = new User(username, userEntity.getUserPassword(), getAuthoritiesByUserId(userEntity.getUserId())); String strUser = JSONObject.toJSONString(user); System.out.println(username+"查询了数据库并写入Redis缓存:" + strUser); // 将User对象转换成String并写入缓存 stringRedisTemplate.opsForValue().set("username::" + username, strUser, 60, TimeUnit.SECONDS); return user; } } /** * @Author HJJ * @Date 2022-12-27 9:42 * @Params * @Return * @Description 查询数据库,获取用户信息 */ public UserEntity getUserEntityByUsername(String username) { QueryWrapper<UserEntity> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("userName", username); UserEntity userEntity = userMapper.selectOne(queryWrapper); if (Objects.isNull(userEntity)) { System.out.println("没有此用户:" + username); throw new UsernameNotFoundException("没有此用户"); } return userEntity; } /** * @Author HJJ * @Date 2022-12-27 9:55 * @Params * @Return * @Description 查询数据库,获取用户权限信息 */ public List<GrantedAuthority> getAuthoritiesByUserId(int userId) { StringBuilder roles = new StringBuilder(); QueryWrapper<UserRole> queryWrapper1 = new QueryWrapper<>(); queryWrapper1.eq("userId", userId); List<UserRole> userRoles = userRoleMapper.selectList(queryWrapper1); for (int i = 0; i < userRoles.size(); i++) { UserRole userRole = userRoles.get(i); int roleId = userRole.getRoleId(); QueryWrapper<Role> queryWrapper2 = new QueryWrapper<>(); queryWrapper2.eq("roleId", roleId); Role role = roleMapper.selectOne(queryWrapper2); String roleName = role.getRoleName(); if (Objects.equals(userRoles.size() - 1, i)) { roles.append(roleName); break; } roles.append(roleName).append(","); } return AuthorityUtils.commaSeparatedStringToAuthorityList(roles.toString()); } }
以上即是Springboot整合SpringSecurity的整体过程。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。