当前位置: 首页 > news >正文

SpringBoot请求权限控制——Shiro

Shiro是一个请求权限校验框架,本文介绍如何与SpringBoot集成使用。

登录校验

1、增加maven依赖

<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.13.0</version>
		</dependency>
		
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.13.0</version>
		</dependency>

2、shiro配置类

package com.wzu.utilities.config.shiro;

import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.ValueOperations;

@Configuration
public class ShiroConfig {

	@Bean(name = "shiroFilter")
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager, ValueOperations<String, Object> valueOperations, @Value("${shiro.session-timeout}") Long sessionTimeout) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		
		LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
		filterChainDefinitionMap.put("/login", "anon");
        
        filterChainDefinitionMap.put("/**", "authc");
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		
        Map<String, Filter> filters = new HashMap<>();
        filters.put("authc", new MyAuthenticationFilter(valueOperations, sessionTimeout));
        shiroFilterFactoryBean.setFilters(filters);
		return shiroFilterFactoryBean;
	}
	
	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(customRealm());
		return securityManager;
	}
	
	@Bean
	public CustomRealm customRealm() {
		return new CustomRealm();
	}
}

授权逻辑类CustomRealm

package com.wzu.utilities.config.shiro;

import java.util.concurrent.TimeUnit;

import javax.annotation.Resource;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
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.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.wzu.utilities.entity.po.User;
import com.wzu.utilities.mapper.UserMapper;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CustomRealm extends AuthorizingRealm {
	
	@Resource
	UserMapper userMapper;
	
	@Resource
	ValueOperations<String, String> valueOperations;
	
	@Resource
	RedisTemplate redisTemplate;

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		String token = (String) principals.getPrimaryPrincipal();
		String userStr = valueOperations.get(token);
		if (StringUtils.isBlank(userStr)) {
			throw new AccountException("登录失效,请重新登录");
		}
		
		User user = JSONObject.parseObject(userStr, User.class);
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		//TODO:info.setRoles();
		return info;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
		String name = (String) authenticationToken.getPrincipal();
		String password = new String((char[]) authenticationToken.getCredentials());
		
		User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", name));
		if (user==null || !password.equals(user.getPassword())) {
			throw new AuthenticationException("用户名或密码错误");
		}

		Session session = SecurityUtils.getSubject().getSession();
		String token = (String)session.getAttribute(WebConstants.LOGIN_USER_TOKEN);
		if (StringUtils.isBlank(token)) {
//			for(Session activeSession:sessionDAO.getActiveSessions()) {
//				if(user.getId().equals(activeSession.getAttribute(WebConstants.LOGIN_USER_ID)) && !activeSession.getId().equals(session.getId())) {
//					String tmpToken = (String)activeSession.getAttribute(WebConstants.LOGIN_USER_TOKEN);
//					if(StringUtils.isNotBlank(tmpToken)) {
//						redisTemplate.delete(tmpToken);
//					}
//					sessionDAO.delete(activeSession);
//				}
//			}
			
//			if (user.getLoginNum()!=1) {
//				user.setLoginNum(user.getLoginNum()+1);
//				User toUpdate = new User();
//				toUpdate.setId(user.getId());
//				toUpdate.setLoginNum(user.getLoginNum());
//				userMapper.updateById(toUpdate);
//			}
			
			token = System.currentTimeMillis() + "-" + user.getId();
			user.setToken(token);
			
			valueOperations.set(token, JSON.toJSONString(user), 1, TimeUnit.DAYS);
			session.setAttribute(WebConstants.LOGIN_USER_ID, user.getId());
	    	session.setAttribute(WebConstants.LOGIN_USER_NAME, user.getRealname());
	    	session.setAttribute(WebConstants.LOGIN_USER_TOKEN, token);
	    	session.setAttribute(WebConstants.LOGIN_USER, user);
	    	
		} else {
			log.warn("token不为空");
		}
		
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token, password, getName());
		return simpleAuthenticationInfo;
	}

}

授权验证不通过时的filter类 MyAuthenticationFilter

