SpringBoot 基于Shiro + Jwt + Redis的用户权限管理 (三) 鉴权
项目Github地址: https://github.com/baiye21/ShiroDemo
-
SpringBoot 基于Shiro + Jwt + Redis的用户权限管理 (一) 简介与配置
-
SpringBoot 基于Shiro + Jwt + Redis的用户权限管理 (二) 认证
-
SpringBoot 基于Shiro + Jwt + Redis的用户权限管理 (三) 鉴权
一,ShiroFilterFactoryBean配置
FilterChainDefinitionMap排除需要权限才能访问的URL,这样才能进入自定义过滤器JwtFilter中进行后续验证。
比如,测试权限的URl为/role/OneRole.do,它不需要写入FilterChainDefinitionMap中,如下图
二,自定义ShiroSessionManager
认证成功后,将shiro生成的sessionid保存到了access_token中,所以携带token访问时,需要从token将其取出。
com.demo.config.ShiroConfig中
/**
* SecurityManager 是 Shiro 架构的核心,通过它来链接Realm和用户(文档中称之为Subject.)
*/
@Bean
public SecurityManager securityManager(
@Qualifier("passwordRealm") PasswordRealm passwordRealm,
@Qualifier("jwtRealm") JwtRealm jwtRealm,
@Qualifier("demoRealm") DemoRealm demoRealm,
@Qualifier("userModularRealmAuthenticator") UserModularRealmAuthenticator userModularRealmAuthenticator) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm
securityManager.setAuthenticator(userModularRealmAuthenticator);
List<Realm> realms = new ArrayList<>();
// 添加多个realm
realms.add(passwordRealm);
realms.add(jwtRealm);
realms.add(demoRealm);
/*
* 关闭shiro自带的session,详情见文档
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
// 不需要将 Shiro Session 中的东西存到任何地方(包括 Http Session 中)
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
// 自定义sessionManager
securityManager.setSessionManager(shiroSessionManager());
// securityManager设置自定义认证规则
securityManager.setRealms(realms);
return securityManager;
}
@Bean
public ShiroSessionManager shiroSessionManager() {
ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
// TODO redis 配置session持久化
shiroSessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
return shiroSessionManager;
}
com.demo.shiro.ShiroSessionManager
@Slf4j
public class ShiroSessionManager extends DefaultWebSessionManager {
public ShiroSessionManager() {
super();
}
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
@Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
// 如果请求头中有 Authorization
String token = WebUtils.toHttp(request).getHeader(Const.TOKEN_HEADER_NAME);
if (!StringUtils.isEmpty(token)) {
if (JwtUtil.verify(token, Const.TOKEN_SECRET)) {
String id = JwtUtil.getClaim(token, Const.JSESSIONID);
log.debug("ShiroSessionManager从http header 取出token中的JSESSIONID:{}", id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
REFERENCED_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
}
return super.getSessionId(request, response);
} else {
// 否则按默认规则从cookie取sessionId
return super.getSessionId(request, response);
}
}
}
三,JwtFilter执行executeLogin();
/**
* 执行登录认证
*
* @param request
* @param response
* @param mappedValue
* @return 是否成功
*/
@Override
// 这个方法判断 尝试进行登录的操作,如果token存在,那么进行提交登录,如果不存在说明可能是正在进行登录或者做其它的事情 直接放过即可
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
try {
executeLogin(request, response);
return true;
} catch (Exception e) {
// return false;
// throw new AuthenticationException("Token失效请重新登录");
// 认证出现异常,传递错误信息msg
String msg = e.getMessage();
// 获取应用异常(该Cause是导致抛出此throwable(异常)的throwable(异常))
Throwable throwable = e.getCause();
if (throwable != null && throwable instanceof SignatureVerificationException) {
// 该异常为JWT的AccessToken认证失败(Token或者密钥不正确)
msg = "token或者密钥不正确(" + throwable.getMessage() + ")";
} else if (throwable != null && throwable instanceof TokenExpiredException) {
// 该异常为JWT的AccessToken已过期(TokenExpiredException),
// 判断RefreshToken未过期就进行AccessToken刷新
if (this.refreshToken(request, response)) {
return true;
} else {
msg = "token已过期(" + throwable.getMessage() + ")";
}
} else {
// 应用异常不为空
if (throwable != null) {
// 获取应用异常msg
msg = throwable.getMessage();
}
}
/**
* 错误两种处理方式
* 1. 将非法请求转发到/401的Controller处理,抛出自定义无权访问异常被全局捕捉再返回Response信息
* 2. 无需转发,直接返回Response信息 一般使用第二种(更方便)
*/
// 直接返回Response信息
this.response401(request, response, msg);
return false;
}
}
四,UserModularRealmAuthenticator加载DemoRealm
login的时候创建的是CustomizedToken,之后的请求Header携带的都是JwtToken,因此区分加载哪一种类型的Realm。
if(authenticationToken instanceof JwtToken) {
log.debug("验证的Token类型是:{}", "JwtToken");
typeRealms.clear();
// 获取header部的token进行强制类型转换
JwtToken jwtToken = (JwtToken) authenticationToken;
for (Realm realm : realms) {
if (realm.getName().contains("Demo")) {
typeRealms.add(realm);
}
}
return doSingleRealmAuthentication(typeRealms.iterator().next(), jwtToken);
}
五,DemoRealm进行鉴权(doGetAuthorizationInfo)
/**
* 功能: 获取用户权限信息,包括角色以及权限。只有当触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
*
* @param principals
* @return AuthorizationInfo 权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
log.info("demoRealm doGetAuthorizationInfo 用户赋权 ");
String userid = null;
if (principals != null) {
// 此处的principals为 UserMaster
Object PrimaryPrincipal = principals.getPrimaryPrincipal();
if (PrimaryPrincipal instanceof UserMaster) {
UserMaster userMaster = (UserMaster) PrimaryPrincipal;
userid = userMaster.getUserId();
} else {
// 此处的principals为token
userid = JwtUtil.getClaim(principals.toString(), Const.TOKEN_CLAIM_USERID);
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 获取用户角色与权限信息
UserAccessInfo userAccessInfo = iUserService.getUserAccessInfo(userid);
/**
* 设置用户拥有的角色集合,<br>
*
* accountType = 1 管理员 admin <br>
* accountType = 2 领导 Leader <br>
* accountType = 3 普通用户 user <br>
*
*/
info.setRoles(userAccessInfo.getRoleSet());
// 设置用户拥有的权限集合
info.addStringPermissions(userAccessInfo.getPermissionSet());
return info;
}
六,getUserAccessInfo
com.demo.service.impl.UserServiceImpl的getUserAccessInfo方法
/**
* 获取用户角色set与权限set
*
* @param userId
* @return
*/
public UserAccessInfo getUserAccessInfo(String userId) {
UserAccessInfo userAccessInfo = new UserAccessInfo();
// 用户角色set
Set<String> roleSet = new HashSet<String>();
// 用户权限set
Set<String> permissionSet = new HashSet<String>();
UserMaster userMaster = userMasterMapper.selectByUserId(userId);
// 账号类型
String accountType = userMaster.getAccountType();
// 管理员 accountType = 1
if (Const.ADMIN_USER_CODE.equals(accountType)) {
// admin
roleSet.add(Const.ADMIN_USER);
// Leader accountType = 2
} else if (Const.LEADWER_USER_CODE.equals(accountType)) {
// Leader
roleSet.add(Const.LEADER_USER);
// 普通用户 accountType = 3
} else if (Const.NORMAL_USER_CODE.equals(accountType)) {
// user
roleSet.add(Const.NORMAL_USER);
}
// 用户权限类型
String permissionType = userMaster.getPermissionType();
if (Const.LEVEL_001_CODE.equals(permissionType)) {
permissionSet.add(Const.LEVEL_001);
} else if (Const.LEVEL_002_CODE.equals(permissionType)) {
permissionSet.add(Const.LEVEL_002);
} else if (Const.LEVEL_003_CODE.equals(permissionType)) {
permissionSet.add(Const.LEVEL_003);
} else if (Const.LEVEL_004_CODE.equals(permissionType)) {
permissionSet.add(Const.LEVEL_004);
} else if (Const.LEVEL_005_CODE.equals(permissionType)) {
permissionSet.add(Const.LEVEL_005);
}
userAccessInfo.setRoleSet(roleSet);
userAccessInfo.setPermissionSet(permissionSet);
return userAccessInfo;
}
七,简单测试
测试用的TestRoleController ,主要测试以下权限:
ps:由于之前测试用户表设计一个用户只对应一种角色一种权限,所以第四个测试只有失败的情况
- @RequiresRoles(Const.ADMIN_USER) 角色-- 只有管理员用户才能访问
- @RequiresRoles(value = { Const.ADMIN_USER, Const.LEADER_USER }, logical = Logical.OR) 角色-- 管理员用户和Leader用户都能访问
- @RequiresPermissions(Const.LEVEL_004) 权限-- 001 能访问
- @RequiresPermissions(value = {Const.LEVEL_004,Const.LEVEL_005}, logical = Logical.AND) 权限-- 004 且 005 才能访问
/*
* Author : baiye <baiye_21@163.com>
Time : 2021/06/30
Function:
*/
@RestController
@RequestMapping("/role/")
public class TestRoleController {
/**
* 只有管理员用户才能访问
*
* @return
* @throws Exception
*/
@RequiresRoles(Const.ADMIN_USER)
@RequestMapping(value = "/OneRole.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> OneRole() throws Exception {
return ServerResponse.createBySuccessMessage("One Role");
}
/**
* 管理员用户和Leader用户能访问
*
* @return
* @throws Exception
*/
@RequiresRoles(value = { Const.ADMIN_USER, Const.LEADER_USER }, logical = Logical.OR)
@RequestMapping(value = "/TwoRole.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> TwoRole() throws Exception {
return ServerResponse.createBySuccessMessage("Two Role");
}
/**
* Permission 001 能访问
*
* @return
* @throws Exception
*/
@RequiresPermissions(Const.LEVEL_004)
@RequestMapping(value = "/OnePermission.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> OnePermission() throws Exception {
return ServerResponse.createBySuccessMessage("One Permission");
}
/**
* Permission 004 且 005 能访问
*
* @return
* @throws Exception
*/
@RequiresPermissions(value = {Const.LEVEL_004,Const.LEVEL_005}, logical = Logical.AND)
@RequestMapping(value = "/TwoPermission.do", method = RequestMethod.POST)
@ResponseBody
public ServerResponse<String> TwoPermission() throws Exception {
return ServerResponse.createBySuccessMessage("Two Permission");
}
}
准备的测试用户
使用demo002用户登录
localhost:9999/role/OneRole.do
localhost:9999/role/TwoRole.do
localhost:9999/role/OnePermission.do
localhost:9999/role/TwoPermission.do