springboot整合shiro-spring-boot-web-starter实现前后端分离的跨域问题
最近从github上发现一个不错的项目,项目使用的是springboot整合shiro-spring-boot-web-starter实现前后端分离技术,
但部署的启动后,出现了一些问题。
这里是借鉴的文章:
https://segmentfault.com/a/1190000013630601
https://www.cnblogs.com/yfzhou/p/9813177.html
https://www.jianshu.com/p/dbe441dcdbcf
https://segmentfault.com/a/1190000014479154
这里是github上面的项目连接:
https://github.com/CaiBaoHong/biu
这里是shiro官方文档:
https://shiro.apache.org/spring-boot.html
项目本身采用的是gradle项目架构,我将其改为maven后发现了一个问题,关于登陆时,获取用户详细信息被shiro的过滤器拦截了下来。导致无法获取数据。同样的代码,在gradle上,没有问题,反而在maven架构上,出现了问题。百思不得其,实在没办法后只能通过修改shiro的过滤器,让其放行OPTIONS的请求。具体实现如下:
首先是jar:
[code]<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.0</version> </dependency>
前后端分离跨域解决方案是采用CORS
[code]//这里实不实现接口没有影响 @Configuration public class WebConfig implements WebMvcConfigurer { //解决跨域 @Bean public CorsFilter corsFilter() { CorsConfiguration conf = new CorsConfiguration(); conf.addAllowedHeader("*"); conf.addAllowedMethod("*"); conf.addAllowedOrigin("*"); //允许cookie conf.setAllowCredentials(true); conf.setMaxAge(3600L); conf.addExposedHeader("set-cookie"); conf.addExposedHeader("access-control-allow-headers"); conf.addExposedHeader("access-control-allow-methods"); conf.addExposedHeader("access-control-allow-origin"); conf.addExposedHeader("access-control-max-age"); conf.addExposedHeader("X-Frame-Options"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", conf); // 4 对接口配置跨域设置 return new CorsFilter(source); } }
这个是我shiro的验证类:
[code]package com.mrlv.api.shiro; import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.mrlv.api.entity.SysUser; import com.mrlv.api.service.ISysPermService; import com.mrlv.api.service.ISysRoleService; import com.mrlv.api.service.ISysUserService; import com.mrlv.api.vo.AuthVo; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationException; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; /** * 这个类是参照JDBCRealm写的 */ public class UserRealm extends AuthorizingRealm { private static final Logger log = LoggerFactory.getLogger(UserRealm.class); @Autowired private ISysUserService sysUserService; @Autowired private ISysRoleService sysRoleService; @Autowired private ISysPermService sysPermService; //适配密码,编写加密代码 // @Override // public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) { // //设置用于匹配密码的CredentialsMatcher // HashedCredentialsMatcher hashcredentialsMatcher = new HashedCredentialsMatcher(); // //采用算法:Md5Hash,Sha1Hash,Sha256Hash // hashcredentialsMatcher.setHashAlgorithmName(Md5Hash.ALGORITHM_NAME); // //是否采用16进制,默认是true // hashcredentialsMatcher.setStoredCredentialsHexEncoded(false); // //哈希值 // hashcredentialsMatcher.setHashIterations(1024); // super.setCredentialsMatcher(hashcredentialsMatcher); // } //授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { if (principals == null) { throw new AuthorizationException("PrincipalCollection method argument cannot be null."); } SysUser user = (SysUser) getAvailablePrincipal(principals); Set<AuthVo> roles = user.getRoles(); Set<AuthVo> perms = user.getPerms(); log.info("获取角色权限信息: roles: {}, perms: {}",roles,perms); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); info.setRoles(roles.stream().map(AuthVo::getVal).collect(Collectors.toSet())); info.setStringPermissions(perms.stream().map(AuthVo::getVal).collect(Collectors.toSet())); return info; } //认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken userToken = (UsernamePasswordToken)token; String username = userToken.getUsername(); if (username == null){ //账户问题发生异常,抛出此异常 throw new AccountException("用户名不能为空"); } SysUser user = sysUserService.selectOne(new EntityWrapper<SysUser>().eq("login_name", username)); if (user == null){ //当用户不存在的时候,抛出此异常 throw new UnknownAccountException("找不到用户(" + username + ")的账号信息"); } //查询用户的角色和权限存到SimpleAuthenticationInfo中,这样在其它地方 //SecurityUtils.getSubject().getPrincipal()就能拿出用户的所有信息,包括角色和权限 Set<AuthVo> roles = sysRoleService.getRolesByUserId(user.getId()); //用户所有角色值,用于shiro做角色权限的判断 Set<AuthVo> perms = sysPermService.getPermsByUserId(user.getId()); //用户所有权限值,用于shiro做资源权限的判断 user.getRoles().addAll(roles); user.getPerms().addAll(perms); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName()); return info; } }
然后配置shiroConfig:
[code]@Configuration public class ShiroConfig { @Bean public Realm realm() { return new UserRealm(); } @Bean public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); /** * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。 * 在@Controller注解的类的方法中加入@RequiresRole注解,会导致该方法无法映射请求,导致返回404。 * 加入这项配置能解决这个bug */ creator.setUsePrefix(true); return creator; } /** * 这里统一做鉴权,即判断哪些请求路径需要用户登录,哪些请求路径不需要用户登录。 * 这里只做鉴权,不做权限控制,因为权限用注解来做。 * @return */ @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition(); //哪些请求可以匿名访问 chain.addPathDefinition("/auth/login", "anon"); chain.addPathDefinition("/auth/logout", "anon"); chain.addPathDefinition("/page/401", "anon"); chain.addPathDefinition("/page/403", "anon"); chain.addPathDefinition("/page/index", "anon"); //除了以上的请求外,其它请求都需要登录,这里改成corsFilterAAAA。使用这个自定义过滤器 chain.addPathDefinition("/**", "corsFilterAAAA"); return chain; } }
还有application-shiro.yml
[code]shiro: # 未经身份验证的用户重定向到登录页面时使用的登录URL loginUrl: /auth/page/401 # 页面将用户重定向到未经授权的页面(403页) unauthorizedUrl: /auth/page/403 # 用户登录后的默认登录页面(如果在当前会话中找不到替代) successUrl: /auth/page/index
接下来是重点:
从github上面的项目源码上,并没有看到有其他过滤器。但偏偏能完美运行,这里我采用maven构建后的项目会出现被拦截的问题。所以只能修改ShiroWebFilterConfiguration
这里我创建了一个类,继承了ShiroWebFilterConfiguration
代码如下
[code]@Configuration public class ShiroWebFilter extends ShiroWebFilterConfiguration { @Override protected ShiroFilterFactoryBean shiroFilterFactoryBean() { //采用父类的默认方法生成shiroFilterFactoryBean ShiroFilterFactoryBean shiroFilterFactoryBean = super.shiroFilterFactoryBean(); //获取shiroFilterFactoryBean里的Filters集合 Map<String, Filter> filters = shiroFilterFactoryBean.getFilters(); //put进一个自己编写的过滤器,并命名,上面会引用到 filters.put("corsFilterAAAA", new CorsAuthenticationFilter()); shiroFilterFactoryBean.setFilters(filters); return shiroFilterFactoryBean; } }
注意,这里根据借鉴的博客中直接使用shiroConfig来继承ShiroWebFilterConfiguration 的话,会出现循环依赖的bug而导致无法启动。
最后是我自定义的一个过滤器CorsAuthenticationFilter。
[code]public class CorsAuthenticationFilter extends FormAuthenticationFilter { //这个方法是判断是否能通过 @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { System.out.println("测试是否通过自定义"); boolean allowed = super.isAccessAllowed(request, response, mappedValue); if (!allowed) { // 判断请求是否是options请求 String method = WebUtils.toHttp(request).getMethod(); if (StringUtils.equalsIgnoreCase("OPTIONS", method)) { return true; } } return allowed; } //下面这个方法,重写了登陆失败后的输出信息。 // @Override // protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { // HttpServletResponse res = (HttpServletResponse)response; // res.setHeader("Access-Control-Allow-Origin", "*"); // res.setStatus(HttpServletResponse.SC_OK); // res.setCharacterEncoding("UTF-8"); // PrintWriter writer = res.getWriter(); // Map<String, Object> map= new HashMap<>(); // map.put("code", 702); // map.put("msg", "未登录"); // writer.write(JSON.toJSONString(map)); // writer.close(); // return false; // } }
如此,便解决了我遇到的问题。
- springboot+springdata-jpa+vue+swagger ui2+axios实现前后端分离一套增删改查,分页+解决跨域问题
- springboot+springdata-jpa+vue+swagger ui2+axios实现前后端分离一套增删改查,分页+解决跨域问题
- vue+springboot前后端分离实现单点登录跨域问题解决方法
- vue+springboot前后端分离实现单点登录跨域问题解决方法
- idea+HBuilderX实现springboot+springdata-jpa+vue+axios前后端分离增删改查,分页+解决跨域问题的idea页面
- Springboot + Vue + shiro 实现前后端分离、权限控制
- spring-boot前后端分离跨域问题的解决,主要是使用cors
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十二):解决跨域问题
- springboot 解决前后端分离时的跨域问题
- 前后端分离,spring boot跨域问题
- 使用 vue + springboot 前后端分离 跨域、cookie、session 问题,全套配置一篇就够了
- SpringBoot 实现前后端分离的跨域访问(CORS)
- SpringBoot 解决前后端分离 跨域问题
- Spring-boot整合Swagger 实现前后端分离,前后端分离测试
- 前后端分离框架前端react,后端springboot跨域问题分析
- 基于CAS的单点登录SSO[5]: 基于Springboot实现CAS客户端的前后端分离
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例
- 前后端分离项目中shiro与vue的集成时的跨域问题
- 解决SpringBoot整合shiro之后无法加载静态资源文件问题
- 使用springBoot实现 web后端(一)