SpringBoot+Shiro实现权限控制

编程

一、Shiro简介

二、项目实现

2.1  数据库结构

2.2 SQL

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------

-- Table structure for menu

-- ----------------------------

DROP TABLE IF EXISTS `menu`;

CREATE TABLE `menu` (

`menu_id` int(32) NOT NULL AUTO_INCREMENT,

`menu_ name` varchar(200) NOT NULL,

`parent_id` int(32) NOT NULL DEFAULT "0",

`url` varchar(250) NOT NULL DEFAULT "#",

`menu_type` int(3) NOT NULL,

`perms` varchar(250) NOT NULL,

PRIMARY KEY (`menu_id`)

) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of menu

-- ----------------------------

INSERT INTO `menu` VALUES ("1", "用户管理", "0", "#", "1", "");

INSERT INTO `menu` VALUES ("2", "添加用户", "1", "/user/add", "2", "user:add");

INSERT INTO `menu` VALUES ("3", "分页查询用户", "1", "/user/page", "2", "user:page");

INSERT INTO `menu` VALUES ("4", "修改用户", "1", "/user/update", "2", "user:update");

INSERT INTO `menu` VALUES ("5", "删除用户", "1", "/user/delete", "2", "user:delete");

-- ----------------------------

-- Table structure for role

-- ----------------------------

DROP TABLE IF EXISTS `role`;

CREATE TABLE `role` (

`role_id` int(32) NOT NULL AUTO_INCREMENT,

`role_name` varchar(200) DEFAULT NULL,

`remark` varchar(255) DEFAULT NULL,

`delete_flag` int(3) DEFAULT NULL,

PRIMARY KEY (`role_id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of role

-- ----------------------------

INSERT INTO `role` VALUES ("1", "admin", "系统管理员", "2");

INSERT INTO `role` VALUES ("2", "biz", "业务用户", "2");

-- ----------------------------

-- Table structure for role_menu

-- ----------------------------

DROP TABLE IF EXISTS `role_menu`;

CREATE TABLE `role_menu` (

`role_menu_id` int(32) NOT NULL AUTO_INCREMENT,

`role_id` int(32) NOT NULL,

`menu_id` int(32) NOT NULL,

PRIMARY KEY (`role_menu_id`)

) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of role_menu

-- ----------------------------

INSERT INTO `role_menu` VALUES ("1", "1", "1");

INSERT INTO `role_menu` VALUES ("2", "1", "2");

INSERT INTO `role_menu` VALUES ("3", "1", "3");

INSERT INTO `role_menu` VALUES ("4", "1", "4");

INSERT INTO `role_menu` VALUES ("5", "1", "5");

-- ----------------------------

-- Table structure for user

-- ----------------------------

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`user_id` bigint(32) NOT NULL AUTO_INCREMENT,

`user_name` varchar(50) NOT NULL,

`password` varchar(50) NOT NULL,

`salt` varchar(255) NOT NULL,

`del_flag` int(3) NOT NULL DEFAULT "2",

PRIMARY KEY (`user_id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of user

-- ----------------------------

INSERT INTO `user` VALUES ("1", "admin", "14e1b600b1fd579f47433b88e8d85291", "14e1b600b1fd579", "2");

INSERT INTO `user` VALUES ("2", "biz", "14e1b600b1fd579f47433b88e8d85291", "579f47433b88e8d85291", "2");

-- ----------------------------

-- Table structure for user_role

-- ----------------------------

DROP TABLE IF EXISTS `user_role`;

CREATE TABLE `user_role` (

`user_role_id` int(32) NOT NULL AUTO_INCREMENT,

`user_id` int(32) NOT NULL,

`role_id` int(32) NOT NULL,

PRIMARY KEY (`user_role_id`)

) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------

-- Records of user_role

-- ----------------------------

INSERT INTO `user_role` VALUES ("1", "1", "1");

INSERT INTO `user_role` VALUES ("2", "2", "2");

2.3 项目结构

2.4 Maven依赖

<properties>

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

</properties>

<parent>

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

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

<version>2.2.6.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<dependencies>

<dependency>

<groupId>mysql</groupId>

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

</dependency>

<dependency>

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

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

</dependency>

<!--Shiro Spring-->

<dependency>

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

<artifactId>shiro-spring</artifactId>

<version>1.5.3</version>

</dependency>

<!--MyBatis-->

<dependency>

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

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

<version>2.1.1</version>

</dependency>

<dependency>

<groupId>org.mybatis</groupId>

<artifactId>mybatis-typehandlers-jsr310</artifactId>

<version>1.0.1</version>

</dependency>

</dependencies>

<build>

<plugins>

<plugin>

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

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

</plugin>

</plugins>

</build>

2.5 基础代码

    POJO、Mapper、Service等,不再这里赘述。

2.6 自定义Shiro Realm

/**

* 自定义权限控制数据源

*/

public class CustomRealm extends AuthorizingRealm {

@Autowired

private IUserService userService;

@Autowired

private IRoleService roleService;

@Autowired

private IMenuService menuService;

/**

* 授权,通过服务加载用户角色和权限信息设置进去

* @param principalCollection

* @return

*/

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

if(null == principalCollection){

throw new AuthorizationException("principalCollection should not be null.");

}

//记录用户的角色和权限

SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

//获取登录的用户名

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

//从数据库中,根据用户名查询用户信息

User user = userService.getUserByUserName(userName);

//用户所属角色集合

List<Role> roleList = roleService.queryByUserId(user.getUserId());

for (Role role : roleList) {

//添加角色

simpleAuthorizationInfo.addRole(role.getRoleName());

List<Menu> menuList = menuService.queryByRoleId(role.getRoleId());

//添加权限

for (Menu menu : menuList) {

simpleAuthorizationInfo.addStringPermission(menu.getPerms());

}

}

return simpleAuthorizationInfo;

}

/**

* 身份验证,通过服务加载用户信息并构造认证对象返回

* @param authenticationToken

* @return

* @throws AuthenticationException

*/

@Override

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

//从Token获取用户名,从主体认证信息中获取。Post请求时会先进入认证再到请求。

Object principal = authenticationToken.getPrincipal();

if(null == principal){

return null;

}

//获取用户名

String userName = String.valueOf(principal);

//获取密码

String password = new String((char[])authenticationToken.getCredentials());

//根据用户名查询用户信息

User user = userService.getUserByUserName(userName);

if(null == user){

throw new UnknownAccountException("用户名或密码错误!");

}

if(!user.getPassword().equals(MD5Util.md5Encrypt32Lower(password))){

throw new IncorrectCredentialsException("用户名或密码错误!");

}

//验证AuthenticationToken和SimpleAuthenticationInfo的信息

SimpleAuthenticationInfo simpleAuthenticationInfo

= new SimpleAuthenticationInfo(userName,user.getPassword(),super.getName());

return simpleAuthenticationInfo;

}

}

