SpringSecurity整合MybatisPlus实现用户登录查库以及权限信息获取
登录Demo完整代码仓库地址:https://gitee.com/strivezhangp/java-study-log.git(分支为logindemo)
1 整体概述
接上篇博客,本博客记录了SpringBoot项目结合SpringSecurity整合MybatisPlus3实现用户登录的查库认证以及用户权限的获取功能。
- 整合MybatisPlus3
- 结合MybatisX插件实现代码生
- 用户权限的存储–将用户信息、权限信息存储到UsernamePasswordAuthenticationToken中
2 项目引入MybatisPlus相关依赖
2.1 Maven依赖引入
<!-- 整合mybatis plus https://baomidou.com/-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2.2 .yml配置文件汇总配置数据源信息
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/数据库名?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=UTC
username: ****
password: ****
# mybatisPlus中mapper的xml文件对应位置,classpath指向的是resources文件夹
mybatis-plus:
mapper-locations: classpath*:/mapper/**Mapper.xml
2.3 MybatisPlus配置类
@Configuration
@MapperScan("com.strive.logindemo.mapper") // mapper文件的扫描路径
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor()); // 防止全表更新
interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 载入分页插件
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer(){
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
}
2.4 结合MybatisX插件自动生成代码
基本的实现步骤:
- IEDA数据库工具中导入数据源
- 选择相应的数据表实现代码自动生成
- 代码生成的一些配置–点击完成后自动生成(service、mapper、实体类等)
到此为止,数据库相关文件自动生成,可以进行相关的测试以及使用。
3 实现用户登录的认证
3.1 自定义Jwt过滤器
在此过滤器中,判断用户是否登录(登录后用户请求头拥有JWT令牌),然后根据令牌信息,查库后提取用户详细信息(用户名、密码、权限等),将其存储到会话中,方便后续该用户发起的请求的鉴权操作等。
首先在JwtUtil工具类中追加以下方法:
/**
* 解析JWT字符串
*
* @param jwt
* @return
*/
public Claims getClaimByToken(String jwt) {
try {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(jwt)
.getBody();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 判断JWT令牌是否过期
*
* @param claims
* @return
*/
public boolean isTokenExpired(Claims claims) {
return claims.getExpiration().before(new Date());
}
然后自定义Jwt过滤器,在用户登录成功后,首先进入Jwt过滤器,对用户权限信息进行进一步的解析和获取。
public class JwtFilter extends BasicAuthenticationFilter {
@Autowired
JwtUtil jwtUtil;
/**
* 继承构造方法 使用 AuthenticationManager 来处理认证请求
*/
public JwtFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* 重写方法 实现自定义的用户请求处理逻辑
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String jwt = request.getHeader(jwtUtil.getHeader());
// 判断是否存在JWT
if (StrUtil.isBlankOrUndefined(jwt)) {
chain.doFilter(request, response);
return;
}
Claims claims = jwtUtil.getClaimByToken(jwt);
if (claims == null) {
throw new JwtException("token 异常");
}
// 判断是否过期
if (jwtUtil.isTokenExpired(claims)) {
throw new JwtException("token 已过期");
}
// 合法情况 生成token 存储在令牌中 包含 用户信息、用户权限信息
String username = claims.getSubject();
// 自定义一个用户权限数组
String userAuth = "sys:user:list,sys:resident:list";
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
username, // 返回用户名
null, // 返回用户密码
AuthorityUtils.commaSeparatedStringToAuthorityList(userAuth) // 返回用户权限信息
);
SecurityContextHolder.getContext().setAuthentication(token);
System.out.println("jwt:" + jwt);
System.out.println("token:" + token);
chain.doFilter(request, response);
}
}
3.2 将自定义过滤器引入Security配置中
// 引入自定义的过滤器
@Bean
JwtFilter jwtFilter() throws Exception {
JwtFilter jwtFilter = new JwtFilter(authenticationManager());
return jwtFilter;
}
// 相关配置项
.and()
.addFilter(jwtFilter()); // jwt过滤器判断用户请求是否存在JWT令牌
4 自定义 UserDetails类
根据源码:package org.springframework.security.core.userdetails
自定义一个UserAccount类,实现用户信息和权限信息的返回。
public class DefinedUser implements UserDetails {
/**
* 自定义一些变量
*/
private Long userId;
/** 此处复制源类的其他属性和构造方法 */
/**
* 重写一些方法(基本全部拿过来重写默认的User)
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities; // 返回权限
}
@Override
public String getPassword() {
return this.password; // 返回密码
}
@Override
public String getUsername() {
return this.username; // 返回用户名
}
@Override
public boolean isAccountNonExpired() {
return this.accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return this.accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return this.credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return this.enabled;
}
}
5 结合mybatisPlus实现用户登录查库操作
5.1 定义用户登录查库的Service
在用户的登录提交后,跳转到Service层实现用户的查库操作。说明:在本类中未实现用户权限信息获取的查库操作,而是直接模拟了提供了一个用户权限信息。
@Service
public class UserDetailService implements UserDetailsService {
@Autowired
SysUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("用户查库");
SysUser user = userService.getByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户名或密码错误");
}
return new DefinedUser(user.getId(), user.getUsername(), user.getPassword(), getUserAuthority(user.getId()));
}
/**
* 获取用户权限信息
*/
public List<GrantedAuthority> getUserAuthority(Long id) {
/**
* 进行查库操作 获取用户权限信息
*/
// 模拟一个用户权限信息
String authority = "sys:user:list,sys:resident:list";
return AuthorityUtils.commaSeparatedStringToAuthorityList(authority);
}
}
5.2 Security配置
添加查库操作后,用户登录表单提交,要实现用户查库操作,验证用户名和密码,配置文件中需要引入加密方式以及用户验证查库的Service。
// 类开始引入密码加密方式
// 密码加密形式
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
// 类中实现方法
@Override
protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailService);
}
6 Security配置添加权限验证
在Security配置类中,添加以下注解,开启全局权限验证。
/**
* Security配置类
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启全局安全检查(权限判断)
public class SecurityConfig extends WebSecurityConfigurerAdapter...
6.1 添加用户没有权限的异常处理器
/**
* 用户没有接口 访问权限 处理器
*/
@Component
public class UserNotAuthorityHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
// 权限不足 返回 403
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
ServletOutputStream outputStream = httpServletResponse.getOutputStream();
outputStream.write(JSONUtil.toJsonStr(Result.failure(e.getMessage())).getBytes("UTF-8"));
outputStream.flush();
outputStream.close();
}
}
6.2 Security配置中进行相关配置
// 异常处理
.and()
.exceptionHandling()
.authenticationEntryPoint(userNotAuthenticated) // 用户未认证异常处理
.accessDeniedHandler(userNotAuthorityHandler) // 权限不组处理器
结果测试
// 模拟一个用户权限信息
String authority = "sys:user:list,sys:resident:list";
// 测试Controller中 权限的鉴定方式如下
@PreAuthorize("hasAuthority('sys:user:list')") // 菜单权限鉴定
@GetMapping("/test/list")
public Result testPermMenu(){
return Result.success("用户有该菜单或按钮的访问权限");
}
@PreAuthorize("hasRole('admin')") // 角色权限鉴定
@GetMapping("/test/admin")
public Result testPermRole(){
return Result.success("用户角色有访问权限");
}