权限管理一共五张表:实体类:递归查询菜单递归删除菜单:权限管理
引入依赖实体类第一步:登录,实现SpringSecrity的UserDetailsService接口第二步:登录成功,将权限信息存入redis中第三步:生成token返回第四步:将token放入请求头第五步:将token从请求头中拿出来,从token获取用户名,拿着用户名从redis获取权限列表springsecrity对token的登出处理:默认密码加密方式token管理工具类未授权的统一处理编写springsecrity的配置类:
权限管理一共五张表: 实体类:用户表:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("acl_user")
@ApiModel(value="User对象", description="用户表")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "会员id")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
@ApiModelProperty(value = "微信openid")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "用户头像")
private String salt;
@ApiModelProperty(value = "用户签名")
private String token;
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
private Boolean isDeleted;
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
private Date gmtModified;
}
角色表:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("acl_role")
@ApiModel(value="Role对象", description="")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "角色id")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
@ApiModelProperty(value = "角色名称")
private String roleName;
@ApiModelProperty(value = "角色编码")
private String roleCode;
@ApiModelProperty(value = "备注")
private String remark;
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
private Boolean isDeleted;
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
private Date gmtModified;
}
权限表:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("acl_permission")
@ApiModel(value="Permission对象", description="权限")
public class Permission implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "编号")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
@ApiModelProperty(value = "所属上级")
private String pid;
@ApiModelProperty(value = "名称")
private String name;
@ApiModelProperty(value = "类型(1:菜单,2:按钮)")
private Integer type;
@ApiModelProperty(value = "权限值")
private String permissionValue;
@ApiModelProperty(value = "访问路径")
private String path;
@ApiModelProperty(value = "组件路径")
private String component;
@ApiModelProperty(value = "图标")
private String icon;
@ApiModelProperty(value = "状态(0:禁止,1:正常)")
private Integer status;
@ApiModelProperty(value = "层级")
@TableField(exist = false)
private Integer level;
@ApiModelProperty(value = "下级")
@TableField(exist = false)
private List children;
@ApiModelProperty(value = "是否选中")
@TableField(exist = false)
private boolean isSelect;
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
private Boolean isDeleted;
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
private Date gmtModified;
}
用户角色表:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("acl_user_role")
@ApiModel(value="UserRole对象", description="")
public class UserRole implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "主键id")
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
@ApiModelProperty(value = "角色id")
private String roleId;
@ApiModelProperty(value = "用户id")
private String userId;
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
private Boolean isDeleted;
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
private Date gmtModified;
}
角色权限表:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("acl_role_permission")
@ApiModel(value="RolePermission对象", description="角色权限")
public class RolePermission implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.ID_WORKER_STR)
private String id;
private String roleId;
private String permissionId;
@ApiModelProperty(value = "逻辑删除 1(true)已删除, 0(false)未删除")
private Boolean isDeleted;
@ApiModelProperty(value = "创建时间")
private Date gmtCreate;
@ApiModelProperty(value = "更新时间")
private Date gmtModified;
}
递归查询菜单
简单增删改查不做赘述:
controller:
@GetMapping("menu")
public R getMenu(){
//获取当前登录用户用户名
String username = SecurityContextHolder.getContext().getAuthentication().getName();
List permissionList = indexService.getMenu(username);
return R.ok().data("permissionList", permissionList);
}
service:
public List getMenu(String username) {
User user = userService.selectByUsername(username);
//根据用户id获取用户菜单权限
List permissionList = permissionService.selectPermissionByUserId(user.getId());
return permissionList;
}
通过用户id调研权限service的selectPermissionByUserId方法:
@Override
public List selectPermissionByUserId(String userId) {
List selectPermissionList = null;
if(this.isSysAdmin(userId)) {
//如果是超级管理员,获取所有菜单
selectPermissionList = baseMapper.selectList(null);
} else {
selectPermissionList = baseMapper.selectPermissionByUserId(userId);
}
List permissionList = PermissionHelper.bulid(selectPermissionList);
List result = MemuHelper.bulid(permissionList);
return result;
}
不是超级管理员获取菜单:
p.id,p.pid,p.name,p.type,p.permission_value,path,p.component,p.icon,p.status,p.is_deleted,p.gmt_create,p.gmt_modified
采用PermissionHelper 工具类:
通过用户id查询的权限数据构建菜单数据
第一步查找第一级菜单:遍历循环权限数据list如果是第一级菜单pid为0"0".equals(treeNode.getPid()),将循环到的一条权限数据和list传入findChildren方法中
第二步:通过第一级菜单查找下面的第二级菜单treeNode.getId().equals(it.getPid()),通过第二级菜单再次递归查询第三级菜单,如果找不到更低一级的菜单,则返回上一级菜单节点:
public class PermissionHelper {
public static List bulid(List treeNodes) {
List trees = new ArrayList<>();
for (Permission treeNode : treeNodes) {
if ("0".equals(treeNode.getPid())) {
treeNode.setLevel(1);
trees.add(findChildren(treeNode,treeNodes));
}
}
return trees;
}
public static Permission findChildren(Permission treeNode,List treeNodes) {
treeNode.setChildren(new ArrayList());
for (Permission it : treeNodes) {
if(treeNode.getId().equals(it.getPid())) {
int level = treeNode.getLevel() + 1;
it.setLevel(level);
if (treeNode.getChildren() == null) {
treeNode.setChildren(new ArrayList<>());
}
treeNode.getChildren().add(findChildren(it,treeNodes));
}
}
return treeNode;
}
}
测试
{
"success": true,
"code": 20000,
"message": "成功",
"data": {
"permissionList": [
{
"redirect": "noredirect",
"path": "/acl",
"component": "Layout",
"hidden": false,
"children": [
{
"path": "user/list",
"component": "/acl/user/list",
"hidden": false,
"meta": {
"title": "用户管理"
},
"name": "name_1195268616021139457"
},
{
"path": "user/add",
"component": "/acl/user/form",
"hidden": true,
"meta": {
"title": "添加"
},
"name": "name_1195269295926206466"
},
{
"path": "user/update/:id",
"component": "/acl/user/form",
"hidden": true,
"meta": {
"title": "修改"
},
递归删除菜单:
通过菜单id,递归查询子菜单id
controller:
@ApiOperation(value = "递归删除菜单")
@DeleteMapping("remove/{id}")
public R remove(@PathVariable String id) {
permissionService.removeChildByIdGuli(id);
return R.ok();
}
service:
一个一个遍历,判断是否具有子菜单
//============递归删除菜单==================================
@Override
public void removeChildByIdGuli(String id) {
//1 创建list集合,用于封装所有删除菜单id值
List idList = new ArrayList<>();
//2 向idList集合设置删除菜单id
this.selectPermissionChildById(id,idList);
//把当前id封装到list里面
idList.add(id);
baseMapper.deleteBatchIds(idList);
}
//2 根据当前菜单id,查询菜单里面子菜单id,封装到list集合
private void selectPermissionChildById(String id, List idList) {
//查询菜单里面子菜单id
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("pid",id);
wrapper.select("id");
List childIdList = baseMapper.selectList(wrapper);
//把childIdList里面菜单id值获取出来,封装idList里面,做递归查询
childIdList.stream().forEach(item -> {
//封装idList里面
idList.add(item.getId());
//递归查询
this.selectPermissionChildById(item.getId(),idList);
});
}
权限管理
SpringSecurity认证授权过程:
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
实体类
@Data
@Slf4j
public class SecurityUser implements UserDetails {
//当前登录用户
private transient User currentUserInfo;
//当前权限
private List permissionValueList;
public SecurityUser() {
}
public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}
@Override
public Collection extends GrantedAuthority> getAuthorities() {
Collection authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
@Override
public String getUsername() {
return currentUserInfo.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;
}
}
@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "微信openid")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "用户头像")
private String salt;
@ApiModelProperty(value = "用户签名")
private String token;
}
第一步:登录,实现SpringSecrity的UserDetailsService接口
查询用户信息,以及用户权限信息
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库中取出用户信息
User user = userService.selectByUsername(username);
// 判断用户是否存在
if (null == user){
//throw new UsernameNotFoundException("用户名不存在!");
}
// 返回UserDetails实现类
com.atguigu.security.entity.User curUser = new com.atguigu.security.entity.User();
BeanUtils.copyProperties(user,curUser);
List authorities = permissionService.selectPermissionValueByUserId(user.getId());
SecurityUser securityUser = new SecurityUser(curUser);
securityUser.setPermissionValueList(authorities);
return securityUser;
}
}
//根据用户id获取用户菜单
@Override
public List selectPermissionValueByUserId(String id) {
List selectPermissionValueList = null;
if(this.isSysAdmin(id)) {
//如果是系统管理员,获取所有权限
selectPermissionValueList = baseMapper.selectAllPermissionValue();
} else {
selectPermissionValueList = baseMapper.selectPermissionValueByUserId(id);
}
return selectPermissionValueList;
}
第二步:登录成功,将权限信息存入redis中
TokenLoginFilter登录过滤器,继承UsernamePasswordAuthenticationFilter,对用户名密码进行登录校验
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
this.authenticationManager = authenticationManager;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}
//会调用UserDetailsService 的loadUserByUsername进行校验
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException {
try {
User user = new ObjectMapper().readValue(req.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException, ServletException {
SecurityUser user = (SecurityUser) auth.getPrincipal();
String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
ResponseUtil.out(res, R.ok().data("token", token));
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
第三步:生成token返回
successfulAuthentication登录成功,将信息存入redis中,生成token返回
TokenManager 工具类:
@Component
public class TokenManager {
private long tokenExpiration = 24*60*60*1000;
private String tokenSignKey = "123456";
public String createToken(String username) {
String token = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
return token;
}
public String getUserFromToken(String token) {
String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return user;
}
public void removeToken(String token) {
//jwttoken无需删除,客户端扔掉即可。
}
}
第四步:将token放入请求头
// request拦截器
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
},
error => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
第五步:将token从请求头中拿出来,从token获取用户名,拿着用户名从redis获取权限列表
(谷粒学院这里的权限列表保存在redis里面感觉没什么用,因为菜单本来就是通过权限递归查询的,不会出现权限不够的问题,但是菜单如果不做权限递归处理,这里就可以对用户访问权限进行判断,并且权限列表是保存在redis中也不会对数据库造成压力,我的理解,大佬勿喷)
通过访问过滤器TokenAuthenticationFilter实现
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
super(authManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
logger.info("================="+req.getRequestURI());
if(req.getRequestURI().indexOf("admin") == -1) {
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = null;
try {
authentication = getAuthentication(req);
} catch (Exception e) {
ResponseUtil.out(res, R.error());
}
if (authentication != null) {
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
ResponseUtil.out(res, R.error());
}
chain.doFilter(req, res);
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
// token置于header里
String token = request.getHeader("token");
if (token != null && !"".equals(token.trim())) {
String userName = tokenManager.getUserFromToken(token);
List permissionValueList = (List) redisTemplate.opsForValue().get(userName);
Collection authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
if (!StringUtils.isEmpty(userName)) {
return new UsernamePasswordAuthenticationToken(userName, token, authorities);
}
return null;
}
return null;
}
}
springsecrity对token的登出处理:
因为token是无状态的,并且不保存在服务端所以只有通过token解析用户信息,将redis中的用户权限信息删除,实现系统登出功能
public class TokenLogoutHandler implements LogoutHandler {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenLogoutHandler(TokenManager tokenManager, RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String token = request.getHeader("token");
if (token != null) {
tokenManager.removeToken(token);
//清空当前用户缓存中的权限数据
String userName = tokenManager.getUserFromToken(token);
redisTemplate.delete(userName);
}
ResponseUtil.out(response, R.ok());
}
}
默认密码加密方式
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder() {
this(-1);
}
public DefaultPasswordEncoder(int strength) {
}
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
}
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
}
}
token管理工具类
@Component
public class TokenManager {
private long tokenExpiration = 24*60*60*1000;
private String tokenSignKey = "123456";
public String createToken(String username) {
String token = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
.signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
return token;
}
public String getUserFromToken(String token) {
String user = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return user;
}
public void removeToken(String token) {
//jwttoken无需删除,客户端扔掉即可。
}
}
未授权的统一处理
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
编写springsecrity的配置类:
将认证过滤器,授权过滤器,密码加密方式,请求放行等配置到配置类中
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
private TokenManager tokenManager;
private DefaultPasswordEncoder defaultPasswordEncoder;
private RedisTemplate redisTemplate;
@Autowired
public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
TokenManager tokenManager, RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.defaultPasswordEncoder = defaultPasswordEncoder;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthorizedEntryPoint())
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout")
.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
.addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))
.addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**",
"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**"
);
}
}
完成!!!