package com.wzu.utilities.config.shiro;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.springframework.data.redis.core.ValueOperations;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.wzu.utilities.entity.po.User;
import com.wzu.utilities.entity.vo.R;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class MyAuthenticationFilter extends FormAuthenticationFilter {
	
	public MyAuthenticationFilter(ValueOperations<String, Object> valueOperations, Long timeout) {
		this.timeout = timeout;
		this.valueOperations = valueOperations;
	}

	private ValueOperations<String, Object> valueOperations;
	
	private Long timeout;

	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		httpServletResponse.setStatus(200);
		httpServletResponse.setContentType("application/json;charset=utf8");
		Session session = SecurityUtils.getSubject().getSession();
        User user = (User)session.getAttribute(WebConstants.LOGIN_USER);
        String token = httpServletRequest.getHeader("token");
        if (user==null) {
        	log.debug("没有获取到登录信息,当前token值:{}", token);

        	//如果session失效,通过header中的token来获取授权信息
            if(StringUtils.isNotBlank(token)) {
            	String userStr = (String)valueOperations.get(token);
            	if (StringUtils.isBlank(userStr)) {
            		writeNeedLoginInfo(httpServletResponse);
            		return false;
            	}
            	user = JSONObject.parseObject(userStr, User.class);
            	session.setAttribute(WebConstants.LOGIN_USER_ID, user.getId());
            	session.setAttribute(WebConstants.LOGIN_USER_NAME, user.getRealname());
            	session.setAttribute(WebConstants.LOGIN_USER_TOKEN, token);
            	session.setAttribute(WebConstants.LOGIN_USER, user);
            	session.setTimeout(timeout);
            	log.debug("没有获取到登录信息2,当前token值:{}", token);
                return true;
            }
        } else {
        	log.debug("获取到登录信息,当前token值:{}", token);
        	session.setAttribute(WebConstants.LOGIN_USER_ID, user.getId());
        	session.setAttribute(WebConstants.LOGIN_USER_NAME, user.getRealname());
        	session.setAttribute(WebConstants.LOGIN_USER_TOKEN, token);
        	session.setAttribute(WebConstants.LOGIN_USER, user);
        	session.setTimeout(timeout);
            return true;
        }
        
        writeNeedLoginInfo(httpServletResponse);
        return false;
    }
	
	/**
	 * 返回自定义需要登录的json
	 * @param httpServletResponse
	 * @throws IOException
	 */
	private void writeNeedLoginInfo(HttpServletResponse httpServletResponse) throws IOException {
        PrintWriter out = httpServletResponse.getWriter();
        out.println(JSON.toJSONString(R.failWithCode(7777, "请先登录")));
        out.flush();
        out.close();
    }
	
	
}

登录、登出、登录信息获取的请求代码

    @PostMapping("login")
	public R<User> login(String username, String password, HttpServletRequest request, HttpServletResponse response) {
		Subject subject = SecurityUtils.getSubject();
		User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
		if (user==null) {
			throw new AuthenticationException("用户名或密码错误");
		}
		UsernamePasswordToken token = new UsernamePasswordToken(username, MD5SecurityUtil.encryptMD5(password));
		subject.login(token);
		return R.ok(getCurrentLoginUser());
	}


    @PostMapping("logout")
    public R<User> logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.getSession().removeAttribute(WebConstants.LOGIN_USER);
        subject.getSession().removeAttribute(WebConstants.LOGIN_USER_ID);
        String token = (String)subject.getSession().removeAttribute(WebConstants.LOGIN_USER_TOKEN);
        if(StringUtils.isNotBlank(token)) {
        	redisTemplate.delete(token);
        }
        subject.getSession().removeAttribute(WebConstants.LOGIN_USER_NAME);
        
        subject.logout();
        return R.ok();
    }

    @GetMapping("info")
	public R<User> getUserInfo() {
		User user = getCurrentLoginUser();;
		return R.ok(user);
	}

    public User getCurrentLoginUser() {
		 User user = (User) SecurityUtils.getSubject().getSession().getAttribute(WebConstants.LOGIN_USER);
		 if(user==null) {
			 throw new AccountException();
		 }
		 return user;
	 }

对登录失败时的AccountException进行拦截,返回需要登录的json字符串:

package com.wzu.utilities.web.aspect;


import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.wzu.utilities.entity.enums.ControllerResultEnum;
import com.wzu.utilities.entity.vo.R;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author zhangyugu
 * @Date 2021/4/24 7:30 下午
 * @Version 1.0
 */
@Slf4j
@ControllerAdvice
public class WebExceptionHandler {

