在前后端分离项目中使用SpringBoot集成Shiro
前言
这次在处理一个小项目时用到了前后端分离,服务端使用springboot2.x。权限验证使用了Shiro。前后端分离首先需要解决的是跨域问题,POST接口跨域时会预发送一个OPTIONS请求,浏览器收到响应后会继续执行POST请求。 前后端分离后为了保持会话状态使用session持久化插件shiro-redis,持久化session可以持久化到关系型数据库,也可以持久化到非关系型数据库(主要是重写SessionDao)。Shiro已提供了SessionDao接口和抽象类。如果项目中用到Swagger的话,还需要把swagger相关url放行。
搭建依赖
1 2 3 4 5 6 7 8 9 10 11 12 | <dependency> <!--session持久化插件--> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version> 3.2 . 3 </version> </dependency> <dependency> <!--spring shiro依赖--> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version> 1.4 . 1 </version> </dependency> |
Shiro权限配置
1、ShiroConfig。这里主要是shiro核心配置。比如SecurityManager、SessionManager、CacheManager。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | public class ShiroConfig { @Value ( "${spring.redis.shiro.host}" ) private String host; @Value ( "${spring.redis.shiro.port}" ) private int port; @Value ( "${spring.redis.shiro.timeout}" ) private int timeout; @Value ( "${spring.redis.shiro.password}" ) private String password; /** * 权限规则配置 **/ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String, Filter> filters = shiroFilterFactoryBean.getFilters(); filters.put( "authc" , new MyFormAuthorizationFilter()); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //swagger资源不拦截 filterChainDefinitionMap.put( "/swagger-ui.html" , "anon" ); filterChainDefinitionMap.put( "/swagger-resources/**/**" , "anon" ); filterChainDefinitionMap.put( "/v2/api-docs" , "anon" ); filterChainDefinitionMap.put( "/webjars/springfox-swagger-ui/**" , "anon" ); filterChainDefinitionMap.put( "/configuration/security" , "anon" ); filterChainDefinitionMap.put( "/configuration/ui" , "anon" ); filterChainDefinitionMap.put( "/login/ajaxLogin" , "anon" ); filterChainDefinitionMap.put( "/login/unauth" , "anon" ); filterChainDefinitionMap.put( "/login/logout" , "anon" ); filterChainDefinitionMap.put( "/login/register" , "anon" ); filterChainDefinitionMap.put( "/**" , "authc" ); shiroFilterFactoryBean.setLoginUrl( "/login/unauth" ); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * shiro安全管理器(权限验证核心配置) **/ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); securityManager.setSessionManager(sessionManager()); securityManager.setCacheManager(cacheManager()); return securityManager; } /** * 会话管理 **/ @Bean public SessionManager sessionManager() { MySessionManager sessionManager = new MySessionManager(); sessionManager.setSessionIdUrlRewritingEnabled( false ); //取消登陆跳转URL后面的jsessionid参数 sessionManager.setSessionDAO(sessionDAO()); sessionManager.setGlobalSessionTimeout(- 1 ); //不过期 return sessionManager; } /** * 使用的是shiro-redis开源插件 缓存依赖 **/ @Bean public RedisManager redisManager() { RedisManager redisManager = new RedisManager(); redisManager.setHost(host+ ":" +port); redisManager.setTimeout(timeout); redisManager.setPassword(password); return redisManager; } /** * 使用的是shiro-redis开源插件 session持久化 **/ public RedisSessionDAO sessionDAO() { RedisSessionDAO redisSessionDAO = new RedisSessionDAO(); redisSessionDAO.setRedisManager(redisManager()); return redisSessionDAO; } /** * 缓存管理 **/ @Bean public CacheManager cacheManager() { RedisCacheManager redisCacheManager = new RedisCacheManager(); redisCacheManager.setRedisManager(redisManager()); return redisCacheManager; } /** * 权限管理 **/ @Bean public MyShiroRealm myShiroRealm() { return new MyShiroRealm(); } } |
2、MyShiroRealm 用户身份验证、自定义权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | public class MyShiroRealm extends AuthorizingRealm { private Logger logger= LoggerFactory.getLogger(MyShiroRealm. class ); @Resource UserDao userDao; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info( "===================权限验证==================" ); return null ; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken; User currentUser=userDao.findUser(token.getUsername()); if ( null == currentUser){ throw new AuthenticationException( "账户不存在" ); } if (!currentUser.getPassword().equals( new String(token.getPassword()))){ throw new IncorrectCredentialsException( "账户密码不正确" ); } if (currentUser.getIsdel()== 1 ){ throw new LockedAccountException( "账户已冻结" ); } Subject subject = SecurityUtils.getSubject(); BIUser biUser= new BIUser(); biUser.setUserId(currentUser.getUserId()); biUser.setOrgId(currentUser.getOrgid()); biUser.setUserName(currentUser.getUsername()); biUser.setPassword(currentUser.getPassword()); biUser.setSessionId(subject.getSession().getId().toString()); biUser.setIsdel(currentUser.getIsdel()); biUser.setCreateTime(currentUser.getCreatetime()); logger.info( "======已授权" +biUser.toString()+ "====" ); return new SimpleAuthenticationInfo(biUser,biUser.getPassword(),biUser.getUserName()); } } |
3、MySessionManager。shiro权限验证是根据客户端Cookie中的JSESSIONID值来确定身份是否合格。前后端分离后这个地方需要处理。客户端调用服务端登陆接口,验证通过后返回给客户端一个token值(这里我放的是sessionid)。客户端保存token值,然后调用其他接口时把token值放在header中。对前端来说也就是放在ajax的headers参数中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public class MySessionManager extends DefaultWebSessionManager { private static final String AUTHORIZATION = "Authorization" ; private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request" ; public MySessionManager() { } @Override protected Serializable getSessionId(ServletRequest request, ServletResponse response) { //从前端ajax headers中获取这个参数用来判断授权 String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION); if (StringUtils.hasLength(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中取值 return super .getSessionId(request, response); } } } |
4、MyFormAuthorizationFilter。对于跨域的POST请求,浏览器发起POST请求前都会发送一个OPTIONS请求已确定服务器是否可用,OPTIONS请求通过后继续执行POST请求,而shiro自带的权限验证是无法处理OPTIONS请求的,所以这里需要重写isAccessAllowed方法。
1 2 3 4 5 6 7 8 9 10 | public class MyFormAuthorizationFilter extends FormAuthenticationFilter { protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) { HttpServletRequest httpServletRequest = WebUtils.toHttp(servletRequest); if ( "OPTIONS" .equals(httpServletRequest.getMethod())) { return true ; } return super .isAccessAllowed(servletRequest, servletResponse, o); } } |
5、处理跨域
1 2 3 4 5 6 7 8 9 10 11 | @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping( "/**" ) .allowedOrigins( "*" ) .allowedMethods( "PUT" , "DELETE" , "GET" , "POST" ) .allowedHeaders( "*" ) .exposedHeaders( "access-control-allow-headers" , "access-control-allow-methods" , "access-control-allow" + "-origin" , "access-control-max-age" , "X-Frame-Options" , "Authorization" ) .allowCredentials( false ).maxAge( 3600 ); } |
作者:sword-successful
出处:https://www.cnblogs.com/sword-successful/p/11093803.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
博客地址: | http://www.cnblogs.com/sword-successful/ |
博客版权: | 本文以学习、研究和分享为主,欢迎转载,但必须在文章页面明显位置给出原文连接。 如果文中有不妥或者错误的地方还望高手的你指出,以免误人子弟。如果觉得本文对你有所帮助不如【推荐】一下!如果你有更好的建议,不如留言一起讨论,共同进步! 再次感谢您耐心的读完本篇文章。 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具