您的位置:首页 > 编程语言 > Java开发

springboot整合shiro-spring-boot-web-starter实现前后端分离的跨域问题

2019-03-13 20:15 786 查看

 

最近从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;
//    }
}

如此,便解决了我遇到的问题。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