前后分离springboot2.1集成shiro使用redis做权限认证缓存

编程入门 行业动态 更新时间:2024-10-24 18:22:43

前后分离springboot2.1集成shiro使用redis做权限认证<a href=https://www.elefans.com/category/jswz/34/1771061.html style=缓存"/>

前后分离springboot2.1集成shiro使用redis做权限认证缓存

整整搞了两天,网上好多文章没有标注出小版本,让我很是艰难。这里记录一下。

1:pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0" xmlns:xsi=""xsi:schemaLocation=".0.0 .0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.yunfei</groupId><artifactId>xxx</artifactId><version>0.0.1-SNAPSHOT</version><name>xxx</name><description>study demo</description><properties><java.version>1.8</java.version><mybatis-spring-boot>1.3.0</mybatis-spring-boot><mysql-connector>5.1.39</mysql-connector><fastjson.version>1.2.47</fastjson.version><ehcache.version>2.6.11</ehcache.version><ehcache-web.version>2.0.4</ehcache-web.version><commons-lang3.version>3.3.2</commons-lang3.version><commons-codec.version>1.9</commons-codec.version><shiro-spring.version>1.4.0</shiro-spring.version><shiro-redis.version>3.1.0</shiro-redis.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></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>2.7.0</version></dependency><!-- MySQL 连接驱动依赖 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql-connector}</version></dependency><!-- SpringBoot Mybatis 依赖 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>${mybatis-spring-boot}</version></dependency><!-- lombok依赖 可以减少大量的模块代码--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--Slf4j 依赖--><dependency><groupId>org.slf4j</groupId><artifactId>jcl-over-slf4j</artifactId></dependency><!-- logback 依赖 是slf4j的实现--><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId></dependency><!-- Druid数据库连接池组件 --><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.18</version></dependency><!-- fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>${fastjson.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- .clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>2.9.0</version></dependency><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-core</artifactId><version>${ehcache.version}</version></dependency><dependency><groupId>net.sf.ehcache</groupId><artifactId>ehcache-web</artifactId><version>${ehcache-web.version}</version></dependency><dependency><groupId>commons-net</groupId><artifactId>commons-net</artifactId><version>3.6</version></dependency><dependency><groupId>org.apachemons</groupId><artifactId>commons-lang3</artifactId><version>${commons-lang3.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>${commons-codec.version}</version></dependency><!--poi--><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>RELEASE</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>RELEASE</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>${shiro-spring.version}</version></dependency><dependency><!--session持久化插件--><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>${shiro-redis.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.5</version><configuration><!--允许移动生成的文件 --><verbose>true</verbose><!--允许覆盖生成的文件 --><overwrite>true</overwrite></configuration></plugin></plugins><resources><resource><directory>src/main/resources</directory><includes><include>**/*.xml</include><include>**/*.properties</include><include>**/*.yml</include><include>**/*.*</include></includes></resource><resource><directory>src/main/java</directory><includes><include>**/*.properties</include><include>**/*.yml</include></includes></resource></resources></build></project>

2:shiro配置类

package com.yunfei.cultural.shiro;import com.yunfei.cultural.filter.MyFormAuthenticationFilter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import javax.servlet.Filter;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;/*** @Description: shiro配置类* @Author: HuiYunfei* @Date: 2019/11/9*/
@Configuration
@Slf4j
@Data
@ConfigurationProperties(prefix = "spring.redis")
public class ShiroConfig {private String host;private int port = 6379;private Duration timeout;/*** Filter工厂,设置对应的过滤条件和跳转条件** @return ShiroFilterFactoryBean*/@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);//自定义过滤器,前后分离重定向会出现302等ajax跨域错误,这里直接返回错误不重定向Map<String, Filter> filterMap = new LinkedHashMap<>();filterMap.put("authc", new MyFormAuthenticationFilter());shiroFilterFactoryBean.setFilters(filterMap);// 过滤器链定义映射Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();/** anon:所有url都都可以匿名访问,authc:所有url都必须认证通过才可以访问;* 过滤链定义,从上向下顺序执行,authc 应放在 anon 下面* */filterChainDefinitionMap.put("/system/login", "anon");filterChainDefinitionMap.put("/file/*", "anon");//filterChainDefinitionMap.put("/**", "corsAuthenticationFilter");// 所有url都必须认证通过才可以访问filterChainDefinitionMap.put("/**", "authc");// 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面filterChainDefinitionMap.put("/system/logout", "logout");// 未登录//shiroFilterFactoryBean.setLoginUrl("/system/unLogin");// 未授权//shiroFilterFactoryBean.setUnauthorizedUrl("/system/unAuthorized");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}/*** RedisSessionDAO shiro sessionDao层的实现 通过redis, 使用的是shiro-redis开源插件** @return RedisSessionDAO*/@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());redisSessionDAO.setExpire(1800);return redisSessionDAO;}/*** Session ID 生成器** @return JavaUuidSessionIdGenerator*/@Beanpublic JavaUuidSessionIdGenerator sessionIdGenerator() {return new JavaUuidSessionIdGenerator();}/*** 自定义sessionManager,禁用cookie,使用http header方式传入sessionId token** @return SessionManager*/@Beanpublic SessionManager sessionManager() {MySessionManager mySessionManager = new MySessionManager();mySessionManager.setSessionIdCookieEnabled(false);mySessionManager.setSessionDAO(redisSessionDAO());//这里修改sessionIdCookie的Name属性为jsid可以避免同一请求都会在redis生成一条新的sessionId记录mySessionManager.getSessionIdCookie().setName("jsid");return mySessionManager;}/*** 配置shiro redisManager, 使用的是shiro-redis开源插件** @return RedisManager*/private RedisManager redisManager() {RedisManager redisManager = new RedisManager();redisManager.setHost(host);//redisManager.setPort(port);redisManager.setTimeout((int) timeout.toMillis());return redisManager;}/*** cacheManager 缓存 redis实现, 使用的是shiro-redis开源插件** @return RedisCacheManager*/@Beanpublic RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());// 必须要设置主键名称,shiro-redis 插件用过这个缓存用户信息redisCacheManager.setPrincipalIdFieldName("id");return redisCacheManager;}/*** 权限管理,配置主要是Realm的管理认证** @return SecurityManager*/@Beanpublic SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(myShiroRealm());// 自定义session管理 使用redissecurityManager.setSessionManager(sessionManager());// 自定义缓存实现 使用redissecurityManager.setCacheManager(cacheManager());return securityManager;}/*** 自定义安全域,用户验证、权限等数据在此提供* @return*/@Beanpublic ShiroRealm myShiroRealm() {ShiroRealm myShiroRealm = new ShiroRealm();//关闭myShiroRealm.setAuthenticationCachingEnabled(false);//myShiroRealm.setAuthenticationCacheName("authenticcationCache");myShiroRealm.setAuthorizationCachingEnabled(true);myShiroRealm.setAuthorizationCacheName("authorizationCache");return myShiroRealm;}/** 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}@Beanpublic SimpleCookie cookie() {// cookie的name,对应的默认是 JSESSIONIDSimpleCookie cookie = new SimpleCookie("SHARE_JSESSIONID");cookie.setHttpOnly(true);//  path为 / 用于多个系统共享 JSESSIONID//cookie.setPath("/");return cookie;}/* 此项目使用 shiro 场景为前后端分离项目,这里先注释掉,统一异常处理已在 GlobalExceptionHand.java 中实现 */}

 

这里要注意的是我在很多博客上看到说前后分离的时候shiro过滤器不能跳转jsp,要直接返回给客户端状态让客户端控制跳转,所以这里要shiroFilterFactoryBean.setLoginUrl("/system/unLogin");重定向一下,然后在controller里边返回给json给前端。但是!!!实际上这么操作会出现前端页面循环跳转跨域问题:request doesnt pass access control check:Redirect is not allowed for a preflight request。所以改成在上边添加自定义登陆校验异常过滤器MyFormAuthenticationFilter,然后设为"authc"。

在缓存了用户的认证、授权信息后shiro提供的退出方法有一个bug就是无法删除用户的认证信息,看过底层redis操作的源码可以发现认证和授权的删除方法并不太一样。有兴趣的可以去看看源码然后在登陆认证方法返回SimpleAuthenticationInfo对象的时候返回用户的id去做对应的修改。

package com.yunfei.cultural.filter;import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** Created by hui.yunfei@qq on 2019/11/18*/
@Slf4j
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {public MyFormAuthenticationFilter() {super();}@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {log.info("进入自定义shiro拦截器isAccessAllowed方法");if(request instanceof HttpServletRequest){if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")){log.info("进入自定义shiro拦截器isAccessAllowed方法:OPTIONS请求");return true;}}return super.isAccessAllowed(request, response, mappedValue);}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response)throws Exception {log.info("进入身份认证失败filter");
//        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
//        httpServletResponse.setStatus(200);
//        httpServletResponse.setContentType("application/json;charset=utf-8");
//        PrintWriter pw = httpServletResponse.getWriter();
//        ResultObj result=new ResultObj();
//        result.setInfo(401);
//        result.setMsg("身份认证失败,请重新登录");
//        pw.write(JSONObject.toJSONString(result));
//        pw.flush();
//        pw.close();
//        return false;WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);return false;}
}

我这个地方直接printwriter打印的信息前端看不到不知道为啥,没办法只能把http状态码改成401校验错误给前端让他们判断是否校验成功。

理论上角色、权限认证失败也可以直接重写对应的过滤器RolesAuthorizationFilter、PermissionsAuthorizationFilter的onAccessDenied方法。我这么试过但是没有起作用,因为我的权限、角色认证失败被异常处理类捕捉了。

package com.yunfei.cultural.utils.exception;import com.yunfei.cultural.utils.result.ResultObj;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;import java.util.List;/*** @author * @Description 全局异常处理* @Date 2019/9/14 15:34* @version1.0*/
@EnableWebMvc
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHand {/*** 401 - 未登录*/@ExceptionHandler(UnLoginException.class)public ResultObj handleUnLoginException(UnLoginException e) {String msg = e.getMessage();log.error("登录异常:", e);ResultObj resultObj = new ResultObj();resultObj.setInfo(401);resultObj.setMsg(msg);return resultObj;}/*** 900 - 参数异常*/@ExceptionHandler(LogicException.class)public ResultObj handleLogicException(LogicException e) {String msg =  e.getMessage();log.error("参数异常", e);ResultObj resultObj = new ResultObj();resultObj.setInfo(900);resultObj.setMsg(msg);return resultObj;}/*** 403 - 无权限*/@ExceptionHandler(UnauthorizedException.class)public ResultObj handleLoginException(UnauthorizedException e) {String msg = e.getMessage();log.error("用户无权限:", e);ResultObj resultObj = new ResultObj();resultObj.setInfo(403);resultObj.setMsg("用户无权限");return resultObj;}/*** 999 - 服务器异常*/@ExceptionHandler(SystemException.class)public ResultObj handleSysException(SystemException e) {String msg = "服务内部异常!" + e.getMessage();log.error(msg, e);ResultObj resultObj = new ResultObj();resultObj.setInfo(999);resultObj.setMsg(e.getMessage());return resultObj;}/*** 999 - 服务器异常*/@ExceptionHandler(Exception.class)public ResultObj handleException(Exception e) {String msg = "服务内部异常!" + e.getMessage();log.error(msg, e);ResultObj resultObj = new ResultObj();resultObj.setInfo(999);resultObj.setMsg(e.getMessage());return resultObj;}/*** 处理参数绑定异常,并拼接出错的参数异常信息。* <p>* 创建人:leigq <br>* 创建时间:2017年10月16日 下午9:09:22 <br>* <p>* 修改人: <br>* 修改时间: <br>* 修改备注: <br>* </p>** @param result*/private String handleBindingResult(BindingResult result) {if (result.hasErrors()) {final List<FieldError> fieldErrors = result.getFieldErrors();return fieldErrors.iterator().next().getDefaultMessage();}return null;}}

有人要问那为什么登陆认证异常全局异常没有捕捉到呢,捕捉到了不就也可以不用重写过滤器了吗?理论上是这样但可能我捕捉的异常非shiro内部的登陆异常也可能是其他原因反正我没有成功,有搞成功的小伙伴可以贴在下边哦。

3:shiro认证授权类

package com.yunfei.cultural.shiro;import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunfei.cultural.entity.TUser;
import com.yunfei.cultural.mapper.TRolePermissionsMapper;
import com.yunfei.cultural.mapper.TUserRoleMapper;
import com.yunfei.cultural.model.vo.RolePermissionsModel;
import com.yunfei.cultural.model.vo.UserRoleModel;
import com.yunfei.cultural.service.UserService;
import com.yunfei.cultural.utils.MySimpleByteSource;
import com.yunfei.cultural.utils.ShiroUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;/*** @Description: 自定义shiro认证赋权类* @Author: HuiYunfei* @Date: 2019/11/9*/
@Slf4j
@Component
public class ShiroRealm extends AuthorizingRealm {public ShiroRealm() {}@Autowired@SuppressWarnings("all")public ShiroRealm(UserService userService,TUserRoleMapper userRoleMapper,TRolePermissionsMapper rolePermissionsMapper) {this.userService = userService;this.rolePermissionsMapper=rolePermissionsMapper;this.userRoleMapper=userRoleMapper;}@Resourceprivate UserService userService;@Autowiredprivate TUserRoleMapper userRoleMapper;@Autowiredprivate TRolePermissionsMapper rolePermissionsMapper;@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();TUser user = (TUser) principals.getPrimaryPrincipal();//TUser user = userService.findUserByUserName(username);//获取用户角色List<UserRoleModel> userRoleList=userRoleMapper.findUserRoleByUserId(user.getId());if(userRoleList.size()>0){userRoleList.forEach(t->{authorizationInfo.addRole(t.getRoleMarking());});}//获取用户权限List<RolePermissionsModel> rolePermissionsList = rolePermissionsMapper.findRolePermissionsByUserId(user.getId());if(rolePermissionsList.size()>0){rolePermissionsList.forEach(t->{authorizationInfo.addStringPermission(t.getPermissionsMarking());});}return authorizationInfo;}/*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)throws AuthenticationException {//获取用户的输入的账号.String username = (String) token.getPrincipal();//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法TUser user = userService.findUserByUserName(username);if(user==null){throw new UnknownAccountException();}if(user.getStatus()==1){throw new DisabledAccountException("账号已禁用!");}//处理sessionDefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();DefaultWebSessionManager sessionManager = (DefaultWebSessionManager)securityManager.getSessionManager();Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();//获取当前已登录的用户session列表if(sessions.size()>0){for(Session session:sessions){//清除该用户以前登录时保存的sessionif(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)!=null){Object obj = ((SimplePrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)).asList().get(0);ObjectMapper objectMapper = new ObjectMapper();TUser tUser = objectMapper.convertValue(obj, TUser.class);if(username.equals(tUser.getUsername())) {sessionManager.getSessionDAO().delete(session);}}}}SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, //用户名user.getPassword(), //密码//ByteSource.Util.bytes(user.getSalt()),// md5(salt+password),采用明文访问时,不需要此句new MySimpleByteSource(user.getSalt()),getName()  //realm name);return authenticationInfo;}/*** 将自己的验证方式加入容器** 凭证匹配器(由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了)** @param credentialsMatcher*/@Overridepublic void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();/*** 散列算法:这里可以使用MD5算法 也可以使用SHA-256*/hashedCredentialsMatcher.setHashAlgorithmName(ShiroUtils.hashAlgorithmName);// 散列的次数,比如散列16次,相当于 md5(md5(""));hashedCredentialsMatcher.setHashIterations(ShiroUtils.hashIterations);super.setCredentialsMatcher(hashedCredentialsMatcher);}/*** 重写方法,清除当前用户的的 授权缓存* @param principals*/@Overridepublic void clearCachedAuthorizationInfo(PrincipalCollection principals) {super.clearCachedAuthorizationInfo(principals);}/*** 重写方法,清除当前用户的 认证缓存* @param principals*/@Overridepublic void clearCachedAuthenticationInfo(PrincipalCollection principals) {super.clearCachedAuthenticationInfo(principals);}@Overridepublic void clearCache(PrincipalCollection principals) {super.clearCache(principals);}/*** 自定义方法:清除所有 授权缓存*/public void clearAllCachedAuthorizationInfo() {getAuthorizationCache().clear();}/*** 自定义方法:清除所有 认证缓存*/public void clearAllCachedAuthenticationInfo() {getAuthenticationCache().clear();}/*** 自定义方法:清除所有的  认证缓存  和 授权缓存*/public void clearAllCache() {clearAllCachedAuthenticationInfo();clearAllCachedAuthorizationInfo();}}

身份认证方法发现每次用户重新登陆以后之前的token并没有过期,所以加了一个处理session的功能。

4:自定义session获取类。因项目是前后分离的,前端是在Ajax的请求头加上token访问的,所以要重写这个取session的方法

package com.yunfei.cultural.shiro;import lombok.extern.slf4j.Slf4j;
import org.apachemons.lang3.StringUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.SessionKey;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.WebSessionKey;
import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;/*** @Description: 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,在前后端分离的项目中(也可在移动APP项目使用),*  我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。*  自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法* @Author: HuiYunfei* @Date: 2019/11/9*/
@Slf4j
public class MySessionManager extends DefaultWebSessionManager {private static final String AUTHORIZATION = "token";private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";public MySessionManager() {super();}@Overrideprotected Serializable getSessionId(ServletRequest request, ServletResponse response) {String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);//如果请求头中有 Authorization 则其值为sessionIdif (!StringUtils.isEmpty(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;} else {//否则按默认规则从cookie取sessionIdreturn null;//super.getSessionId(request, response);}}//这个方法加不加我也没看出来区别@Overrideprotected Session retrieveSession(SessionKey sessionKey){Serializable sessionId = getSessionId(sessionKey);ServletRequest request = null;if(sessionKey instanceof WebSessionKey){request = ((WebSessionKey)sessionKey).getServletRequest();}if(request != null && sessionId != null){Session session =  (Session) request.getAttribute(sessionId.toString());if(session != null){return session;}}Session session = super.retrieveSession(sessionKey);if(request != null && sessionId != null){request.setAttribute(sessionId.toString(),session);}return session;}
}

 

5:登陆退出方法

 public LoginResult login(LoginParams params) {LoginResult result = new LoginResult();// 获取Subject实例对象,用户实例Subject currentUser = SecurityUtils.getSubject();// 将用户名和密码封装到UsernamePasswordTokenUsernamePasswordToken token = new UsernamePasswordToken(params.getUsername(), params.getPassword());// 认证try {// 传到 MyShiroRealm 类中的方法进行认证currentUser.login(token);// 构建缓存用户信息返回给前端TUser user = (TUser) currentUser.getPrincipals().getPrimaryPrincipal();//TUser user = this.userMapper.findByUserName(username);//校验当前用户是否有角色List<UserRoleModel> userRoleList=userRoleMapper.findUserRoleByUserId(user.getId());if(userRoleList.size()==0){throw new LogicException("用户暂无角色,不能登录");}//校验当前用户是否有权限登录到后台(是否管理员角色)boolean isAdmin=false;for (UserRoleModel userRole : userRoleList) {if(userRole.getRoleMarking().equals(CommonConstants.ROLE_ADMIN_MARKING)){isAdmin=true;}}result.setIsAdmin(isAdmin);BeanUtils.copyProperties(user, result);result.setToken(currentUser.getSession().getId().toString());userMapper.updateByPrimaryKeySelective(TUser.builder().id(user.getId()).token(result.getToken()).build());}catch (UnknownAccountException e) {throw new LogicException("账号不存在!");}catch (IncorrectCredentialsException e) {throw new LogicException("密码错误!");}return result;}@Overridepublic void logout(JSONObject params) {Subject subject = SecurityUtils.getSubject();subject.logout();}

 6:shiroUtils

package com.yunfei.cultural.utils;import com.yunfei.cultural.entity.TUser;
import com.yunfei.cultural.shiro.ShiroRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import sun.misc.BASE64Encoder;import java.security.SecureRandom;
import java.util.Random;/*** Shiro工具类*/
public class ShiroUtils {/**  加密算法 */public final static String hashAlgorithmName = "SHA-256";/**  循环次数 */public final static int hashIterations = 16;public static String sha256(String password, String salt) {return new SimpleHash(hashAlgorithmName, password, salt, hashIterations).toString();}// 获取一个测试账号 adminpublic static void main(String[] args) {// 3743a4c09a17e6f2829febd09ca54e627810001cf255ddcae9dabd288a949c4aString salt=getNextSalt();System.out.println("salt:"+salt);System.out.println("password:"+sha256("yunfei",salt)) ;}public static String getNextSalt() {Random RANDOM = new SecureRandom();byte[] salt = new byte[16];RANDOM.nextBytes(salt);String str = new BASE64Encoder().encode(salt);return str;}/*** 获取会话*/public static Session getSession() {return SecurityUtils.getSubject().getSession();}/*** Subject:主体,代表了当前“用户”*/public static Subject getSubject() {return SecurityUtils.getSubject();}/*** 重新赋值权限(在比如:给一个角色临时添加一个权限,需要调用此方法刷新权限,否则还是没有刚赋值的权限)* @param myRealm 自定义的realm* @param username 用户名*/
//    public static void reloadAuthorizing(ShiroRealm myRealm, String userName){
//        Subject subject = SecurityUtils.getSubject();
//        String realmName = subject.getPrincipals().getRealmNames().iterator().next();
//        //第一个参数为用户名,第二个参数为realmName,test想要操作权限的用户
//        subject.runAs(new SimplePrincipalCollection(userName, subject.getPrincipals().getRealmNames().iterator().next()));
//        myRealm.getAuthorizationCache().remove(subject.getPrincipals());
//        subject.releaseRunAs();
//    }/*** @Description:清除所有用户的权限信息(修改用户、修改角色时调用)* @Author: HuiYunfei* @Date: 2019/11/12*/public static void clearAllCachedAuthorizationInfo(){DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();ShiroRealm shiroRealm = (ShiroRealm) securityManager.getRealms().iterator().next();shiroRealm.clearAllCachedAuthorizationInfo();}/*** @Description:清除所有用户的认证缓存(暂未启用认证缓存)* @Author: HuiYunfei* @Date: 2019/11/12*/public static void clearAllCachedAuthenticationInfo(){DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();ShiroRealm shiroRealm = (ShiroRealm) securityManager.getRealms().iterator().next();shiroRealm.clearAllCachedAuthorizationInfo();}public static TUser getUserEntity() {return (TUser) SecurityUtils.getSubject().getPrincipal();}public static Integer getUserId() {return getUserEntity().getId();}public static void setSessionAttribute(Object key, Object value) {getSession().setAttribute(key, value);}public static Object getSessionAttribute(Object key) {return getSession().getAttribute(key);}public static boolean isLogin() {return SecurityUtils.getSubject().getPrincipal() != null;}public static void logout() {SecurityUtils.getSubject().logout();}
}

 里边提供了获取加密密码方法和清楚认证授权缓存的方法。这样在修改用户、角色、权限相关信息的时候可以删除缓存实现直接刷新对应用户权限功能。(清除单个用户的方法没调成功清除所有的是可用的)

最后:

前后分离解决跨域问题,在主启动文件直接添加过滤器

 @Beanpublic CorsFilter corsFilter() {final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();final CorsConfiguration config = new CorsConfiguration();// 允许cookies跨域config.setAllowCredentials(true);// 允许向该服务器提交请求的URI,*表示全部允许。。这里尽量限制来源域,比如http://xxxx:8080// ,以降低安全风险。。config.addAllowedOrigin("*");// 允许访问的头信息,*表示全部config.addAllowedHeader("*");// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了config.setMaxAge(18000L);// 允许提交请求的方法,*表示全部允许,也可以单独设置GET、PUT等config.addAllowedMethod("*");/** config.addAllowedMethod("HEAD"); config.addAllowedMethod("GET");//* 允许Get的请求方法 config.addAllowedMethod("PUT");* config.addAllowedMethod("POST"); config.addAllowedMethod("DELETE");* config.addAllowedMethod("PATCH");*/source.registerCorsConfiguration("/**", config);return new CorsFilter(source);}

 

更多推荐

前后分离springboot2.1集成shiro使用redis做权限认证缓存

本文发布于:2024-03-08 22:10:20,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1722589.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:缓存   权限   shiro   redis

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!