最近从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:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.4.0</version>
</dependency>
前后端分离跨域解决方案是采用CORS
//这里实不实现接口没有影响
@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的验证类:
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:
@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
shiro:
# 未经身份验证的用户重定向到登录页面时使用的登录URL
loginUrl: /auth/page/401
# 页面将用户重定向到未经授权的页面(403页)
unauthorizedUrl: /auth/page/403
# 用户登录后的默认登录页面(如果在当前会话中找不到替代)
successUrl: /auth/page/index
接下来是重点:
从github上面的项目源码上,并没有看到有其他过滤器。但偏偏能完美运行,这里我采用maven构建后的项目会出现被拦截的问题。所以只能修改ShiroWebFilterConfiguration
这里我创建了一个类,继承了ShiroWebFilterConfiguration
代码如下
@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。
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;
// }
}
如此,便解决了我遇到的问题。