1、框架介绍
Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
Spring Security其实就是用filter,多请求的路径进行过滤。
(1)如果是基于Session,那么Spring-security会对cookie里的sessionid进行解析,找到服务器存储的sesion信息,然后判断当前用户是否符合请求的要求。
(2)如果是token,则是解析出token,然后将当前请求加入到Spring-security管理的权限信息中去
2、认证与授权实现思路
如果系统的模块众多,每个模块都需要就行授权与认证,所以我们选择基于token的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为key,权限列表为value的形式存入redis缓存中,根据用户名相关信息生成token返回,浏览器将token记录到cookie中,每次调用api接口都默认将token携带到header请求头中,Spring-security解析header头获取token信息,解析token获取当前用户名,根据用户名就可以从redis中获取权限列表,这样Spring-security就能够判断当前请求是否有权限访问。
二、整合Spring Security
1、在common下创建spring_security模块
2、在spring_security引入相关依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>com.javaclimb</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring_security</artifactId>
<dependencies>
<dependency>
<groupId>com.javaclimb</groupId>
<artifactId>common_util</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.javaclimb</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
</project>
<dependency>
<groupId>com.javaclimb</groupId>
<artifactId>spring_security</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
4、代码结构说明
核心配置类:TokenWebSecurityConfig
用户实体类:User
登录过滤器:TokenLoginFilter
访问过滤器:TokenAuthenticationFilter
密码处理类:DefaultPasswordEncoder
登出业务逻辑类:TokenLogoutHandler
token操作工具类:TokenManager
未授权的统一处理方式:UnauthorizedEntryPoint
5、辅助类
(1) UserService.java
package com.stu.service.acl.service;
import com.stu.service.acl.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 用户表 服务类
* </p>
*
* @author stu
* @since 2022-08-16
*/
public interface UserService extends IService<User> {
/***********************************
* 用途说明:获取用户详情
* @param username
* 返回值说明:
* @return com.stu.service.acl.entity.User
***********************************/
User selectByUserName(String username);
}
(2) UserServiceImpl.java
package com.stu.service.acl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.stu.service.acl.entity.User;
import com.stu.service.acl.mapper.UserMapper;
import com.stu.service.acl.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 用户表 服务实现类
* </p>
*
* @author stu
* @since 2022-08-16
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
/***********************************
* 用途说明:获取用户详情
* @param username
* 返回值说明:
* @return com.stu.service.acl.entity.User
***********************************/
@Override
public User selectByUserName(String username) {
return baseMapper.selectOne(new QueryWrapper<User>().eq("username", username));
}
}
package com.stu.service.acl.mapper;
import com.stu.service.acl.entity.Permission;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import java.util.List;
/**
* <p>
* 权限 Mapper 接口
* </p>
*
* @author stu
* @since 2022-08-16
*/
public interface PermissionMapper extends BaseMapper<Permission> {
/***********************************
* 用途说明:获取全部菜单的权限
* 返回值说明:
* @return java.util.List<java.lang.String>
***********************************/
List<String> selectAllPermissionValue();
/***********************************
* 用途说明:根据用户id查询所有权限
* @param id
* 返回值说明:
* @return java.util.List<java.lang.String>
***********************************/
List<String> selectPermissionValueByUserId(String id);
/***********************************
* 用途说明:根据用户id查询所有菜单
* @param userId
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Permission>
***********************************/
List<Permission> selectPermissionByUserId(String userId);
}
<?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.stu.service.acl.mapper.PermissionMapper">
<resultMap id="permissionMap" type="com.stu.service.acl.entity.Permission">
<result property="id" column="id"/>
<result property="pid" column="pid"/>
<result property="name" column="name"/>
<result property="type" column="type"/>
<result property="permissionValue" column="permission_value"/>
<result property="path" column="path"/>
<result property="component" column="component"/>
<result property="icon" column="icon"/>
<result property="status" column="status"/>
<result property="isDeleted" column="is_deleted"/>
<result property="gmtCreate" column="gmt_create"/>
<result property="gmtModified" column="gmt_modified"/>
</resultMap>
<!-- 用于select查询公用抽取的列 -->
<sql id="columns">
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
</sql>
<select id="selectPermissionByUserId" resultMap="permissionMap">
select
<include refid="columns" />
from acl_user_role ur
inner join acl_role_permission rp on rp.role_id = ur.role_id
inner join acl_permission p on p.id = rp.permission_id
where ur.user_id = #{userId}
and ur.is_deleted = 0
and rp.is_deleted = 0
and p.is_deleted = 0
</select>
<select id="selectPermissionValueByUserId" resultType="String">
select
p.permission_value
from acl_user_role ur
inner join acl_role_permission rp on rp.role_id = ur.role_id
inner join acl_permission p on p.id = rp.permission_id
where ur.user_id = #{userId}
and ur.is_deleted = 0
and rp.is_deleted = 0
and p.is_deleted = 0
</select>
<select id="selectAllPermissionValue" resultType="String">
select
permission_value
from acl_permission
where is_deleted = 0
</select>
</mapper>
package com.stu.service.acl.service;
import com.alibaba.fastjson.JSONObject;
import com.stu.service.acl.entity.Permission;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 权限 服务类
* </p>
*
* @author stu
* @since 2022-08-16
*/
public interface PermissionService extends IService<Permission> {
/***********************************
* 用途说明:查询所有权限菜单
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Permission>
***********************************/
List<Permission> ListAllPermissions();
/***********************************
* 用途说明:递归删除菜单
* @param id
* 返回值说明:
* @return boolean
***********************************/
boolean removeChildById(String id);
/***********************************
* 用途说明:根據角色獲取菜單
* @param id
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Permission>
***********************************/
List<Permission> listAllMenu(String id);
/***********************************
* 用途说明:给角色分配菜单权限
* @param roleId
* @param permissionId
* 返回值说明:
* @return boolean
***********************************/
boolean saveRolePermissionrelationShip(String roleId, String[] permissionId);
/***********************************
* 用途说明:根据用户id查询有权限的菜单
* @param id
* 返回值说明:
* @return java.util.List<java.lang.String>
***********************************/
List<String> selectPermissionValueListByUserId(String id);
/***********************************
* 用途说明:根据用户id查询所有权限的菜单详细列表
* @param userId
* 返回值说明:
* @return java.util.List<org.json.JSONObject>
***********************************/
List<JSONObject> selectPermissionByUserId(String userId);
}
(6) PermissionServiceImpl.java实现类
package com.stu.service.acl.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.stu.service.acl.entity.Permission;
import com.stu.service.acl.entity.RolePermission;
import com.stu.service.acl.entity.User;
import com.stu.service.acl.mapper.PermissionMapper;
import com.stu.service.acl.service.PermissionService;
import com.stu.service.acl.service.RolePermissionService;
import com.stu.service.acl.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
* 权限 服务实现类
* </p>
*
* @author stu
* @since 2022-08-16
*/
@Service
public class PermissionServiceImpl extends ServiceImpl<PermissionMapper, Permission> implements PermissionService {
@Autowired
private RolePermissionService rolePermissionService;
@Autowired
private UserService userService;
/***********************************
* 用途说明:查询所有权限菜单
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Permission>
***********************************/
@Override
public List<Permission> ListAllPermissions() {
QueryWrapper<Permission> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("id");
return bulidPermission(baseMapper.selectList(queryWrapper));
}
/***********************************
* 用途说明:把返回所有菜单list集合进行封装的方法
* @param list
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Permission>
***********************************/
private List<Permission> bulidPermission(List<Permission> list) {
//创建list集合,用于数据最终封装
List<Permission> finalNode = new ArrayList<>();
//把所有菜单list集合遍历,得到顶层菜单 pid=0菜单,设置level是1
for (Permission permission : list) {
//得到顶层菜单 pid=0菜单
if ("0".equals(permission.getPid())) {
permission.setLevel(1);
//根据顶层菜单,向里面进行查询子菜单,封装到finalNode里面
finalNode.add(selectChildren(permission, list));
}
}
return finalNode;
}
/***********************************
* 用途说明:递归查询下级菜单
* @param permission
* @param list
* 返回值说明:
* @return com.stu.service.acl.entity.Permission
***********************************/
private Permission selectChildren(Permission permission, List<Permission> list) {
//1 因为向一层菜单里面放二层菜单,二层里面还要放三层,把对象初始化
permission.setChildren(new ArrayList<Permission>());
//2 遍历所有菜单list集合,进行判断比较,比较id和pid值是否相同
for (Permission child : list) {
if (permission.getId().equals(child.getPid())) {
child.setLevel(permission.getLevel() + 1);
if (child.getChildren() == null) {
child.setChildren(new ArrayList<>());
}
// permission.getChildren().add(child);
// selectChildren(child,list);
permission.getChildren().add(selectChildren(child, list));
}
}
return permission;
}
/***********************************
* 用途说明:递归删除菜单
* @param id
* 返回值说明:
* @return boolean
***********************************/
@Override
public boolean removeChildById(String id) {
List<String> idList = new ArrayList<>();
selectChildListById(id, idList);
idList.add(id);
return baseMapper.deleteBatchIds(idList) > 0;
}
/***********************************
* 用途说明:根據角色獲取菜單
* @param id
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Permission>
***********************************/
@Override
public List<Permission> listAllMenu(String id) {
//获取所有菜单
List<Permission> allPermissionList = baseMapper.selectList(new QueryWrapper<>());
//根据角色id呼气角色权限列表
List<RolePermission> rolePermissionsList = rolePermissionService
.list(new QueryWrapper<RolePermission>().eq("role_id", id));
//遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记
List<String> permissionIdList = rolePermissionsList.stream().map(e -> e.getPermissionId()).collect(Collectors.toList());
allPermissionList.forEach(permission -> {
if (permissionIdList.contains(permission.getId())) {
permission.setHasSelect(true);
} else {
permission.setHasSelect(false);
}
});
/*for (int i = 0; i < allPermissionList.size(); i++) {
Permission permission = allPermissionList.get(i);
for (int m = 0; m < rolePermissionList.size(); m++) {
RolePermission rolePermission = rolePermissionList.get(m);
if(rolePermission.getPermissionId().equals(permission.getId())) {
permission.setSelect(true);
}
}
}*/
return bulidPermission(allPermissionList);
}
/***********************************
* 用途说明:给角色分配菜单权限
* @param roleId
* @param permissionId
* 返回值说明:
* @return boolean
***********************************/
@Override
public boolean saveRolePermissionrelationShip(String roleId, String[] permissionId) {
//删除旧的权限
rolePermissionService.remove(new QueryWrapper<RolePermission>().eq("role_id", roleId));
List<RolePermission> list = new ArrayList<>();
for (String id : permissionId) {
RolePermission rolePermission = new RolePermission();
rolePermission.setRoleId(roleId);
rolePermission.setPermissionId(id);
list.add(rolePermission);
}
return rolePermissionService.saveBatch(list);
}
/***********************************
* 用途说明:根据用户id查询有权限的菜单
* @param id
* 返回值说明:
* @return java.util.List<java.lang.String>
***********************************/
@Override
public List<String> selectPermissionValueListByUserId(String id) {
List<String> list;
if (checkAdmin(id)) {
//如果是超级管理员获取所有权限
list = baseMapper.selectAllPermissionValue();
} else {
//根据用户id查询所有权限
list = baseMapper.selectPermissionValueByUserId(id);
}
return list;
}
/***********************************
* 用途说明:根据用户id查询所有权限的菜单详细列表
* @param userId
* 返回值说明:
* @return java.util.List<org.json.JSONObject>
***********************************/
@Override
public List<JSONObject> selectPermissionByUserId(String userId) {
List<Permission> selectPermissionList = null;
if (checkAdmin(userId)) {
//如果是超级管理员获取所有权限
selectPermissionList = baseMapper.selectList(null);
} else {
//根据用户id查询所有权限
selectPermissionList = baseMapper.selectPermissionByUserId(userId);
}
//先转换成树状
List<Permission> permissionList = bulidPermission(selectPermissionList);
//然后转化成前端需要的格式
List<JSONObject> result = bulidJson(permissionList);
return result;
}
/***********************************
* 用途说明:转化成前端需要的格式
* @param permissionList
* 返回值说明:
* @return java.util.List<org.json.JSONObject>
***********************************/
private List<JSONObject> bulidJson(List<Permission> permissionList) {
List<JSONObject> menus = new ArrayList<>();
if (permissionList.size() == 1) {
Permission topNode = permissionList.get(0);
//组建左侧一级菜单
List<Permission> oneMenuList = topNode.getChildren();
for (Permission one : oneMenuList) {
JSONObject oneMenu = new JSONObject();
oneMenu.put("path", one.getPath());
oneMenu.put("component", one.getComponent());
oneMenu.put("redirect", "noredirect");//第一级不需要重定向
oneMenu.put("name", "name_" + one.getId());
oneMenu.put("hidden", false);//一级不需要因此,3级需要
JSONObject oneMeta = new JSONObject();
oneMeta.put("title", one.getName());
oneMeta.put("icon", one.getIcon());
oneMenu.put("meta", oneMeta);
List<JSONObject> children = new ArrayList<>();
List<Permission> twoMenuList = one.getChildren();//二级菜单
for (Permission two : twoMenuList) {
JSONObject twoMenu = new JSONObject();
twoMenu.put("path", two.getPath());
twoMenu.put("component", two.getComponent());
// twoMenu.put("redirect", "noredirect");//第一级不需要重定向
twoMenu.put("name", "name_" + two.getId());
twoMenu.put("hidden", false);//一级不需要因此,3级需要
JSONObject twoMeta = new JSONObject();
twoMeta.put("title", two.getName());
twoMeta.put("icon", two.getIcon());
twoMenu.put("meta", twoMeta);
children.add(twoMenu);
//功能按钮
List<Permission> threeMenuList = two.getChildren();
for (Permission three : threeMenuList) {
if (StringUtils.isEmpty(three.getPath())) {
continue;
}
JSONObject threeMenu = new JSONObject();
threeMenu.put("path", three.getPath());
threeMenu.put("component", three.getComponent());
// threeMenu.put("redirect", "noredirect");//第一级不需要重定向
threeMenu.put("name", "name_" + three.getId());
threeMenu.put("hidden", true);//一级不需要因此,3级需要
JSONObject threeMeta = new JSONObject();
threeMeta.put("title", three.getName());
threeMeta.put("icon", three.getIcon());
threeMenu.put("meta", threeMeta);
children.add(threeMenu);
}
}
oneMenu.put("children", children);
menus.add(oneMenu);
}
}
return menus;
}
/***********************************
* 用途说明:判断是否管理员
* @param id
* 返回值说明:
* @return boolean
***********************************/
private boolean checkAdmin(String id) {
User user = userService.getById(id);
if (user != null && "admin".equals(user.getUsername())) {
return true;
}
return false;
}
/***********************************
* 用途说明:根据当前菜单id查询他的子子孙孙id,封装到list集合
* @param id
* @param idList
* 返回值说明:
***********************************/
private void selectChildListById(String id, List<String> idList) {
//查询当前菜单的下级
QueryWrapper<Permission> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("pid", id);
queryWrapper.select("id");
List<Permission> childList = baseMapper.selectList(queryWrapper);
//把childIdList里面菜单id值获取出来,封装idList里面,做递归查询
childList.forEach(item -> {
idList.add(item.getId());
selectChildListById(item.getId(), idList);
});
}
}
package com.stu.service.acl.service.impl;
import com.stu.security.entity.SecurityUser;
import com.stu.service.acl.entity.User;
import com.stu.service.acl.service.PermissionService;
import com.stu.service.acl.service.UserService;
import com.stu.service.base.exception.CustomException;
import com.stu.service.base.result.ResultCodeEnum;
import org.springframework.beans.BeanUtils;
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 java.util.List;
/******************************
* 用途说明:自定义UserDetailsService实现类,认证用户详情
* 作者姓名: Administrator
* 创建时间: 2022-09-01 10:05
******************************/
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
/**
*根据用户名查询用户信息
* @param username the username identifying the user whose data is required.
* @return a fully populated user record (never <code>null</code>)
* @throws UsernameNotFoundException if the user could not be found or the user has no
* GrantedAuthority
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.selectByUserName(username);
if(user == null){
throw new CustomException(ResultCodeEnum.LOGIN_MOBILE_ERROR);
}
// 返回UserDetails实现类
com.stu.security.entity.User curUser = new com.stu.security.entity.User();
BeanUtils.copyProperties(user,curUser);
//根据用户id查询有权限的菜单
List<String> authorities = permissionService.selectPermissionValueListByUserId(user.getId());
SecurityUser securityUser = new SecurityUser(curUser);
securityUser.setPermissionValueList(authorities);
return securityUser;
}
}
(8)ResponseUtil.java
package com.stu.service.base.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.stu.service.base.result.R;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 20:44
******************************/
public class ResponseUtil {
public static void out(HttpServletResponse response, R r) {
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try {
mapper.writeValue(response.getWriter(), r);
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.stu.service.acl.service;
import com.stu.service.acl.entity.Permission;
import com.stu.service.acl.entity.Role;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import java.util.Map;
/**
* <p>
* 服务类
* </p>
*
* @author stu
* @since 2022-08-16
*/
public interface RoleService extends IService<Role> {
/***********************************
* 用途说明:根据用户获取角色
* @param userId
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Permission>
***********************************/
Map<String, Object> findRoleByUserId(String userId);
/***********************************
* 用途说明:给用户分配角色权限
* @param userId
* @param permissionIds
* 返回值说明:
* @return boolean
***********************************/
boolean saveUserRelationShip(String userId, String[] permissionIds);
/***********************************
* 用途说明:根据userid获取用户信息
* @param id
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Role>
***********************************/
List<Role> selectRoleByUserId(String id);
}
package com.stu.service.acl.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.stu.service.acl.entity.Permission;
import com.stu.service.acl.entity.Role;
import com.stu.service.acl.entity.RolePermission;
import com.stu.service.acl.entity.UserRole;
import com.stu.service.acl.mapper.RoleMapper;
import com.stu.service.acl.service.RoleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.stu.service.acl.service.UserRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* <p>
* 服务实现类
* </p>
*
* @author stu
* @since 2022-08-16
*/
@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {
@Autowired
private UserRoleService userRoleService;
/***********************************
* 用途说明:根据用户获取角色
* @param userId
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Permission>
***********************************/
@Override
public Map<String, Object> findRoleByUserId(String userId) {
//获取所有角色
List<Role> allRoleList = baseMapper.selectList(new QueryWrapper<>());
//根据用户id获取角色列表
List<UserRole> existUserRoleList = userRoleService
.list(new QueryWrapper<UserRole>().eq("user_id", userId).select("role_id"));
//遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记
List<String> existRoleLists = existUserRoleList.stream().map(e -> e.getRoleId()).collect(Collectors.toList());
List<Role> assignRoles = new ArrayList<>();
allRoleList.forEach(role -> {
if (existRoleLists.contains(role.getId())) {
assignRoles.add(role);
}
});
Map<String, Object> roleMap = new HashMap<>();
roleMap.put("assignRoles", assignRoles);
roleMap.put("allRoleList", allRoleList);
return roleMap;
}
/***********************************
* 用途说明:给用户分配角色权限
* @param userId
* @param roleIds
* 返回值说明:
* @return boolean
***********************************/
@Override
public boolean saveUserRelationShip(String userId, String[] roleIds) {
//删除旧的所有角色权限
userRoleService.remove(new QueryWrapper<UserRole>().eq("user_id", userId));
List<UserRole> list = new ArrayList<>();
for (String id : roleIds) {
UserRole rolePermission = new UserRole();
rolePermission.setRoleId(id);
rolePermission.setUserId(userId);
list.add(rolePermission);
}
return userRoleService.saveBatch(list);
}
/***********************************
* 用途说明:根据userid获取用户信息
* @param userId
* 返回值说明:
* @return java.util.List<com.stu.service.acl.entity.Role>
***********************************/
@Override
public List<Role> selectRoleByUserId(String userId) {
//根据用户id获取角色列表
List<UserRole> userRoleList = userRoleService
.list(new QueryWrapper<UserRole>().eq("user_id", userId).select("role_id"));
//遍历所有菜单,获取每一项,看是否在权限列表,如果在,就标记
List<String> roleIdLists = userRoleList.stream().map(e -> e.getRoleId()).collect(Collectors.toList());
List<Role> roleList = new ArrayList<>();
if (roleIdLists.size() > 0) {
roleList = baseMapper.selectBatchIds(roleIdLists);
}
return roleList;
}
}
package com.stu.service.acl.service;
import com.alibaba.fastjson.JSONObject;
import java.util.List;
import java.util.Map;
public interface IndexService {
/***********************************
* 用途说明:根据用户明获取用户登录信息
* @param userName
* 返回值说明:
* @return java.util.Map<java.lang.String, java.lang.Object>
***********************************/
Map<String, Object> getUserInfo(String userName);
/***********************************
* 用途说明:根据用户动态获取菜单
* @param userName
* 返回值说明:
* @return java.util.List<org.json.JSONObject>
***********************************/
List<JSONObject> getMenu(String userName);
}
package com.stu.service.acl.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.stu.service.acl.entity.Role;
import com.stu.service.acl.entity.User;
import com.stu.service.acl.service.IndexService;
import com.stu.service.acl.service.PermissionService;
import com.stu.service.acl.service.RoleService;
import com.stu.service.acl.service.UserService;
import com.stu.service.base.exception.CustomException;
import com.stu.service.base.result.ResultCodeEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 22:34
******************************/
@Service
public class IndexServiceImpl implements IndexService {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
@Autowired
private RedisTemplate redisTemplate;
/***********************************
* 用途说明:根据用户明获取用户登录信息
* @param userName
* 返回值说明:
* @return java.util.Map<java.lang.String, java.lang.Object>
***********************************/
@Override
public Map<String, Object> getUserInfo(String userName) {
Map<String, Object> result = new HashMap<>();
User user = userService.selectByUserName(userName);
if (user == null) {
throw new CustomException(ResultCodeEnum.FETCH_USERINFO_ERROR);
}
//根据用户id获取角色
List<Role> roleList = roleService.selectRoleByUserId(user.getId());
//转换成角色名称列表
List<String> roleNameList = roleList.stream()
.map(item -> item.getRoleName()).collect(Collectors.toList());
//前端框架必须返回一个角色,否则报错,如果没有角色,返回一个空角色
if (roleNameList.size() == 0) {
roleNameList.add("");
}
List<String> permissionValueList = permissionService.selectPermissionValueListByUserId(user.getId());
redisTemplate.opsForValue().set(userName, permissionValueList);
result.put("name", user.getUsername());
result.put("roles", roleNameList);
result.put("permissionValueList", permissionValueList);
return result;
}
/***********************************
* 用途说明:根据用户动态获取菜单
* @param userName
* 返回值说明:
* @return java.util.List<org.json.JSONObject>
***********************************/
@Override
public List<JSONObject> getMenu(String userName) {
User user = userService.selectByUserName(userName);
if (user == null) {
throw new CustomException(ResultCodeEnum.FETCH_USERINFO_ERROR);
}
//根据用户动态获取菜单
return permissionService.selectPermissionByUserId(user.getId());
}
}
package com.stu.service.acl.controller.admin;
import com.alibaba.fastjson.JSONObject;
import com.stu.service.acl.service.IndexService;
import com.stu.service.base.result.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 21:42
******************************/
@RestController
@RequestMapping("/admin/acl/index")
public class IndexController {
@Autowired
private IndexService indexService;
/***********************************
* 用途说明:退出
* 返回值说明:
* @return com.stu.service.base.result.R
***********************************/
@PostMapping("logout")
public R logout() {
return R.ok();
}
/***********************************
* 用途说明:
* 返回值说明:
* @return com.stu.service.base.result.R
***********************************/
@GetMapping("getUserInfo")
public R getUserInfo() {
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
Map<String, Object> userInfo = indexService.getUserInfo(userName);
return R.ok().data(userInfo);
}
/***********************************
* 用途说明:根据用户明获取动态菜单
* 返回值说明:
* @return com.stu.service.base.result.R
***********************************/
@GetMapping("menu")
public R menu() {
String userName = SecurityContextHolder.getContext().getAuthentication().getName();
List<JSONObject> permissionList = indexService.getMenu(userName);
return R.ok().data("permissionList", permissionList);
}
}
6、Spring Security配置
(1)核心配置类:TokenWebSecurityConfig
Spring Security的核心配置就是继承WebSecurityConfigurerAdapter并注解@EnableWebSecurity的配置。
这个配置指明了用户名密码的处理方式、请求路径的开合、登录登出控制等和安全相关的配置。
package com.stu.security.config;
import com.stu.security.filter.TokenAuthenticationFilter;
import com.stu.security.filter.TokenLoginFilter;
import com.stu.security.security.DefaultPasswordEncoder;
import com.stu.security.security.TokenLogoutHandler;
import com.stu.security.security.TokenManager;
import com.stu.security.security.UnauthorizedEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:30
******************************/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private DefaultPasswordEncoder defaultPasswordEncoder;
@Autowired
private TokenManager tokenManager;
@Autowired
private RedisTemplate redisTemplate;
/***********************************
* 用途说明:密码处理
* @param auth
* 返回值说明:
***********************************/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
/***********************************
* 用途说明:配置设置
* @param http
* 返回值说明:
***********************************/
@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();
}
/***********************************
* 用途说明:配置哪些请求不拦截
* @param web
* 返回值说明:
***********************************/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(
"/api/**",
"/swagger-resources/**",
"/webjars/**",
"/v2/**",
"/swagger-ui.html/**"
);
}
}
package com.stu.security.entity;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:38
******************************/
@Data
@ApiModel(value = "User对象", description = "用户表")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "会员id")
private String id;
@ApiModelProperty(value = "用户名称")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "用户头像")
private String salt;
@ApiModelProperty(value = "用户签名")
private String token;
}
package com.stu.security.entity;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:38
******************************/
@Data
@Slf4j
public class SecurityUser implements UserDetails {
//当前登录用户
private transient User currentUserInfo;
//当前登录用户权限
private List<String> permissionValueList;
public SecurityUser() {
}
public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}
/***********************************
* 用途说明:获取当前用户的所有权限
* 返回值说明:
* @return java.util.Collection<? extends org.springframework.security.core.GrantedAuthority>
***********************************/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> 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;
}
}
package com.stu.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.stu.security.entity.SecurityUser;
import com.stu.security.entity.User;
import com.stu.security.security.TokenManager;
import com.stu.service.base.exception.CustomException;
import com.stu.service.base.result.R;
import com.stu.service.base.result.ResultCodeEnum;
import com.stu.service.base.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
/******************************
* 用途说明:登录过滤器,对用户名称和密码进行校验
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:39
******************************/
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;
//设置访问方式,可以post以为的访问
this.setPostOnly(false);
this.setRequiresAuthenticationRequestMatcher(
new AntPathRequestMatcher("/admin/acl/login", "POST"));
}
/***********************************
* 用途说明:验证账号密码是否正确
* @param request
* @param response
* 返回值说明:
* @return org.springframework.security.core.Authentication
***********************************/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), new ArrayList<>()));
} catch (Exception e) {
e.printStackTrace();
throw new CustomException(ResultCodeEnum.LOGIN_MOBILE_ERROR);
}
}
/***********************************
* 用途说明:登录成功
* @param request
* @param response
* @param chain
* @param authResult
* 返回值说明:
***********************************/
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityUser user = (SecurityUser) authResult.getPrincipal();
String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(), user.getPermissionValueList());
ResponseUtil.out(response, R.ok().data("token", token));
}
/***********************************
* 用途说明:登录失败
* @param request
* @param response
* @param e
* 返回值说明:
***********************************/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
package com.stu.security.filter;
import com.stu.security.security.TokenManager;
import com.stu.service.base.result.R;
import com.stu.service.base.utils.ResponseUtil;
import io.netty.util.internal.StringUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:39
******************************/
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
private AuthenticationManager authenticationManager;
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthenticationFilter(AuthenticationManager authenticationManager,
TokenManager tokenManager,
RedisTemplate redisTemplate) {
super(authenticationManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request.getRequestURI().indexOf("admin") == -1) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = null;
try {
usernamePasswordAuthenticationToken = getAuthentication(request);
} catch (Exception e) {
ResponseUtil.out(response, R.error());
}
if (usernamePasswordAuthenticationToken != null) {
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
} else {
ResponseUtil.out(response, R.error());
}
chain.doFilter(request, response);
}
/***********************************
* 用途说明:从request获取token,根据token获取权限列表
* 返回值说明:
* @return org.springframework.security.authentication.UsernamePasswordAuthenticationToken
***********************************/
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader("token");
if (token != null && !"".equals(token.trim())) {
String userName = tokenManager.getUserFromToken(token);
List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
Collection<GrantedAuthority> authorities = new ArrayList<>();
for (String permissionValue : permissionValueList) {
if (StringUtils.isEmpty(permissionValue)) {
continue;
}
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permissionValue);
authorities.add(simpleGrantedAuthority);
}
return new UsernamePasswordAuthenticationToken(userName, token, authorities);
}
return null;
}
}
package com.stu.security.security;
import com.stu.service.base.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:39
******************************/
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
/**
* 加密密码
*
* @param rawPassword
*/
@Override
public String encode(CharSequence rawPassword) {
return MD5.encrypt(rawPassword.toString());
}
/**
* 判断密码是否正确
*
* @param rawPassword the raw password to encode and match
* @param encodedPassword the encoded password from storage to compare with
* @return true if the raw password, after encoding, matches the encoded password from
* storage
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(rawPassword.toString()));
}
}
package com.stu.security.security;
import com.stu.service.base.result.R;
import com.stu.service.base.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:39
******************************/
public class TokenLogoutHandler implements LogoutHandler {
private AuthenticationManager authenticationManager;
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());
}
}
}
package com.stu.security.security;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:40
******************************/
@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无需删除,客户端扔掉即可。
}
}
package com.stu.security.security;
import com.stu.service.base.result.R;
import com.stu.service.base.utils.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/******************************
* 用途说明:
* 作者姓名: Administrator
* 创建时间: 2022-09-01 9:40
******************************/
public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(httpServletResponse, R.error());
}
}
目录截图

作者:明
出处:https://www.cnblogs.com/konglxblog//
版权:本文版权归作者和博客园共有
转载:欢迎转载,文章中请给出原文连接,此文章仅为个人知识学习分享,否则必究法律责任
2236

被折叠的 条评论
为什么被折叠?



