写点什么

Springboot 整合 Shiro 轻量级权限框架,从数据库设计开始带你快速上手 shiro

  • 2021 年 11 月 10 日
  • 本文字数:7168 字

    阅读完需:约 24 分钟

sys_permissions AS per,


role_per,


user_role


WHERE user.userName='adminHong'


AND user.userId=user_role.userId


AND user_role.roleId=role.roleId


AND role_per.roleId=role.roleId


AND role_per.perId=per.perId


查询出来的结果是:



根据用户登录帐号查询出这个帐号的角色身份,权限,结果非常清晰。


再查下另一个帐号,也是非常清晰:



ok,这么啰嗦地终于从数据库的设计 以及 模拟帐号注册,创建角色,创建权限,给帐号分批角色,给角色分批权限生成的数据


的层面场景,大致给这套系统呈现了个漂浮的使用场景。


代码实现




接下来就是看咱们怎么结合这个数据库去实现了。


先来看看我们的最后项目结构(里面的 controller 就是模拟的各个功能模块,日志功能,登录功能,导出功能):



创建一个 springboot 项目,


然后在 pom.xml 里,加入我们需要使用到的 jar 包:


<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">


<modelVersion>4.0.0</modelVersion>


<parent>


<groupId>org.springframework.boot</groupId>


<artifactId>spring-boot-starter-parent</artifactId>


<version>2.2.6.RELEASE</version>


</parent>


<groupId>com.jc</groupId>


<artifactId>shiro</artifactId>


<version>0.0.1-SNAPSHOT</version>


<name>shiro</name>


<description>Demo project for Spring Boot</description>


<properties>


<java.version>1.8</java.version>


</properties>


<dependencies>


<dependency>


<groupId>org.springframework.boot</groupId>


<artifactId>spring-boot-starter-web</artifactId>


</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>com.alibaba</groupId>


<artifactId>druid-spring-boot-starter</artifactId>


<version>1.1.10</version>


</dependency>


<dependency>


<groupId>mysql</groupId>


<artifactId>mysql-connector-java</artifactId>


<scope>runtime</scope>


</dependency>


<dependency>


<groupId>org.projectlombok</groupId>


<artifactId>lombok</artifactId>


<version>1.18.10</version>


</dependency>


<dependency>


<groupId>org.mybatis.spring.boot</groupId>


<artifactId>mybatis-spring-boot-starter</artifactId>


<version>2.0.1</version>


</dependency>


<dependency>


<groupId>org.apache.shiro</groupId>


<artifactId>shiro-spring</artifactId>


<version>1.4.0</version>


</dependency>


</dependencies>


<build>


<plugins>


<plugin>


<groupId>org.springframework.boot</groupId>


<artifactId>spring-boot-maven-plugin</artifactId>


</plugin>


</plugins>


</build>


</project>


然后是 application.yml 文件:


server:


port: 8077


spring:


datasource:


druid:


username: root


password: root


url: jdbc:mysql://localhost:3306/my_system?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull


initialSize: 5


minIdle: 5


maxActive: 20


maxWait: 60000


timeBetweenEvictionRunsMillis: 60000


minEvictableIdleTimeMillis: 300000


validationQuery: SELECT 1 FROM DUAL


testWhileIdle: true


testOnBorrow: false


testOnReturn: false


poolPreparedStatements: true


maxPoolPreparedStatementPerConnectionSize: 20


useGlobalDataSourceStat: true


connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000


mybatis:


config-location: classpath:mybatis/mybatis-config.xml