2.7 自定义密码验证器

/**

* 自定义密码验证器

*/

public class CustomMatcher extends SimpleCredentialsMatcher {

/**

* 重写密码校验逻辑

* @param token

* @param info

* @return

*/

@Override

public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;

String tokenPassword = this.encrypt(String.valueOf(usernamePasswordToken.getPassword()));

String password = (String)info.getCredentials();

return super.equals(tokenPassword,password);

}

/**

* 将传来的密码再次加密

* 可根据实际需求修改成别的加密方式

* @param str

* @return

*/

private String encrypt(String str){

return MD5Util.md5Encrypt32Lower(str);

}

}

2.8 自定义记住我过滤器

/**

* 自定义“记住我”过滤器

*/

public class CustomRememberMeFilter extends FormAuthenticationFilter {

/**

* 登录条件过滤:要么通过权限认证登录成功,要么通过“记住我”登录成功。

* @param request

* @param response

* @param mappedValue

* @return

*/

@Override

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {

Subject subject = super.getSubject(request, response);

if(!subject.isAuthenticated() && !subject.isRemembered()){

if(null == subject.getSession().getAttribute("user") && null == subject.getPrincipal()){

subject.getSession().setAttribute("user",subject.getPrincipal());

}

}

return subject.isAuthenticated() || subject.isRemembered();

}

}

2.9 Shiro配置类

@Configuration

public class ShiroConfig {

/**

* 密码验证器

* @return

*/

@Bean

public CredentialsMatcher credentialsMatcher(){

return new CustomMatcher();

}

/**

* 权限验证

* @param credentialsMatcher

* @return

*/

@Bean

public CustomRealm customRealm(@Qualifier("credentialsMatcher") CredentialsMatcher credentialsMatcher){

CustomRealm customRealm = new CustomRealm();

//给权限验证器注入自定义的密码验证器

customRealm.setCredentialsMatcher(credentialsMatcher);

return customRealm;

}

/**

* 缓存管理

* @return

*/

@Bean

public CacheManager cacheManager(){

return new MemoryConstrainedCacheManager();

}

/**

* “记住我”Cookie管理

* @return

*/

@Bean

public CookieRememberMeManager cookieRememberMeManager(){

CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();

//手动设置对称加密密钥,避免出现 Unable to execute "doFinal" with cipher instance 异常。原因在于:rememberMe的cookie在第二次打开页面后Shiro无法解密

cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j3Y+R1aSn5BOlAA=="));

cookieRememberMeManager.setCookie(this.rememberMeCookie());

return cookieRememberMeManager;

}

/**

* “记住我”Cookie对象

* @return

*/

private SimpleCookie rememberMeCookie(){

SimpleCookie simpleCookie = new SimpleCookie("rememberMe");

//cookie有效时间为30天

simpleCookie.setMaxAge(2592000);

return simpleCookie;

}

/**

* 自定义“记住我”过滤器

* @return

*/

@Bean

public CustomRememberMeFilter customRememberMeFilter(){

return new CustomRememberMeFilter();

}

/**

* Session会话管理

* @return

*/

@Bean

public DefaultWebSessionManager sessionManager(){

DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();

Long timeout=60L*1000*60;

defaultWebSessionManager.setGlobalSessionTimeout(timeout);

return defaultWebSessionManager;

}

/**

* SecurityManager安全管理器,起桥梁作用

* @param customRealm

* @return

*/

@Bean("securityManager")

public SecurityManager securityManager(@Qualifier("customRealm") CustomRealm customRealm){

DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();

//自定义Realm

defaultWebSecurityManager.setRealm(customRealm);

//自定义CacheManager

defaultWebSecurityManager.setCacheManager(this.cacheManager());

//自定义“记住我”

defaultWebSecurityManager.setRememberMeManager(this.cookieRememberMeManager());

//自定义SessionManager

defaultWebSecurityManager.setSessionManager(this.sessionManager());

return defaultWebSecurityManager;

}

/**

* 全局配置,Filter工厂。

* 设置对应的过滤条件和跳转条件,有自定义的过滤器、Shiro认证成功、失败、退出登录等跳转的页面。

* @param securityManager

* @return

*/

@Bean

public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager){

ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

shiroFilterFactoryBean.setSecurityManager(securityManager);

shiroFilterFactoryBean.setLoginUrl("/login");

Map<String, Filter> filterMap = new LinkedHashMap<>();

filterMap.put("customRememberFilter",this.customRememberMeFilter());

LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap<>();

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

linkedHashMap.put("/error","anon");

shiroFilterFactoryBean.setFilterChainDefinitionMap(linkedHashMap);

return shiroFilterFactoryBean;

}

