超详细Vue+Spring Boot整合Shiro前后端分离架构-Shiro权限分配设计（四）
前端设计使用npm创建web工程通过以下命令创建一个web工程vue init webpack shiroui添加axios，用于请求后端服务cnpm install axios --save在工程main.js中引入axiosVue.config.productionTip = false axios.defaults.baseURL = 'localhost:8080' Vue.prototype.$axios = axios添加elementuicnpm i element-ui -S在工程main.js中引入elementuiimport ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' ··· Vue.use(ElementUI)main.js预览实现登录功能前端界面 <el-form ref="form" :model="user" label-width="80px"> <el-form-item label="用户编号"> <el-input v-model="user.userCode" placeholder="请输入用户编号"></el-input> </el-form-item> <el-form-item label="密 码"> <el-input v-model="user.password" placeholder="请输入密码" show-password></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmit">登录</el-button> <el-button>取消</el-button> </el-form-item> </el-form> <el-breadcrumb separator-class="el-icon-arrow-right"> <el-breadcrumb-item :to="{ path: '/' }">用户管理</el-breadcrumb-item> <el-breadcrumb-item :to="{ path: '/role' }">角色管理</el-breadcrumb-item> <el-breadcrumb-item :to="{ path: '/permission' }">权限管理</el-breadcrumb-item> <el-breadcrumb-item>角色分配</el-breadcrumb-item> <el-breadcrumb-item>权限分配</el-breadcrumb-item> </el-breadcrumb> <router-view/> 登录后端代码/** * @author laughing * @date 2020/10/11 * @site https://www.lisen.cc */ @RestController @RequestMapping("/shiro") public class ShiroController { private final Logger logger = LoggerFactory.getLogger(ShiroController.class); @Resource ShiroService shiroService; @RequestMapping("/findUserByUserCode") public ShiroUser findUserByUserCode() { String userCode = "lisen"; ShiroUser shiroUser = shiroService.findUserByCode(userCode); return shiroUser; } @RequestMapping("/login") public String login(@RequestParam("userCode") String userCode, @RequestParam("password") String password) { // password = new SimpleHash(AuthenConst.HASH_ALGORITHM_NAME,password,AuthenConst.SALT,AuthenConst.HASH_INTERACTIONS).toHex(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userCode, password); Subject subject = SecurityUtils.getSubject(); try { //进行验证，这里可以捕获异常，然后返回对应信息 subject.login(usernamePasswordToken); } catch (UnknownAccountException e) { logger.error("用户名不存在！", e); return "用户名不存在！"; } catch (AuthenticationException e) { logger.error("账号或密码错误！", e); return "账号或密码错误！"; } catch (AuthorizationException e) { logger.error("没有权限！", e); return "没有权限"; } return "login success"; } @RequestMapping("/index") public String index(String userCode, String password) { return "index"; } @RequiresPermissions("permission") @RequestMapping("/permission") public String permission() { return "permission"; } @RequiresPermissions("dept:add") @RequestMapping("/deptadd") public String deptAdd() { return "dept:add"; } @RequestMapping("/nopermission") public String noPermission() { return "noPermission"; } }前端权限调用 <el-form> <el-form-item> <el-button @click="findAll">加载用户信息</el-button> <el-button @click="dialogFormVisible=true">增加用户</el-button> <el-button @click="dialogFormRoleVisible=true">分配岗位</el-button> </el-form-item> </el-form> <el-table :data="tableData" style="width: 100%"> <el-table-column label="ID" width="180"> {{ scope.row.id }} </el-table-column> <el-table-column label="用户编号" width="180"> {{ scope.row.userCode }} </el-table-column> <el-table-column label="用户姓名" width="180"> {{ scope.row.userName }} </el-table-column> <el-table-column label="操作"> <el-button size="mini" @click="handleEdit(scope.$index, scope.row)">编辑</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)">删除</el-button> </el-table-column> </el-table> <el-dialog title="增加用户" :visible.sync="dialogFormVisible"> <el-form :model="form" :rules="rules" ref="form"> <el-form-item label="用户编号" prop="userCode"> <el-input v-model="form.userCode" autocomplete="off" maxlength="30" show-word-limit></el-input> </el-form-item> <el-form-item label="用户名称"> <el-input v-model="form.userName" autocomplete="off" maxlength="30" show-word-limit></el-input> </el-form-item> <el-form-item label="密 码"> <el-input v-model="form.password" autocomplete="off" maxlength="30" show-word-limit></el-input> </el-form-item> </el-form> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="submitForm">确 定</el-button> </el-dialog> <el-dialog title="分配角色" :visible.sync="dialogFormRoleVisible"> <el-form :model="roleForm" :rules="roleRules" ref="roleForm"> <el-autocomplete popper-class="my-autocomplete" v-model="state" :fetch-suggestions="querySearch" placeholder="请输入内容" @select="handleSelect"> {{ item.value }} {{ item.address }} </el-autocomplete> </el-form> <el-button @click="dialogFormRoleVisible = false">取 消</el-button> <el-button type="primary" @click="submitFormRole">确 定</el-button> </el-dialog> 后端权限调用/** * @author laughing * @date 2020/10/13 * @site https://lisen.cc */ @RestController @RequestMapping("user") public class UserController { @Resource ShiroUserService shiroUserService; /** * 查找所有用户 * @return */ @RequiresPermissions("user:find") @RequestMapping("/findAll") public List<ShiroUser> findAll() { return shiroUserService.findAll(); } /** * 查找所有用户 * @return */ @RequiresPermissions("user:find") @RequestMapping("/insert") public int save(ShiroUser shiroUser) { return shiroUserService.insert(shiroUser); } }
超详细Vue+Spring Boot整合Shiro前后端分离架构-Shiro后端设计（三）
Shiro实战shiro实战内容包括三个部分：（1）shiro后台表结构，用于存储shiro对应的用户、角色、权限及关联关系。（2）后端代码，及基于shiro配置用户、角色、权限及对应关系以及登录、认证。（3）前端代码，维护shiro信息及登录、认证。这篇博文我们介绍第二部分，即后端设计后端代码后端数据库访问用的mybatis及lombok插件。添加依赖 <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${spring.shiro.version}</version> </dependency> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>${shiro.redis.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies>配置文件主要配置数据库连接、mybatis及shiro信息。spring: datasource: url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis: type-aliases-package: cc.lisen.shiro.entity mapper-locations: classpath:mapper/*.xml shiro: user: loginUrl: /shiro/login/** unauthorizedUrl: /shiro/logout indexUrl: /shiro/index captchaEnabled: true captchaType: math redis: host: localhost port: 6379 cookie: domain: path: / httpOnly: false maxAge: 72 #Hours，利用 cookie 免登录。 secure: false session: expireTime: 72 #Hours dbSyncPeriod: 1 validationInterval: 10 maxSession: -1 kickoutAfter: false server: port: 8888 #配置跨域 cors: allowedOrigin: - http://localhost:8080 allowCredentials: true allowHeaders: - \* allowMethods: - GET - POST - PUT - DELETE - OPTIONS maxAge: 7200 path: /**mybatis代码配置完成后，我们先生成mybatis代码，完成mybatis的操作。我使用Free MyBatis plugin插件生成一些代码。mybatis实体ShiroUser.java@Data public class ShiroUser implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String userCode; private String userName; private String password; private Set<ShiroRole> shiroRoleSet; }ShiroRole.java@Data public class ShiroRole implements Serializable { private static final long serialVersionUID = 1L; private Long id; private String roleCode; private String roleName; private Set<ShiroPermission> shiroPermissionSet; }ShiroPermission.java@Data public class ShiroPermission implements Serializable { private Long id; private String permissionCode; private String permissionName; private static final long serialVersionUID = 1L; }ShiroUserRole.java@Data public class ShiroUserRole implements Serializable { private Long id; private Long userId; private Long roleId; private static final long serialVersionUID = 1L; }ShiroRolePermission.java@Data public class ShiroRolePermission implements Serializable { private Long id; private Long roleId; private Long permissionId; private static final long serialVersionUID = 1L; }数据访问接口ShiroUserMapper.java@Mapper public interface ShiroUserMapper { int deleteByPrimaryKey(Long id); int insert(ShiroUser record); int insertSelective(ShiroUser record); /** * 根据主键获取用户信息 * @param id Id * @return 用户 */ ShiroUser selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ShiroUser record); int updateByPrimaryKey(ShiroUser record); /** * 根据用户编号获取 * @param userCode 用户编号 * @return 用户 */ ShiroUser findUserByUserCode(String userCode); }ShiroRoleMapper.java@Mapper public interface ShiroRoleMapper { int deleteByPrimaryKey(Long id); int insert(ShiroRole record); int insertSelective(ShiroRole record); ShiroRole selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ShiroRole record); int updateByPrimaryKey(ShiroRole record); /** * 根据用户编号获取其角色列表 * @param userCode 用户编号 * @return 权限列表 */ Set<ShiroRole> findByUserCode(String userCode); }ShiroPermissionMapper.java@Mapper public interface ShiroPermissionMapper { int deleteByPrimaryKey(Long id); int insert(ShiroPermission record); int insertSelective(ShiroPermission record); ShiroPermission selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ShiroPermission record); int updateByPrimaryKey(ShiroPermission record); /** * 获取角色对应权限 * @param roleId 角色Id * @return 权限集合 */ Set<ShiroPermission> findByRoleId(long roleId); }ShiroUserRoleMapper.java@Mapper public interface ShiroUserRoleMapper { int deleteByPrimaryKey(Long id); int insert(ShiroUserRole record); int insertSelective(ShiroUserRole record); ShiroUserRole selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ShiroUserRole record); int updateByPrimaryKey(ShiroUserRole record); }ShiroRolePermissionMapper.java@Mapper public interface ShiroRolePermissionMapper { int deleteByPrimaryKey(Long id); int insert(ShiroRolePermission record); int insertSelective(ShiroRolePermission record); ShiroRolePermission selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(ShiroRolePermission record); int updateByPrimaryKey(ShiroRolePermission record); }xml文件ShiroUserMapper.xml<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cc.lisen.shiro.mapper.ShiroUserMapper"> <resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroUser"> <id column="id" jdbcType="BIGINT" property="id"/> <result column="userCode" jdbcType="VARCHAR" property="userCode"/> <result column="userName" jdbcType="VARCHAR" property="userName"/> <result column="password" jdbcType="VARCHAR" property="password"/> </resultMap> <sql id="Base_Column_List"> id, userCode, userName, `password` </sql> select <include refid="Base_Column_List"/> from Shiro_User where id = #{id,jdbcType=BIGINT} select <include refid="Base_Column_List"/> from Shiro_User where userCode = #{userCode} <delete id="deleteByPrimaryKey" parameterType="java.lang.Long"> delete from Shiro_User where id = #{id,jdbcType=BIGINT} </delete> <insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroUser" useGeneratedKeys="true"> insert into Shiro_User (userCode, userName, `password`) values (#{usercode,jdbcType=VARCHAR}, #{username,jdbcType=VARCHAR}, #{password,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroUser" useGeneratedKeys="true"> insert into Shiro_User <trim prefix="(" suffix=")" suffixOverrides=","> <if test="userCode != null"> userCode, </if> <if test="userName != null"> userName, </if> <if test="password != null"> `password`, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="userCode != null"> #{usercode,jdbcType=VARCHAR}, </if> <if test="userName != null"> #{username,jdbcType=VARCHAR}, </if> <if test="password != null"> #{password,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroUser"> update Shiro_User <set> <if test="userCode != null"> userCode = #{usercode,jdbcType=VARCHAR}, </if> <if test="userName != null"> userName = #{username,jdbcType=VARCHAR}, </if> <if test="password != null"> `password` = #{password,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=BIGINT} </update> <update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroUser"> update Shiro_User set userCode = #{usercode,jdbcType=VARCHAR}, userName = #{username,jdbcType=VARCHAR}, `password` = #{password,jdbcType=VARCHAR} where id = #{id,jdbcType=BIGINT} </update> </mapper>ShiroRoleMapper.xml<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cc.lisen.shiro.mapper.ShiroRoleMapper"> <resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroRole"> <id column="id" jdbcType="BIGINT" property="id" /> <result column="roleCode" jdbcType="VARCHAR" property="roleCode" /> <result column="roleName" jdbcType="VARCHAR" property="roleName" /> </resultMap> <sql id="Base_Column_List"> id, roleCode, roleName </sql> select <include refid="Base_Column_List" /> from Shiro_Role where id = #{id,jdbcType=BIGINT} select Shiro_Role.* from Shiro_Role inner join Shiro_User_Role SUR on Shiro_Role.id = SUR.roleId inner join Shiro_User SU on SUR.userId = SU.id where su.userCode = #{userCode} <delete id="deleteByPrimaryKey" parameterType="java.lang.Long"> delete from Shiro_Role where id = #{id,jdbcType=BIGINT} </delete> <insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroRole" useGeneratedKeys="true"> insert into Shiro_Role (roleCode, roleName) values (#{rolecode,jdbcType=VARCHAR}, #{rolename,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroRole" useGeneratedKeys="true"> insert into Shiro_Role <trim prefix="(" suffix=")" suffixOverrides=","> <if test="roleCode != null"> roleCode, </if> <if test="roleName != null"> roleName, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="roleCode != null"> #{rolecode,jdbcType=VARCHAR}, </if> <if test="roleName != null"> #{rolename,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroRole"> update Shiro_Role <set> <if test="roleCode != null"> roleCode = #{rolecode,jdbcType=VARCHAR}, </if> <if test="roleName != null"> roleName = #{rolename,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=BIGINT} </update> <update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroRole"> update Shiro_Role set roleCode = #{rolecode,jdbcType=VARCHAR}, roleName = #{rolename,jdbcType=VARCHAR} where id = #{id,jdbcType=BIGINT} </update> </mapper>ShiroPermissionMapper.xml<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cc.lisen.shiro.mapper.ShiroPermissionMapper"> <resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroPermission"> <id column="id" jdbcType="BIGINT" property="id"/> <result column="permissionCode" jdbcType="VARCHAR" property="permissionCode"/> <result column="permissionName" jdbcType="VARCHAR" property="permissionName"/> </resultMap> <sql id="Base_Column_List"> id, permissionCode, permissionName </sql> select <include refid="Base_Column_List"/> from Shiro_Permission where id = #{id,jdbcType=BIGINT} select Shiro_Permission.* from Shiro_Permission inner join Shiro_Role_Permission SRP on Shiro_Permission.id = SRP.permissionId where SRP.roleId = #{roleId} <delete id="deleteByPrimaryKey" parameterType="java.lang.Long"> delete from Shiro_Permission where id = #{id,jdbcType=BIGINT} </delete> <insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroPermission" useGeneratedKeys="true"> insert into Shiro_Permission (permissionCode, permissionName) values (#{permissioncode,jdbcType=VARCHAR}, #{permissionname,jdbcType=VARCHAR}) </insert> <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroPermission" useGeneratedKeys="true"> insert into Shiro_Permission <trim prefix="(" suffix=")" suffixOverrides=","> <if test="permissionCode != null"> permissionCode, </if> <if test="permissionName != null"> permissionName, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="permissionCode != null"> #{permissioncode,jdbcType=VARCHAR}, </if> <if test="permissionName != null"> #{permissionname,jdbcType=VARCHAR}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroPermission"> update Shiro_Permission <set> <if test="permissionCode != null"> permissionCode = #{permissioncode,jdbcType=VARCHAR}, </if> <if test="permissionName != null"> permissionName = #{permissionname,jdbcType=VARCHAR}, </if> </set> where id = #{id,jdbcType=BIGINT} </update> <update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroPermission"> update Shiro_Permission set permissionCode = #{permissioncode,jdbcType=VARCHAR}, permissionName = #{permissionname,jdbcType=VARCHAR} where id = #{id,jdbcType=BIGINT} </update> </mapper>ShiroUserRoleMapper.xml<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cc.lisen.shiro.mapper.ShiroUserRoleMapper"> <resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroUserRole"> <id column="id" jdbcType="BIGINT" property="id" /> <result column="userId" jdbcType="BIGINT" property="userId" /> <result column="roleId" jdbcType="BIGINT" property="roleId" /> </resultMap> <sql id="Base_Column_List"> id, userId, roleId </sql> select <include refid="Base_Column_List" /> from Shiro_User_Role where id = #{id,jdbcType=BIGINT} <delete id="deleteByPrimaryKey" parameterType="java.lang.Long"> delete from Shiro_User_Role where id = #{id,jdbcType=BIGINT} </delete> <insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroUserRole" useGeneratedKeys="true"> insert into Shiro_User_Role (userId, roleId) values (#{userid,jdbcType=BIGINT}, #{roleid,jdbcType=BIGINT}) </insert> <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroUserRole" useGeneratedKeys="true"> insert into Shiro_User_Role <trim prefix="(" suffix=")" suffixOverrides=","> <if test="userId != null"> userId, </if> <if test="roleId != null"> roleId, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="userid != null"> #{userid,jdbcType=BIGINT}, </if> <if test="roleId != null"> #{roleid,jdbcType=BIGINT}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroUserRole"> update Shiro_User_Role <set> <if test="userId != null"> userId = #{userid,jdbcType=BIGINT}, </if> <if test="roleId != null"> roleId = #{roleid,jdbcType=BIGINT}, </if> </set> where id = #{id,jdbcType=BIGINT} </update> <update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroUserRole"> update Shiro_User_Role set userId = #{userid,jdbcType=BIGINT}, roleId = #{roleid,jdbcType=BIGINT} where id = #{id,jdbcType=BIGINT} </update> </mapper>ShiroRolePermissionMapper.xml<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="cc.lisen.shiro.mapper.ShiroRolePermissionMapper"> <resultMap id="BaseResultMap" type="cc.lisen.shiro.entity.ShiroRolePermission"> <id column="id" jdbcType="BIGINT" property="id" /> <result column="roleId" jdbcType="BIGINT" property="roleId" /> <result column="permissionId" jdbcType="BIGINT" property="permissionId" /> </resultMap> <sql id="Base_Column_List"> id, roleId, permissionId </sql> select <include refid="Base_Column_List" /> from Shiro_Role_Permission where id = #{id,jdbcType=BIGINT} <delete id="deleteByPrimaryKey" parameterType="java.lang.Long"> delete from Shiro_Role_Permission where id = #{id,jdbcType=BIGINT} </delete> <insert id="insert" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroRolePermission" useGeneratedKeys="true"> insert into Shiro_Role_Permission (roleId, permissionId) values (#{roleid,jdbcType=BIGINT}, #{permissionid,jdbcType=BIGINT}) </insert> <insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="cc.lisen.shiro.entity.ShiroRolePermission" useGeneratedKeys="true"> insert into Shiro_Role_Permission <trim prefix="(" suffix=")" suffixOverrides=","> <if test="roleId != null"> roleId, </if> <if test="permissionId != null"> permissionId, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="roleId != null"> #{roleid,jdbcType=BIGINT}, </if> <if test="permissionId != null"> #{permissionid,jdbcType=BIGINT}, </if> </trim> </insert> <update id="updateByPrimaryKeySelective" parameterType="cc.lisen.shiro.entity.ShiroRolePermission"> update Shiro_Role_Permission <set> <if test="roleId != null"> roleId = #{roleid,jdbcType=BIGINT}, </if> <if test="permissionId != null"> permissionId = #{permissionid,jdbcType=BIGINT}, </if> </set> where id = #{id,jdbcType=BIGINT} </update> <update id="updateByPrimaryKey" parameterType="cc.lisen.shiro.entity.ShiroRolePermission"> update Shiro_Role_Permission set roleId = #{roleid,jdbcType=BIGINT}, permissionId = #{permissionid,jdbcType=BIGINT} where id = #{id,jdbcType=BIGINT} </update> </mapper>服务层服务层这里忽略了接口，有需要的可以查看源代码ShiroUserServiceImpl.java@Service public class ShiroUserServiceImpl implements ShiroUserService { @Resource ShiroUserMapper shiroUserMapper; @Resource ShiroRoleService shiroRoleService; /** * 根据用户编号获取 * * @param userCode 用户编号 * @return 用户 */ @Override public ShiroUser findUserByUserCode(String userCode) { ShiroUser shiroUser = shiroUserMapper.findUserByUserCode(userCode); Set<ShiroRole> shiroRoleSet = shiroRoleService.findByUserCode(userCode); shiroUser.setShiroRoleSet(shiroRoleSet); return shiroUser; } }ShiroRoleServiceImpl.java@Service public class ShiroRoleServiceImpl implements ShiroRoleService { @Resource ShiroRoleMapper shiroRoleMapper; @Resource ShiroPermissionService shiroPermissionService; /** * 根据用户编号获取其角色列表 * * @param userCode 用户编号 * @return 权限列表 */ @Override public Set<ShiroRole> findByUserCode(String userCode) { Set<ShiroRole> shiroRoleSet = shiroRoleMapper.findByUserCode(userCode); for (ShiroRole shiroRole : shiroRoleSet) { Set<ShiroPermission> shiroPermissionSet = shiroPermissionService.findByRoleId(shiroRole.getId()); shiroRole.setShiroPermissionSet(shiroPermissionSet); } return shiroRoleSet; } } ShiroPermissionServiceImpl.java@Service public class ShiroPermissionServiceImpl implements ShiroPermissionService { @Resource ShiroPermissionMapper shiroPermissionMapper; /** * 获取角色对应权限 * * @param roleId 角色Id * @return 权限集合 */ @Override public Set<ShiroPermission> findByRoleId(long roleId) { return shiroPermissionMapper.findByRoleId(roleId); } }ShiroServiceImpl.java@Service public class ShiroServiceImpl implements ShiroService { @Resource ShiroUserService shiroUserService; /** * 根据用户编号获取 * * @param userCode 用户编号 * @return 用户 */ @Override public ShiroUser findUserByCode(String userCode) { return shiroUserService.findUserByUserCode(userCode); } }shiro配置常量配置AuthenConst常量类，用于配置加密方式、盐、加密次数等信息。/** * @author laughing * @date 2020/10/12 * @site https://lisen.cc */ public class AuthenConst { public static final String SALT = "LiSen"; public static final String HASH_ALGORITHM_NAME = "MD5"; public static final int HASH_INTERACTIONS = 1024; public static final String OPTION_REQUEST_NAME = "OPTIONS"; }property映射/** * @author laughing * @date 2020/10/11 * @site https://lisen.cc */ @ConfigurationProperties(prefix = "shiro") @Configuration @Data public class ShiroProperty { private User user; private Redis redis; } @Data class User{ private String loginUrl; private String unauthorizedUrl; private String indexUrl; private boolean captchaEnabled; private String captchaType; } @Data class Redis{ private String host; private long port; }自定义realm/** * @author laughing * @date 2020/10/11 * @site https://www.lisen.org */ public class MyAuthenRealm extends AuthorizingRealm { @Resource ShiroService shiroService; /** * 认证 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; String userCode = token.getUsername(); ShiroUser shiroUser = shiroService.findUserByCode(userCode); if (shiroUser == null) { throw new AuthenticationException("账户不存在"); } //自定义盐值 // ByteSource salt = ByteSource.Util.bytes(AuthenConst.SALT); // String password = new SimpleHash(AuthenConst.HASH_ALGORITHM_NAME, new String(token.getPassword()), AuthenConst.SALT, AuthenConst.HASH_INTERACTIONS).toHex(); if (!new String(token.getPassword()).equals(shiroUser.getPassword())) { throw new IncorrectCredentialsException("账户密码不正确"); } // Subject subject = SecurityUtils.getSubject(); // ShiroUserVO shiroUserVO = new ShiroUserVO(); // BeanUtils.copyProperties(shiroUserVO,shiroUser); // shiroUserVO.setSessionId(subject.getSession().getId().toString()); return new SimpleAuthenticationInfo(userCode, shiroUser.getPassword(), getName()); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { if (StringUtils.isEmpty(principalCollection)) { return null; } String userCode = principalCollection.getPrimaryPrincipal().toString(); ShiroUser shiroUser = shiroService.findUserByCode(userCode); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (ShiroRole shiroRole : shiroUser.getShiroRoleSet()) { simpleAuthorizationInfo.addRole(shiroRole.getRoleCode()); for (ShiroPermission shiroPermission : shiroRole.getShiroPermissionSet()) { simpleAuthorizationInfo.addStringPermission(shiroPermission.getPermissionCode()); } } return simpleAuthorizationInfo; } }禁止未登录跳转因为我们是前后端分离项目，如果未登陆跳转的话，前端无法捕捉重定向后的消息，所以我们需要配置禁止未登录跳转。/** * 对于跨域的POST请求，浏览器发起POST请求前都会发送一个OPTIONS请求已确定服务器是否可用， * OPTIONS请求通过后继续执行POST请求，而shiro自带的权限验证是无法处理OPTIONS请求的， * 所以这里需要重写isAccessAllowed方法 * @author laughing * @date 2020/10/12 * @site https://lisen.cc */ public class ShiroFormAuthenticationFilter extends FormAuthenticationFilter { private static final Logger log = LoggerFactory.getLogger(ShiroFormAuthenticationFilter.class); /** * 重写是否允许访问 * @param request * @param response * @param mappedValue * @return */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { HttpServletRequest httpServletRequest = WebUtils.toHttp(request); if(AuthenConst.OPTION_REQUEST_NAME.equals(httpServletRequest.getMethod())){ return true; } return super.isAccessAllowed(request, response, mappedValue); } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws JSONException { PrintWriter out = null; HttpServletResponse res = (HttpServletResponse) response; try { res.setCharacterEncoding("UTF-8"); res.setContentType("application/json"); out = response.getWriter(); out.println("未授权"); } catch (Exception e) { } finally { if (null != out) { out.flush(); out.close(); } } return false; } } shiro配置/** * @author laughing * @date 2020/10/11 * @site https://lisen.cc */ @Configuration public class ShiroConfig { /** * 自定义验证 * @return */ @Bean MyAuthenRealm myAuthenRealm() { MyAuthenRealm myAuthenRealm = new MyAuthenRealm(); // myAuthenRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myAuthenRealm; } @Resource ShiroProperty shiroProperty; /** * 权限管理，配置主要是Realm的管理认证 * * @return */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(myAuthenRealm()); // 自定义session管理 使用redis defaultWebSecurityManager.setSessionManager(sessionManager()); return defaultWebSecurityManager; } /** * Filter工厂，设置对应的过滤条件和跳转条件 */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager()); Map<String, String> filterChainDefinitionMap = new HashMap<>(); //登出 filterChainDefinitionMap.put(shiroProperty.getUser().getUnauthorizedUrl(), "logout"); //登录 shiroFilterFactoryBean.setLoginUrl(shiroProperty.getUser().getLoginUrl()); //首页 shiroFilterFactoryBean.setSuccessUrl(shiroProperty.getUser().getIndexUrl()); filterChainDefinitionMap.put("/", "anon"); filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put(shiroProperty.getUser().getLoginUrl(), "anon"); filterChainDefinitionMap.put(shiroProperty.getUser().getUnauthorizedUrl(), "anon"); filterChainDefinitionMap.put("/error", "anon"); filterChainDefinitionMap.put("/**", "authc"); LinkedHashMap<String, Filter> filtsMap = new LinkedHashMap<>(); shiroFilterFactoryBean.setFilters(filtsMap); // 这里使用自定义的filter，禁止未登陆跳转 filtsMap.put("authc", new ShiroFormAuthenticationFilter()); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 加入注解的使用，不加入这个注解不生效 * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } // @Bean // public HashedCredentialsMatcher hashedCredentialsMatcher() { // HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); // //指定加密方式 // credentialsMatcher.setHashAlgorithmName(AuthenConst.HASH_ALGORITHM_NAME); // //加密次数 // credentialsMatcher.setHashIterations(AuthenConst.HASH_INTERACTIONS); // //此处的设置，true加密用的hex编码，false用的base64编码 // credentialsMatcher.setStoredCredentialsHexEncoded(true); // return credentialsMatcher; // } @Bean public SessionManager sessionManager(){ MySessionManager mySessionManager = new MySessionManager(); // 取消登陆跳转URL后面的jsessionid参数 mySessionManager.setSessionIdUrlRewritingEnabled(false); mySessionManager.setSessionDAO(redisSessionDAO()); // 不过期 mySessionManager.setGlobalSessionTimeout(-1); return mySessionManager; } @Bean public RedisManager redisManager(){ RedisManager redisManager = new RedisManager(); redisManager.setHost(shiroProperty.getRedis().getHost()+":"+shiroProperty.getRedis().getPort()); redisManager.setDatabase(0); return redisManager; } @Bean public RedisSessionDAO redisSessionDAO(){ RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } }全局异常@RestControllerAdvice public class MyAuthorizationException { @ExceptionHandler(UnauthorizedException.class) public String authorization(UnauthorizedException myAuthorizationException) { return "没有"+myAuthorizationException.toString()+"权限"; } }测试类/** * @author laughing * @date 2020/10/11 * @site https://www.lisen.org */ @RestController @RequestMapping("/shiro") public class ShiroController { private final Logger logger = LoggerFactory.getLogger(ShiroController.class); @Resource ShiroService shiroService; @RequestMapping("/findUserByUserCode") public ShiroUser findUserByUserCode() { String userCode = "lisen"; ShiroUser shiroUser = shiroService.findUserByCode(userCode); return shiroUser; } @RequestMapping("/login") public String login(@RequestParam("userCode") String userCode, @RequestParam("password") String password) { // password = new SimpleHash(AuthenConst.HASH_ALGORITHM_NAME,password,AuthenConst.SALT,AuthenConst.HASH_INTERACTIONS).toHex(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userCode, password); Subject subject = SecurityUtils.getSubject(); try { //进行验证，这里可以捕获异常，然后返回对应信息 subject.login(usernamePasswordToken); } catch (UnknownAccountException e) { logger.error("用户名不存在！", e); return "用户名不存在！"; } catch (AuthenticationException e) { logger.error("账号或密码错误！", e); return "账号或密码错误！"; } catch (AuthorizationException e) { logger.error("没有权限！", e); return "没有权限"; } return "login success"; } @RequestMapping("/index") public String index(String userCode, String password) { return "index"; } @RequiresPermissions("permission") @RequestMapping("/permission") public String permission() { return "permission"; } @RequiresPermissions("dept:add") @RequestMapping("/deptadd") public String deptAdd() { return "dept:add"; } @RequestMapping("/nopermission") public String noPermission() { return "noPermission"; } }表数据因为我们这里还没设计授权的界面，所以临时在数据库插入了测试数据Shiro_UserINSERT INTO `shiro`.`Shiro_User`(`id`, `userCode`, `userName`, `password`) VALUES (1, 'lisen', '李森', 'f5e617c6615d53ae33b9d80a2087e264');Shiro_RoleINSERT INTO `shiro`.`Shiro_Role`(`id`, `roleCode`, `roleName`) VALUES (1, 'admin', '管理员');Shiro_PermissionINSERT INTO `shiro`.`Shiro_Permission`(`id`, `permissionCode`, `permissionName`) VALUES (1, 'dept:add', '部门-增加');Shiro_User_RoleINSERT INTO `shiro`.`Shiro_User_Role`(`id`, `userId`, `roleId`) VALUES (1, 1, 1);Shiro_Role_PermissionINSERT INTO `shiro`.`Shiro_Role_Permission`(`id`, `roleId`, `permissionId`) VALUES (1, 1, 1);
超详细Vue+Spring Boot整合Shiro前后端分离架构-Shiro表结构设计（二）
在超详细Vue+Spring Boot整合Shiro前后端分离架构-Shiro介绍（一）一文中，我们介绍了Shiro的基本概念，本章开始，我们进入Shiro的实现环节。Shiro实战shiro实战内容包括三个部分：（1）shiro后台表结构，用于存储shiro对应的用户、角色、权限及关联关系。（2）后端代码，及基于shiro配置用户、角色、权限及对应关系以及登录、认证。（3）前端代码，维护shiro信息及登录、认证。这篇博文，我们讲解第一部分，及Shiro的表结构设计。表结构设计我这里设计了5张表，分别为Shiro_User，Shiro_Role，Shiro_Permission，Shiro_User_Role，Shiro_Role_Permission。Shiro_UserShiro_User用于存储用户信息，主要涉及字段为用户Id（id）、用户名称（username）及用户密码（password）。shiro由提供的密码加密方式，所以，我们数据库存储的密文为调用shiro加密后的用户密码。CREATE TABLE `Shiro_User` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `userCode` varchar(255) NOT NULL, `userName` varchar(255) NOT NULL, `password` varchar(255) NOT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `id` (`id`) USING BTREE, UNIQUE KEY `code` (`userCode`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Shiro_RoleShiro_Role用于存储角色，如系统管理员、业务人员等信息。一个用户拥有多个角色，一个角色也可以属于多个用户。CREATE TABLE `Shiro_Role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `roleCode` varchar(255) NOT NULL, `roleName` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `id` (`id`) USING BTREE, UNIQUE KEY `roleCode` (`roleCode`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Shiro_PermissionShiro_Permission用于存储权限，一个角色可能有多个权限，一个权限也可能隶属于多个角色。CREATE TABLE `Shiro_Permission` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `permissionCode` varchar(255) NOT NULL, `permissionName` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `id` (`id`) USING BTREE, UNIQUE KEY `permissionCode` (`permissionCode`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Shiro_User_RoleShiro_User_Role用于存储用户与角色的关联关系。CREATE TABLE `Shiro_User_Role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `userId` bigint(20) NOT NULL, `roleId` bigint(20) NOT NULL, PRIMARY KEY (`id`), KEY `userId` (`userId`), KEY `roleId` (`roleId`), CONSTRAINT `shiro_user_role_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `Shiro_User` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT `shiro_user_role_ibfk_2` FOREIGN KEY (`roleId`) REFERENCES `Shiro_Role` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Shiro_Role_PermissionShiro_Role_Permission存储角色与权限的关联关系。CREATE TABLE `Shiro_Role_Permission` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `roleId` bigint(20) NOT NULL, `permissionId` bigint(20) NOT NULL, PRIMARY KEY (`id`), KEY `roleId` (`roleId`), KEY `permissionId` (`permissionId`), CONSTRAINT `shiro_role_permission_ibfk_1` FOREIGN KEY (`roleId`) REFERENCES `Shiro_Role` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT `shiro_role_permission_ibfk_2` FOREIGN KEY (`permissionId`) REFERENCES `Shiro_Permission` (`id`) ON DELETE CASCADE ON UPDATE NO ACTION ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;至此，数据库表结构已经设计完成。
超详细Vue+Spring Boot整合Shiro前后端分离架构-Shiro介绍（一）
其实网上关于spring boot整合shiro的例子并不少见，为什么我这里要重复\`造轮子\`，因为网上所谓的整合，基本上都是以Demo为主，很少能见到Vue+Spring Boot+Shiro完整整合的例子。Shiro概念Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。三个核心组件：Subject, SecurityManager 和 Realms.Subject：即“当前操作用户”。但是，在Shiro中，Subject这一概念并不仅仅指人，也可以是第三方进程、后台帐户（Daemon Account）或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。Subject代表了当前用户的安全操作，SecurityManager则管理所有用户的安全操作。SecurityManager：它是Shiro框架的核心，典型的Facade模式，Shiro通过SecurityManager来管理内部组件实例，并通过它来提供安全管理的各种服务。Realm： Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说，当对用户执行认证（登录）和授权（访问控制）验证时，Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲，Realm实质上是一个安全相关的DAO：它封装了数据源的连接细节，并在需要时将相关数据提供给Shiro。当配置Shiro时，你必须至少指定一个Realm，用于认证和（或）授权。配置多个Realm是可以的，但是至少需要一个。Shiro内置了可以连接大量安全数据源（又名目录）的Realm，如LDAP、关系数据库（JDBC）、类似INI的文本配置资源以及属性文件等。如果系统默认的Realm不能满足需求，你还可以插入代表自定义数据源的自己的Realm实现。