废话不多说 ,咱们直接接上回
上一篇我们讲了如何使用Springboot框架整合Nosql ,并于文章最后部分引入了服务端Session的概念
而早在上上一篇中 ,我们则已经讲到了如何使用Springboot框架整合Mybatis/MybatisPlus实现业务数据的持久化(写入数据库)
本篇我们把关注点放在一个于这两部分有共同交集的内容——安全管理 ,并且引入我们今天的主角——Shiro框架
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证 、授权 、密码和会话管理 。使用Shiro的易于理解的API,您可以快速 、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序 。
—— 来自百度百科
Shiro框架包含三个核心组件:
Subject —— 泛指当前与Shiro交互中的实体 ,可以是用户或者某后台进程
SecurityManager —— Shiro的核心组件 ,对内管理各种组件实例 ,对外提供各种安全服务
Realm —— Shiro与安全数据之间的桥接器
Shiro框架还包含有其他诸多概念 ,为降低大家的心智负担 ,这些我们暂且不谈 ,文末会给大家推荐延展阅读的相关文章
还是老规矩直接上干货 ,以完整的实例让大家对【如何基于Shiro实现权限的细粒度控制】有一个整体上的认知
不知道大家会不会觉得项目结构突然变复杂?别担心 ,接下来我会给大家逐一拆解
1. 创建数据表
首先是角色表——role
然后是用户表——user
最后是权限表——permission
2. 创建三个对应的Mapper
package com.example.hellospringboot.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.hellospringboot.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
}
package com.example.hellospringboot.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.hellospringboot.model.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
@Mapper
@Repository
public interface PermissionMapper extends BaseMapper<Permission> {
}
这里我们用到了上上一节讲到的内容
这里的Mapper会辅助于后续的安全数据读取
3. 接下来是Service及其实现类
package com.example.hellospringboot.service;
import com.example.hellospringboot.model.Role;
public interface RoleService {
Role findRoleById(int id);
}
package com.example.hellospringboot.service.impl;
import com.example.hellospringboot.mapper.RoleMapper;
import com.example.hellospringboot.model.Role;
import com.example.hellospringboot.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class RoleServiceImpl implements RoleService {
@Autowired
RoleMapper mapper;
public Role findRoleById(int id){
Role role = mapper.selectById(id);
return role;
}
}
package com.example.hellospringboot.service;
import com.example.hellospringboot.model.User;
public interface UserService {
boolean checkUserByUsernameAndPassword(String userName, String passWord);
User findUserByUserName(String userName);
}
package com.example.hellospringboot.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.hellospringboot.mapper.UserMapper;
import com.example.hellospringboot.model.User;
import com.example.hellospringboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserMapper mapper;
public boolean checkUserByUsernameAndPassword(String userName, String passWord){
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper = wrapper.eq("user_name", userName).eq("pass_word",passWord);
List<User> userList = mapper.selectList(wrapper);
return userList.size() > 0;
}
public User findUserByUserName(String userName){
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper = wrapper.eq("user_name", userName);
User user = mapper.selectOne(wrapper);
return user;
}
}
package com.example.hellospringboot.service;
import com.example.hellospringboot.model.Permission;
import java.util.List;
public interface PermissionService {
List<Permission> findPermissionsByRoleId(int roleId);
}
package com.example.hellospringboot.service.impl;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.hellospringboot.mapper.PermissionMapper;
import com.example.hellospringboot.model.Permission;
import com.example.hellospringboot.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PermissionServiceImpl implements PermissionService {
@Autowired
PermissionMapper mapper;
public List<Permission> findPermissionsByRoleId(int roleId){
QueryWrapper<Permission> wrapper = new QueryWrapper<>();
wrapper = wrapper.eq("role_id", roleId);
List<Permission> list = mapper.selectList(wrapper);
return list;
}
}
ok,我们已经准备好了所有的安全数据 ,及对应的读取方法
到这里 ,我们就算是做好了所有的准备工作
接下来看我们如何通过Shiro框架来运用这些已经装配好的枪炮子弹
4. 引入Shiro框架相关依赖(pom.xml)
<!-- 引入shiro框架依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.10.0</version>
</dependency>
这次pom.xml终于不是第一步了,哈哈哈 。 。 。
5. 创建Realm嫁接Shiro框架及安全数据(realm/MyAuthorizingRealm)
package com.example.hellospringboot.realm;
import com.example.hellospringboot.model.Permission;
import com.example.hellospringboot.model.Role;
import com.example.hellospringboot.model.User;
import com.example.hellospringboot.service.PermissionService;
import com.example.hellospringboot.service.RoleService;
import com.example.hellospringboot.service.UserService;
import org.apache.shiro.authc.*;
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.HashSet;
import java.util.List;
import java.util.Set;
public class MyAuthorizingRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Autowired
RoleService roleService;
@Autowired
PermissionService permissionService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String userName = token.getUsername();
String passWord = String.valueOf(token.getPassword());
if (!userService.checkUserByUsernameAndPassword(userName, passWord)) {//判断用户账号是否正确
throw new UnknownAccountException("用户名或密码错误!");
}
return new SimpleAuthenticationInfo(userName, passWord, getName());
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
String userName = principalCollection.getPrimaryPrincipal().toString();
User user = userService.findUserByUserName(userName);
if (user == null) {
throw new UnknownAccountException("用户名或密码错误!");
}
List<Integer> rolesList = user.rolesList();
Set<String> roles = new HashSet<>();
Set<String> permissions = new HashSet<>();
for (Integer roleId : rolesList) {
Role role = roleService.findRoleById(roleId);
roles.add(role.getName());
List<Permission> permissionList = permissionService.findPermissionsByRoleId(roleId);
for (Permission permission : permissionList) {
permissions.add(permission.getName());
}
}
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}
}
Realm的创建对于整个Shiro安全验证体系搭建而言是至关重要的一步!
其中两个抽象方法
doGetAuthenticationInfo —— 用于校验用户名及密码的合法性
doGetAuthorizationInfo —— 用于赋予实体对应的角色及交互权限
6. 测试用Controller创建
package com.example.hellospringboot.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
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;
@RequestMapping("/user")
@RestController
public class UserController {
@PostMapping("/login")
public String login(String user, String pass) {
UsernamePasswordToken token = new UsernamePasswordToken(user, pass);
Subject subject = SecurityUtils.getSubject();
if(!subject.isAuthenticated()) {
try {
subject.login(token);
} catch (AuthenticationException e) {
return e.getMessage();
}
}
return "ok";
}
@PostMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
if(subject.isAuthenticated()) {
try {
subject.logout();
} catch (AuthenticationException e) {
return e.getMessage();
}
}
return "ok";
}
@GetMapping("/admin")
public String admin() {
return "admin";
}
@GetMapping("/user")
public String user() {
return "user";
}
}
内容很简单:
login——登录方法
logout——登出方法
admin 、user——两个测试方法 ,用于测试不同角色对于不同方法可访问的细粒度控制
7. ShiroConfig配置类创建 ,实现用户访问权限的细粒度控制
package com.example.hellospringboot.configure;
import com.example.hellospringboot.realm.MyAuthorizingRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
public SecurityManager securityManager(Realm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
@Bean
public MyAuthorizingRealm getRealm() {
MyAuthorizingRealm realm = new MyAuthorizingRealm();
return realm;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
Map<String, String> filterChainMap = new LinkedHashMap<String, String>();
filterChainMap.put("/user/login", "anon");
filterChainMap.put("/user/logout", "anon");
filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]");
filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]");
shiroFilter.setFilterChainDefinitionMap(filterChainMap);
return shiroFilter;
}
}
securityManager 和getRealm 显示指定了Shiro两大组件的实例声明
shiroFilterFactoryBean 则是实现角色访问权限控制的重要方法
filterChainMap.put("/user/login", "anon"); // 代表login方法可以匿名访问
filterChainMap.put("/user/logout", "anon"); // 代表logout方法可以匿名访问
filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]"); // 代表admin方法需要用户满足admin角色 ,同时具备admin:read权限
filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]"); // 代表user方法需要用户满足user角色 ,同时具备user:read权限
至此 ,整个接入流程便结束了
我们再次结合最开始我们配置的数据来对业务逻辑进行分析
用户 admin ,同时具备admin 、user两种角色
用户juste ,仅具备user一种角色
角色 admin ,同时具备admin:write 、admin:read两种权限
角色 user ,同时具备user:write 、user:read两种权限
因此
用户 admin ,同时具备admin:write 、admin:read 、user:write 、user:read 四种操作权限
用户 juste ,同时具备user:write 、user:read两种操作权限
大家理清楚这其中的关系了吗?^ ^
8. 执行Postman验证结果
我们在执行login之前 ,admin方法无权访问
登录admin之后,同时具备admin和user方法的访问权限
logout登出 ,然后login登录普通用户juste
会发现依然具备user方法的访问权限 ,但是失去了admin方法的访问权限
到此,验证我们基于Shiro框架的细粒度权限控制已经实现
除了Shiro框架 ,我们还有另一个选择 ,那就是同样可以通过集成Spring Security框架来达成相同的目的
关于更多Shiro框架的内容 ,及其和Spring Security之间的异同 ,大家感兴趣可以参考这篇文章:
Shiro最全基础教程_思月行云的博客-CSDN博客
对于Spring Security框架 ,我们暂且留个悬念 ,以后会专门再给大家讲解这部分内容
下一节 ,我们将把关注点投向微服务领域 ,SpringCloudAlibaba将会是接下来几个章节的重头戏 ,敬请期待~
MyAuthorizingRealm
声明:本站所有文章 ,如无特殊说明或标注 ,均为本站原创发布 。任何个人或组织 ,在未征得本站同意时,禁止复制 、盗用、采集 、发布本站内容到任何网站 、书籍等各类媒体平台 。如若本站内容侵犯了原著者的合法权益 ,可联系我们进行处理 。