    @ResponseBody
    @ExceptionHandler
    public R exceptionHandler(Exception e) {
        log.error(e.getMessage(), e);
        if(e instanceof AccountException) {
            return R.fail(e.getMessage());
        }
        if(e instanceof UnauthorizedException) {
            return R.failWithCode(ControllerResultEnum.USER_HASNO_AUTH.getCode(), e.getMessage());
        }
        if(e instanceof AuthenticationException) {
            return R.failWithCode(ControllerResultEnum.NEED_LOGIN.getCode(), e.getMessage());
        }
        if(e instanceof UnauthenticatedException) {
            return R.failWithCode(ControllerResultEnum.NEED_LOGIN.getCode(), e.getMessage());
        }
        return R.fail(e.getMessage());
    }
}
package com.wzu.utilities.entity.enums;

/**
 * @Author zhangyugu
 * @Date 2021/4/8 11:47 上午
 * @Version 1.0
 */
public enum ControllerResultEnum {

    ERROR(300, "运行出错"),
    REQUEST_TIMEOUT(408, "请求超时"),
    SERVER_DOWN(503,"服务器超负载,或者停机维护"),

    NEED_LOGIN(7777, "需要登录"),
    USER_HASNO_AUTH(1003, "用户权限不足"),
    VIOLATION_EXCEPTION(1200, "验证数据出现错误!"),

    RESULT_NULL(1300,"结果为空!"),

    FILE_UPLOAD_PATH_NOT_EXIST(1503,"文件路径不存在");


    Integer code;

    String message;

    ControllerResultEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }
}

请求增加角色/权限验证

ShiroConfig配置类增加如下Bean

 @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 以下两个bean(其中DefaultAdvisorAutoProxyCreator可选)
     * 用于启用 @RequiresRoles 和 @RequiresPermissions 注解
     * @return
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SessionDAO sessionDAO) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager(sessionDAO));
        return authorizationAttributeSourceAdvisor;
    }

在CustomRealm中增加角色、权限的设置

@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		String token = (String) principals.getPrimaryPrincipal();
		String userStr = valueOperations.get(token);
		if (StringUtils.isBlank(userStr)) {
			throw new AccountException("登录失效,请重新登录");
		}
		
		User user = JSONObject.parseObject(userStr, User.class);
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		info.setRoles(Sets.newHashSet(user.getRole()));
		//TODO:权限码 info.setStringPermissions();
		return info;
	}

在Controller类或其方法上增加注解

@RequiresRoles("ADMIN") //验证角色
@RequiresRoles(value= {"ADMIN", "SUPER_ADMIN"}, logical=Logical.OR) //验证角色,ADMIN或者SUPER_ADMIN,注意需要用"或"
@RequiresPermissions("ADD_USER") //验证权限
@RequiresPermissions(value= {"ADD_USER", "DELETE_USER"}, logical=Logical.OR) //验证权限, ADD_USER 或 DELETE_USER

相关文章:

  • 实验8 搜索技术
  • 《论分布式系统架构设计及其应用》架构师论文
  • C#中通过Response.Headers设置自定义参数
  • 【设计模式】原型模式
  • OpenCV图像加权函数:addWeighted
  • TSN CB:恢复算法与潜在错误检测
  • 动态规划----完全平方数(3种写法,逐步简化)
  • cursor中使用prettier-code formatter插件方法
  • 六十天前端强化训练之第十七天React Hooks 入门:useState 深度解析
  • 基于 GEE 利用 Sentinel-1 双极化数据计算 SDWI 指数实现逐月提取水域面积
  • CFD交易与传统股票交易在交易机制上存在哪些显著差异
  • 矩阵交换行(信息学奥赛一本通-1119)
  • Compose笔记(九)--Checkbox
  • 【eNSP实战】使用高级ACL实现单向Ping
  • 基于UniApp + Vue3开发的智能汉字转拼音工具
  • 【前端三剑客】万字总结JavaScript
  • 【6*】差分约束系统学习笔记
  • nerfstudio以及相关使用记录(长期更新)
  • 【STM32】NVIC(嵌套向量中断控制器)
  • 【蓝桥】-动态规划-倒水
  • 国家话剧院发讣告悼念朱媛媛:始终秉持“戏比天大”的信念
  • 全国治安管理工作视频会召开
  • 中国社科院国际合作局副局长廖凡调任世界经济与政治研究所所长
  • 阳朔兴坪镇:在建乾元桥“垮塌”是谣言,系降雨导致工程挡土墙倾斜
  • 受工友诱骗为获好处费代购免税品,海口海关:两当事人被立案
  • 多所院校高规格召开考研动员会,有学院考研报名率达84%