项目集成Spring Security认证部分
一、需求分析
在本项目中,使用了Spring Security框架来进行认证和授权管理。由于是前后端分离的项目,所有认证的请求需要通过Token来验证身份,系统中包括了用户登录、角色授权以及资源访问控制等功能。
系统中的资源控制:
- 白名单url:指的是任何用户都可以访问的资源(例如登录、注册等公共接口),这些接口不需要身份验证。
- 需要控制的资源:除了白名单中的接口,其他接口都需要进行权限验证。这些资源通常对应系统的菜单和按钮,用户根据所分配的角色和权限,决定是否能访问对应的接口。
- 授权管理:通过角色和资源的绑定,确保只有授权的用户可以访问指定的接口,未授权的用户则返回403权限拒绝。
二、代码解读
SecurityConfig 配置
SecurityConfig
类主要负责Spring Security的配置,其中包括了认证授权的管理。重点代码如下:
@Configuration
public class SecurityConfig {
@Autowired
private JwtTokenAuthorizationManager jwtTokenAuthorizationManager;
@Autowired
private SecurityConfigProperties securityConfigProperties;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
List<String> ignoreUrl = securityConfigProperties.getIgnoreUrl();
// 前后端分离,放行登录接口等白名单
http.authorizeHttpRequests().antMatchers(ignoreUrl.toArray(new String[ignoreUrl.size()])).permitAll()
.anyRequest().access(jwtTokenAuthorizationManager);
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 关闭session
http.headers().cacheControl().disable(); // 关闭缓存
http.csrf().disable(); // 禁用csrf
return http.build();
}
}
- 白名单配置:
antMatchers
方法用于配置哪些接口是公开的(如登录接口)。这些接口不需要Token验证。 - 认证管理:
anyRequest().access(jwtTokenAuthorizationManager)
表示其他所有接口都需要通过jwtTokenAuthorizationManager
来进行Token认证。 - 关闭Session:由于是无状态的认证方式,关闭了HTTP Session和缓存,确保每次请求都是独立的。
UserDetailsServiceImpl 实现
UserDetailsServiceImpl
类负责加载用户信息,以供Spring Security进行认证。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.selectByUsername(username);
if (ObjectUtil.isEmpty(user) || user.getDataState().equals("1")) {
throw new RuntimeException("用户登录失败");
}
return BeanUtil.toBean(user, UserAuth.class);
}
}
- 通过
loadUserByUsername
方法,查询数据库中的用户信息,并将其封装为UserAuth
对象返回给Spring Security,供认证过程使用。
LoginServiceImpl 认证过程
LoginServiceImpl
类处理了用户的登录请求,并生成JWT Token。
@Override
public UserVo login(LoginDto loginDto) {
UsernamePasswordAuthenticationToken upat = new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
Authentication authenticate = authenticationManager.authenticate(upat);
if (!authenticate.isAuthenticated()) {
throw new BaseException(BasicEnum.LOGIN_FAIL);
}
UserAuth userAuth = (UserAuth) authenticate.getPrincipal();
UserVo userVo = BeanUtil.toBean(userAuth, UserVo.class);
userVo.setPassword("");
// 获取用户角色和资源,生成JWT
List<Resource> resourceVoList = resourceMapper.findResourceVoListByUserId(userVo.getId());
Set<String> resourceRequestPaths = resourceVoList.stream()
.map(Resource::getRequestPath)
.collect(Collectors.toSet());
List<String> publicAccessUrls = securityConfigProperties.getPublicAccessUrls();
resourceRequestPaths.addAll(publicAccessUrls);
Map<String, Object> clamis = new HashMap<>();
clamis.put("currentUser", JSONUtil.toJsonStr(userVo));
String jwtToken = JwtUtil.createJWT(jwtTokenManagerProperties.getBase64EncodedSecretKey(), jwtTokenManagerProperties.getTtl(), clamis);
userVo.setUserToken(jwtToken);
// 存储访问权限到Redis
long ttl = Long.valueOf(jwtTokenManagerProperties.getTtl() / 1000);
String accessUrlsCacheKey = CacheConstants.ACCESS_URLS_CACHE + userVo.getId();
redisTemplate.opsForValue().set(accessUrlsCacheKey, JSONUtil.toJsonStr(resourceRequestPaths), ttl, TimeUnit.SECONDS);
return userVo;
}
1. 使用认证管理器进行认证:
- 用户通过提供的用户名和密码进行登录,系统首先创建
UsernamePasswordAuthenticationToken
对象,这个对象封装了用户的用户名和密码。 - 然后,
authenticationManager.authenticate(upat)
方法会使用Spring Security的认证管理器来验证用户身份。如果认证失败,抛出自定义的BaseException
,提示登录失败。
2. 获取用户数据:
- 当认证通过后,
authenticate.getPrincipal()
会返回包含用户信息的UserAuth
对象。此对象包含了用户的基本信息(如用户名、密码等)。 - 使用
BeanUtil.toBean()
方法将UserAuth
对象转换为UserVo
对象,便于后续的处理。
3. 获取用户资源权限列表:
resourceMapper.findResourceVoListByUserId(userVo.getId())
方法会查询数据库,获取当前用户拥有的资源权限,即用户可以访问的接口路径。- 这些路径将被存储在
resourceRequestPaths
集合中。
4. 合并公共访问的URL与用户资源路径:
- 系统还从配置文件中读取
publicAccessUrls
,即那些不需要认证的公共接口路径。 - 将这些公共路径和用户可访问的资源路径合并,确保最终的访问列表包含了所有允许访问的接口。
5. 生成JWT Token:
- 使用
JwtUtil.createJWT()
方法创建一个JWT Token。这个Token包含了当前用户的信息(通过claims.put("currentUser", JSONUtil.toJsonStr(userVo))
添加),并且设置了有效期(由jwtTokenManagerProperties.getTtl()
控制)。 - 生成的JWT Token被设置到
userVo
对象的userToken
属性中,准备返回给前端。
6. 存储用户资源路径到Redis:
- 生成JWT Token后,系统会将用户可以访问的URL列表存储到Redis缓存中。这样做是为了提高性能,使得后续请求可以快速验证用户的权限。
- 存储时设置了TTL(过期时间),使得权限数据与JWT Token的有效期一致。
7. 返回用户信息和JWT Token:
- 最终,系统返回一个
UserVo
对象,包含用户的基本信息和生成的JWT Token。前端可以使用这个Token进行后续的认证请求。
sql部分的编写:
<select id="selectByUserId" resultType="com.zzyl.entity.Resource">
select sr.request_path requestPath
from sys_user_role sur, sys_role_resource srr, sys_resource sr
where sur.role_id = srr.role_id
and srr.resource_no = sr.resource_no
and sr.resource_type = 'r'
and sr.data_state = '0'
and sur.user_id = #{userId}
</select>
-
表结构与关联:
sys_user_role
:该表将用户与角色关联起来。字段包括user_id
和role_id
,用于表示用户与角色的关系。sys_role_resource
:该表将角色与资源关联起来。字段包括role_id
和resource_no
,用于表示角色与资源的关系。sys_resource
:该表包含了所有系统资源的详细信息。字段包括resource_no
、request_path
(资源路径)、resource_type
等,用于存储系统中所有的资源信息。
-
SQL查询过程:
- 通过
sys_user_role
表和sys_role_resource
表的连接,获取当前用户(由sur.user_id
表示)所拥有的所有角色。 - 然后,通过
sys_role_resource
表和sys_resource
表的连接,获取这些角色对应的资源路径。 - 查询条件
sr.resource_type = 'r'
限定了只选择类型为“按钮”的记录,sr.data_state = '0'
表示只选择状态为“有效”的资源。
- 通过
-
查询结果:
- 查询的结果是该用户可以访问的所有资源的
request_path
(资源路径)。这些路径将用于后续的权限控制。
- 查询的结果是该用户可以访问的所有资源的
使用场景:
- 该SQL查询用于获取当前用户的权限数据。通过用户的
user_id
,系统可以查询到该用户所有角色对应的资源路径,并且只返回这些有效的资源路径(sr.data_state = '0'
表示资源有效)。这些资源路径将会存储在urlList
中,用于生成JWT Token时添加到Token的有效载荷中。
三、总结
在Spring Security认证与授权集成中,主要分为用户登录认证、JWT Token生成与校验以及权限管理等部分。