/**

* Shiro生命周期

* @return

*/

@Bean

public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){

return new LifecycleBeanPostProcessor();

}

/**

* 配置Shiro注解是否生效

* @RequiresRoles,@RequiresPermissions

* @return

*/

@Bean

public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){

DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);

return defaultAdvisorAutoProxyCreator;

}

@Bean

public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager){

AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();

sourceAdvisor.setSecurityManager(securityManager);

return sourceAdvisor;

}

}

2.10 登录Controller

@Controller

public class LoginController {

@Autowired

private IUserService userService;

@PostMapping("/login")

@ResponseBody

public String login(@RequestBody LoginDto loginDto){

UsernamePasswordToken uToken

= new UsernamePasswordToken(loginDto.getUserName(), loginDto.getPassword());

//记住我

uToken.setRememberMe(true);

Subject subject = SecurityUtils.getSubject();

try{

subject.login(uToken);

}catch (UnknownAccountException e){

return e.getMessage();

}catch (IncorrectCredentialsException e){

return e.getMessage();

}

//这里可将一些用户信息保存在Session中

User user = userService.getUserByUserName(loginDto.getUserName());

subject.getSession().setAttribute("user",user);

return "登录成功!";

}

}

2.11 用户模块Controller

此处只演示用法,不赘述业务逻辑。具体业务逻辑可自行定义。

@RestController

@RequestMapping("/user")

public class UserController {

@RequiresPermissions("user:page")

@PostMapping("/page")

public String page(){

return "分页查询";

}

@RequiresPermissions("user:add")

@PostMapping("/add")

public String add(){

return "添加用户";

}

@RequiresPermissions("user:update")

@PostMapping("/update")

public String update(){

return "修改用户";

}

@RequiresPermissions("user:delete")

@PostMapping("/delete")

public String delete(){

return "删除用户";

}

}

2.12 Shiro异常处理

@ControllerAdvice

public class HandlerException {

@ResponseBody

@ExceptionHandler(UnauthorizedException.class)

public String handlerShiroException(Exception e){

return "无权限操作!";

}

public String authorizationException(Exception e){

return "权限验证失败!";

}

}

三、演示

    本案例的数据表中,设置了两个用户,两个角色,五个菜单。

用户名

角色

可操作菜单

admin

admin(系统管理员)

用户管理:分页查询、添加、编辑、删除

biz

biz(业务用户)

其他业务模块(不包括用户管理模块)

    验证规则:admin登录,可以调用page、add、update、delete接口;biz登录,

调用page、add、update、delete接口提示“无操作权限”。

3.1 admin登录

登录接口:

3.2 biz登录

登录接口:

四、笔记

    关于CustomRealm里定义的两个方法,

    doGetAuthorization方法用于授权,查询用户角色和权限信息,设置进AuthorizationInfo中;

    doGetAuthentication方法,用于身份验证,查询用户信息,构造一个认证对象。

    当我们调用“/login”接口,即调用subject.login(uToken)方法时,会自动进入CustomRealm中指定

doGetAuthentication(认证)方法,认证交由SimpleAuthenticationInfo来完成,它会完成身份密码的比较等功能。但是登录接口不走

doGetAuthorization(授权)方法。

    当访问带有@RequiresPermissions或@RequiresRoles注解的接口时,才会去调用

doGetAuthorization(授权)方法。

五、参考链接

https://blog.csdn.net/jiankang66/article/details/90473517

https://www.cnblogs.com/Slags/p/12403448.html

https://www.jianshu.com/p/7f724bec3dc3

https://blog.csdn.net/lidai352710967/article/details/83654132

以上是 SpringBoot+Shiro实现权限控制 的全部内容, 来源链接: utcz.com/z/519020.html

回到顶部