1、自定义权限认证的实现思路
-
自定义一个实体类
LoginUser
用来保存SygUser
用户信息以及权限信息Set<String> permissions
-
让这个
LoginUser
实体类实现Userdetails
接口 -
自定义的
MyUserDetailsServiceImpl
类的loadUserByUsername()
方法中实现用户信息查询及该用户的权限信息查询 -
自定义的
MyUserDetailsServiceImpl
类的loadUserByUsername()
方法返回LoginUser
实体类对象
2、代码实现以及中间出现的问题的解决方法
2.1 定义LoginUser实体类【初始定义,一会还会儿修改】
自定义登录用户实体类LoginUser
如下,让其实现UserDetails
接口
package com.yige.pojo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
/**
* @Author: karma
* @Date: 2022/3/21 0021 - 03 - 21 - 16:17
* @Description: com.yige.pojo
* @version: 1.0
*/
public class LoginUser implements UserDetails{
/**
* 权限列表
*/
private Set<String> permissions;
/**
* 用户信息
*/
private SygUser user;
public LoginUser() {
}
public LoginUser(SygUser user, Set<String> permissions) {
this.permissions = permissions;
this.user = user;
}
public Set<String> getPermissions() {
return permissions;
}
public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}
public SygUser getUser() {
return user;
}
public void setUser(SygUser user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@JsonIgnore
@Override
public String getPassword() {
return null;
}
@Override
public String getUsername() {
return null;
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return false;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return false;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return false;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return false;
}
}
2.2 修改MyUserDetailsServiceImpl类
修改loadUserByUsername(String userName)
方法的实现逻辑,让其最后返回LoginUser
实体类对象,具体实现如下
package com.yige.service.impl;
import com.yige.pojo.LoginUser;
import com.yige.pojo.SygUser;
import com.yige.service.SygUserSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.Set;
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SygUserSerivce userSerivce;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
if(!StringUtils.hasLength(userName)) throw new UsernameNotFoundException("用户名不能为空");
SygUser sygUser = userSerivce.selectUserByUserName(userName);
if(null == sygUser) throw new UsernameNotFoundException("用户不存在");
// 这里我们先不存数据库查权限信息,先给个空值,看看流程可不可以走通
return createLoginUser(sygUser, new HashSet<String>());
}
public UserDetails createLoginUser(SygUser user, Set<String> permissions) {
return new LoginUser(user, permissions);
}
}
保存修改,重启项目,进行测试;
2.3 测试中遇到的问题及处理方法
使用账号student001
,密码student001
进行正常登录测试, 发现报错User account is locked
, 这肯定不对,我们的账号是在数据库查出来的,状态肯定是正常启用的,没有被锁定,这是怎么回事?先说说怎么解决,后面再说为什么会这样
2.3.1 User account is locked异常
`User account is locked异常解决方法
修改你的实体类LoginUser
的isAccountNonLocked
方法返回值为true
即可
修改之后
重新启动项目,再次测试
2.3.2 User is disabled异常
`User is disabled异常解决方法
修改你的实体类LoginUser
的isEnabled
方法返回值为true
即可
重新启动项目,再次测试
2.3.3 User account has expired异常
`User account has expired异常解决方法
修改你的实体类LoginUser
的isAccountNonExpired
方法返回值为true
即可
重新启动项目,再次测试
2.3.4 Bad credentials异常
Bad credentials异常解决方法
修改你的实体类LoginUser
的isCredentialsNonExpired
方法返回值为true
即可
重新启动项目,再次测试,
2.3.5 Empty encoded password异常
发现还是报错Bad credentials
,但是后台console打印了一条warn
日志,说是Empty encoded password
,说明我们提供的userDetails的实现类实现的getPassword方法无法正常获取到用户密码, 怎么处理?
Empty encoded password异常解决方法
修改你的实体类LoginUser
的getPassword
方法返回值为你数据库用户实体类对象的密码即可
重新启动项目,再次测试
使用账号student001
,密码student001
进行正常登录测试, 登录成功
使用账号manager
,密码manager
进行正常登录测试, 登录成功, OK, 基本流程走通
2.4 所有问题处理完成之后的LoginUser实体类的全貌
package com.yige.pojo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
public class LoginUser implements UserDetails {
/**
* 权限列表
*/
private Set<String> permissions;
/**
* 用户信息
*/
private SygUser user;
public LoginUser() {
}
public LoginUser(SygUser user, Set<String> permissions) {
this.permissions = permissions;
this.user = user;
}
public Set<String> getPermissions() {
return permissions;
}
public void setPermissions(Set<String> permissions) {
this.permissions = permissions;
}
public SygUser getUser() {
return user;
}
public void setUser(SygUser user) {
this.user = user;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@JsonIgnore
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getUsername();
}
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@JsonIgnore
@Override
public boolean isEnabled() {
return true;
}
}
2.5 我是怎么知道修改哪些方法就能解决问题的?
其实一开始我也不知道怎么处理这些问题, 我也觉得很奇怪, 为什么就会报这种错误?
直到我想到LoginUser实现了UserDetails接口, 里面有很多需要子类去实现的方法, 我就去看看了一下UserDetails接口中关于这几个方法的注释, 一下就明白了, 我们一起看看
以上就是为什么要修改LoginUser实现类那几个重写方法的返回值的原因, 不知道我有没有说清楚,哈哈, 个人理解哈,不喜勿喷~~
3、自定义权限的代码实现
3.1 实现查询用户权限的接口逻辑
- service层
package com.yige.service;
import com.yige.pojo.SygUser;
import java.util.HashSet;
import java.util.Set;
public interface SygPermissionService {
/**
* @description 获取登录用户的菜单数据权限
* @param user 用户信息
* @return 菜单权限信息
*/
public Set<String> getMenuPermission(SygUser user);
}
- service.impl层
package com.yige.service.impl;
import com.yige.mapper.SygPermissionMapper;
import com.yige.pojo.SygUser;
import com.yige.service.SygPermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.Set;
@Service
public class SygPermissionServiceImpl implements SygPermissionService {
@Autowired
private SygPermissionMapper permissionMapper;
public Set<String> getMenuPermission(SygUser user) {
Set<String> perms = new HashSet<>();
perms.addAll(permissionMapper.selectMenuPermsByUserId(user.getUserId()));
return perms;
}
}
- mapper层
package com.yige.mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface SygPermissionMapper {
List<String> selectMenuPermsByUserId(@Param("userId") Long userId);
}
- SygPermissionMapper.xml层
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yige.mapper.SygPermissionMapper">
<select id="selectMenuPermsByUserId" resultType="string">
select
bm.menu_perms
from
bs_user as bu
left join
bs_user_role as bur
on
bu.user_id = bur.role_id
left join
bs_role_permission as brp
on
bur.role_id = brp.role_id
left join
bs_menu as bm
on
bm.menu_id = brp.menu_id
where
bu.user_id = #{userId}
</select>
</mapper>
3.2 修改MyUserDetailsServiceImpl类的实现逻辑
package com.yige.service.impl;
import com.yige.pojo.LoginUser;
import com.yige.pojo.SygUser;
import com.yige.service.SygPermissionService;
import com.yige.service.SygUserSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Set;
@Service
public class MyUserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SygUserSerivce userSerivce;
@Autowired
private SygPermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
if(!StringUtils.hasLength(userName)) throw new UsernameNotFoundException("用户名不能为空");
SygUser sygUser = userSerivce.selectUserByUserName(userName);
if(null == sygUser) throw new UsernameNotFoundException("用户不存在");
return createLoginUser(sygUser, permissionService.getMenuPermission(sygUser));
}
public UserDetails createLoginUser(SygUser user, Set<String> permissions) {
permissions.stream().forEach(System.out::println);
return new LoginUser(user, permissions);
}
}
重新启动项目,再次测试
使用账号student001
,密码student001
进行正常登录测试, 登录成功
控制台打印用户
student001
的权限列表
使用账号manager
,密码manager
进行正常登录测试, 登录成功
控制台打印manager
用户的权限列表
至此,自定义权限认证的逻辑就全部实现完成了, 起始回头自己在捋一捋, 会不会发现其实也不太复杂,流程很清晰,实现很简单。喜欢我的文章请多多关注哈, 大家一起进步~
4、下一篇主要内容
下一篇主要研究一下WebSecurityConfigurerAdapter这个配置类以及
自定义配置类SecurityConfig来演示一下Spring security提供的各个接口的的作用以及怎么使用,敬请关注~