mapper-locations: classpath:mybatis/mapper/*.xml


接下来是对照数据库表结构,创建三个 pojo:


SysUser.java


import lombok.Data;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@Data


public class SysUser {


private Integer userId;


private String userName;


private String password;


private String userRemarks;


}


SysRole.class


import lombok.Data;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@Data


public class SysRole {


private String roleId;


private String roleName;


private String roleRemarks;


}


SysPermissions.java


import lombok.Data;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@Data


public class SysPermissions {


private Integer perId;


private String permissionsName;


private String perRemarks;


}


实体类创建完毕,先不用管那些操作表的增删改查。


核心使用环节, 创建 ShiroConfig.java :


import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;


import org.apache.shiro.spring.web.ShiroFilterFactoryBean;


import org.apache.shiro.web.mgt.DefaultWebSecurityManager;


import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;


import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;


import org.springframework.context.annotation.Bean;


import org.springframework.context.annotation.Configuration;


import java.util.HashMap;


import java.util.Map;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@Configuration


public class ShiroConfig {


@Bean


@ConditionalOnMissingBean


public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {


DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();


defaultAAP.setProxyTargetClass(true);


return defaultAAP;


}


//将自己的验证方式加入容器


@Bean


public UserRealm myShiroRealm() {


UserRealm userRealm = new UserRealm();


return userRealm;


}


//权限管理,配置主要是 Realm 的管理认证


@Bean


public DefaultWebSecurityManager securityManager() {


DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();


securityManager.setRealm(myShiroRealm());


return securityManager;


}


//对 url 的过滤筛选


@Bean


public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {


ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();


shiroFilterFactoryBean.setSecurityManager(securityManager);


Map<String, String> map = new HashMap<>();


//登出


map.put("/logout", "logout");


//对所有用户认证


map.put("/**", "authc");


//登录


shiroFilterFactoryBean.setLoginUrl("/login");


//成功登录后跳转的 url


//shiroFilterFactoryBean.setSuccessUrl("/xxxx");


//错误页面,认证不通过跳转


// shiroFilterFactoryBean.setUnauthorizedUrl("/error");


shiroFilterFactoryBean.setFilterChainDefinitionMap(map);


return shiroFilterFactoryBean;


}


@Bean


public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {


AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();


authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);


return authorizationAttributeSourceAdvisor;


}


}


创建自定义的权限审核处理类,UserRealm.java(涉及到数据库的查询文章后面有写):


import com.jc.shiro.pojo.SysUser;


import com.jc.shiro.service.LoginService;


import org.apache.shiro.authc.AuthenticationException;


import org.apache.shiro.authc.AuthenticationInfo;


import org.apache.shiro.authc.AuthenticationToken;


import org.apache.shiro.authc.SimpleAuthenticationInfo;


import org.apache.shiro.authz.AuthorizationInfo;


import org.apache.shiro.authz.SimpleAuthorizationInfo;


import org.apache.shiro.realm.AuthorizingRealm;


import org.apache.shiro.subject.PrincipalCollection;


import org.springframework.beans.factory.annotation.Autowired;


import java.util.List;


import java.util.Map;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


public class UserRealm extends AuthorizingRealm {


@Autowired


private LoginService loginService;


@Override


protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {


//获取登录用户名


String userName = (String) principalCollection.getPrimaryPrincipal();


//添加角色和权限


SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();


List<Map<String, Object>> powerList = loginService.getUserPower(userName);


System.out.println(powerList.toString());


for (Map<String, Object> powerMap : powerList) {


//添加角色


simpleAuthorizationInfo.addRole(String.valueOf(powerMap.get("roleName")));


//添加权限


simpleAuthorizationInfo.addStringPermission(String.valueOf(powerMap.get("permissionsName")));


}


return simpleAuthorizationInfo;


}


@Override


protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {


//加这一步的目的是在 Post 请求的时候会先进认证,然后在到请求


if (authenticationToken.getPrincipal() == null) {


return null;


}


//获取用户信息


String userName = authenticationToken.getPrincipal().toString();


//根据用户名去数据库查询用户信息


SysUser sysUser = loginService.queryUser(userName);


if (sysUser == null) {


//这里返回后会报出对应异常


return null;


} else {


//这里验证 authenticationToken 和 simpleAuthenticationInfo 的信息


SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, sysUser.getPassword().toString(), getName());


return simpleAuthenticationInfo;


}


}


}


然后是我们的登录接口,这里融合自己项目的帐号加密方法,LoginController.java:


import com.jc.shiro.service.LoginService;


import org.apache.shiro.SecurityUtils;


import org.apache.shiro.authc.AuthenticationException;


import org.apache.shiro.authc.UsernamePasswordToken;


import org.apache.shiro.authz.AuthorizationException;


import org.apache.shiro.subject.Subject;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.stereotype.Controller;


import org.springframework.util.DigestUtils;


import org.springframework.web.bind.annotation.GetMapping;


import org.springframework.web.bind.annotation.RequestParam;


import org.springframework.web.bind.annotation.ResponseBody;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@Controller


public class LoginController {


@Autowired


LoginService loginService;


@ResponseBody


@GetMapping("/login")


public String login(@RequestParam("userName") String userName, @RequestParam("password") String password) {


//添加用户认证信息


Subject subject = SecurityUtils.getSubject();


//自己系统的密码加密方式 ,这里简单示例一下 MD5


String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());


UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, md5Password);


try {


//进行验证,AuthenticationException 可以 catch 到,但是 AuthorizationException 因为我们使用注解方式,是 catch 不到的,


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


所以后面使用全局异常捕抓去获取


subject.login(usernamePasswordToken);


} catch (AuthenticationException e) {


e.printStackTrace();


return "账号或密码错误!";


} catch (AuthorizationException e) {


e.printStackTrace();


return "没有权限";


}


return "login success";


}


}


其中用到了一个根据用户登录帐号去查询对应的角色权限信息,也就是文章开头我们设计的查询。


mapper 层:


LoginMapper.java


import com.jc.shiro.pojo.SysUser;


import org.apache.ibatis.annotations.Mapper;


import java.util.List;


import java.util.Map;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@Mapper


public interface LoginMapper {


SysUser queryUser(String userName );


List<Map<String,Object>> getUserPower(String userName);


}


loginMapper.xml:


<?xml version="1.0" encoding="UTF-8" ?>


<!DOCTYPE mapper


PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"


"http://mybatis.org/dtd/mybatis-3-mapper.dtd">


<mapper namespace="com.jc.shiro.mapper.LoginMapper">


<select id="queryUser" resultType="com.jc.shiro.pojo.SysUser" parameterType="String">


SELECT *


FROM sys_user


WHERE userName=#{userName}


</select>


<select id="getUserPower" resultType="java.util.HashMap" parameterType="String">


SELECT user.userId ,user.userName,role.roleName,role.roleId,per.permissionsName ,per.perId,per.perRemarks


FROM sys_user AS user,


sys_role AS role,


sys_permissions AS per,


role_per,


user_role


WHERE user.userName=#{userName}


AND user.userId=user_role.userId


AND user_role.roleId=role.roleId


AND role_per.roleId=role.roleId


AND role_per.perId=per.perId


</select>


</mapper>


然后是 service 层:


LoginService.java


import com.jc.shiro.pojo.SysUser;


import java.util.List;


import java.util.Map;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


public interface LoginService {


SysUser queryUser(String userName );


List<Map<String,Object>> getUserPower(String userName );


}


LoginServiceImpl.java


import com.jc.shiro.mapper.LoginMapper;


import com.jc.shiro.pojo.SysUser;


import com.jc.shiro.service.LoginService;


import org.springframework.beans.factory.annotation.Autowired;


import org.springframework.stereotype.Service;


import java.util.List;


import java.util.Map;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@Service


public class LoginServiceImpl implements LoginService {


@Autowired


LoginMapper loginMapper;


@Override


public SysUser queryUser(String userName) {


return loginMapper.queryUser(userName);


}


@Override


public List<Map<String, Object>> getUserPower(String userName) {


return loginMapper.getUserPower(userName);


}


}


最后再补上一个异常全局控制器,MyExceptionHandler.java:


import lombok.extern.slf4j.Slf4j;


import org.apache.shiro.authz.AuthorizationException;


import org.springframework.web.bind.annotation.ControllerAdvice;


import org.springframework.web.bind.annotation.ExceptionHandler;


import org.springframework.web.bind.annotation.ResponseBody;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@ControllerAdvice


@Slf4j


public class MyExceptionHandler {


@ExceptionHandler


@ResponseBody


public String ErrorHandler(AuthorizationException e) {


log.error("权限校验失败!", e);


return "您暂时没有权限,请联系管理员!";


}


}


到这里,已经整合完毕,接下来是我们的使用,后端的校验我们采取 shiro 提供的注解的方式去使用:


@RequiresRoles("xxx")


@RequiresPermissions("xxx")


我们简单模拟导出数据功能模块,ExportController.java:


暂时只提供了一个导出接口,而这个导出接口需要用户拥有 admin 角色 以及?exportUserInfo 权限,也就是代码里注解的参数


import org.apache.shiro.authz.annotation.RequiresPermissions;


import org.apache.shiro.authz.annotation.RequiresRoles;


import org.springframework.web.bind.annotation.RequestMapping;


import org.springframework.web.bind.annotation.ResponseBody;


import org.springframework.web.bind.annotation.RestController;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@RestController


public class ExportController {


@RequiresRoles("admin")


@RequiresPermissions("exportUserInfo")


@ResponseBody


@RequestMapping("/export")


public String export() {


return "u can export !";


}


}


再来模拟日志功能模块,LogController.java:


import org.apache.shiro.authz.annotation.RequiresRoles;


import org.springframework.web.bind.annotation.RequestMapping;


import org.springframework.web.bind.annotation.ResponseBody;


import org.springframework.web.bind.annotation.RestController;


/**


  • @Author : JCccc

  • @CreateTime : 2020/4/24

  • @Description :


**/


@RestController


public class LogController {


//注解验角色和权限


@RequiresRoles("common")


@ResponseBody


@RequestMapping("/querySystemLog")


public String queryLog() {


return "u can queryLog !";


}


}


测试效果




好,接下来我们从登录开始,测试下整体的效果:

评论

发布
暂无评论
Springboot 整合Shiro 轻量级权限框架,从数据库设计开始带你快速上手shiro