shiro为何获取不到请求头中的token值

本文探讨了跨域问题及如何影响Shiro权限控制管理获取Header中的Token值,并提供了解决方案,即通过增加过滤器来放行OPTIONS请求。

先简单介绍一下跨域是什么?
在开发中前后端并不在一个端口下,必然涉及到跨域:
XMLHttpRequest会遵守同源策略(same-origin policy). 也即脚本只能访问相同协议/相同主机名/相同端口的资源, 如果要突破这个限制, 那就是所谓的跨域, 此时需要遵守CORS(Cross-Origin Resource Sharing)机制。
解决跨域问题的方法网上有很多,这里就不给方法了。

关键的问题来了,为什么shiro权限控制管理获取不到header中的token值。我们都知道shiro权限控制管理一般都是通过判断请求头中的token来进行认证授权的。
然而在前后端分离项目中,由于跨域,会导致复杂请求,即会发送预检请求(preflighted request),这样会导致在GET/POST等请求之前会先发一个OPTIONS请求,但OPTIONS请求并不带shiro的’Authorization’字段(shiro的Session),即OPTIONS请求不能通过shiro验证,会返回未认证的信息。
当后台没有接收到token值时,开始认为可能是token没有放入headers中,然后当我们将请求的shiro拦截打开后,会发现后台会获取到token值。所以可以判断token值已经放入请求头中。

解决方法:给shiro增加一个过滤器,过滤OPTIONS请求
public class xxx extends FormAuthenticationFilter {
@Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
//Always return true if the request’s method is OPTIONS
if (request instanceof HttpServletRequest) {
if (((HttpServletRequest) request).getMethod().toUpperCase().equals(“OPTIONS”)) {
return true;
}
}
return false;
}
}

<think>好的,我现在需要帮助用户解决在Shiro项目中仅使用Token获取权限的问题。首先,用户之前的提问是关于为什么仅携带Token不带Cookie无法访问权限,现在他们想知道在Shiro中如何配置才能仅依赖Token进行权限控制。这说明用户可能在开发一个基于Token认证的应用,比如前后端分离的项目,需要无状态的权限管理。 首先,我需要回顾Shiro的基本工作原理。Shiro的核心是SecurityManager,它处理认证和授权。用户需要通过Realm来定义如何获取用户信息和权限。传统上,Shiro可能使用Session或Cookie进行状态管理,但用户希望用Token,所以需要调整为无状态模式。 接下来,用户的需求是仅使用Token获取权限。这意味着需要自定义一个支持Token的Realm。可能需要继承AuthorizingRealm,并重写认证和授权的方法。认证方法需要验证Token的有效性,比如JWT的解析和签名验证。授权方法则根据Token中的信息或关联的数据库查询权限。 然后,考虑如何将Token传递到Shiro的流程中。通常,用户会在HTTP请求头中携带Token,比如Authorization: Bearer <token>。需要创建一个自定义的Filter,继承自AccessControlFilter,来从请求头中提取Token,并创建对应的AuthenticationToken实例(如JwtToken),然后交给Subject进行登录认证。 另外,需要禁用Shiro的Session管理,因为用户希望无状态。可以通过设置SessionManager为DisabledSessionManager来实现,这样可以避免Shiro创建不必要的Session,提升性能并符合无状态的要求。 配置Shiro的ini文件或Java Config时,需要注册自定义的Realm、Filter,并设置SecurityManager的相关属性。同时,需要确保URL的过滤规则正确,比如所有请求都经过自定义的JwtFilter,排除登录接口等不需要认证的路径。 可能的错误点包括Token解析失败、签名密钥不正确、权限数据未正确加载等。需要提醒用户使用如JJWT等库处理JWT的生成和解析,并在Realm中正确实现授权信息的获取,可能需要从数据库或缓存中查询用户的角色和权限。 最后,给出示例代码和配置步骤,帮助用户一步步实现。同时,建议测试时使用Postman或curl命令发送带有Token的请求,验证权限控制是否生效。总结关键步骤,确保用户理解每个部分的作用,比如自定义Realm、Filter,以及配置的调整。</think>在Apache Shiro框架中实现纯Token方式的权限控制,需要完成以下关键配置步骤: $$ \begin{array}{|l|l|} \hline \text{核心组件} & \text{实现要点} \\ \hline \text{自定义Token} & \text{实现AuthenticationToken接口} \\ \hline \text{自定义Realm} & \text{继承AuthorizingRealm重写方法} \\ \hline \text{自定义Filter} & \text{继承AccessControlFilter处理请求} \\ \hline \end{array} $$ ### 一、基础架构搭建步骤 1. **创建JWT Token对象** ```java public class JwtToken implements AuthenticationToken { private String token; public JwtToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; // 返回凭证本身 } @Override public Object getCredentials() { return token; // 同样返回凭证 } } ``` 2. **实现自定义Realm** ```java public class JwtRealm extends AuthorizingRealm { // 声明支持处理的Token类型 @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } // 认证逻辑(校验Token有效性) @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { String jwt = (String) token.getCredentials(); try { // 使用JJWT解析验证 Claims claims = Jwts.parser() .setSigningKey("yourSecretKey") .parseClaimsJws(jwt) .getBody(); return new SimpleAuthenticationInfo( claims.getSubject(), jwt, getName()); } catch (Exception e) { throw new AuthenticationException("Token失效"); } } // 授权逻辑(获取权限信息) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 示例:从数据库查询权限 Set<String> roles = userService.getRoles(username); info.setRoles(roles); info.setStringPermissions(userService.getPermissions(roles)); return info; } } ``` ### 二、安全过滤链配置 ```java public class JwtFilter extends AccessControlFilter { // 是否允许访问 @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return false; // 必须执行onAccessDenied } // 拒绝访问时的处理 @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpRequest = (HttpServletRequest) request; String token = httpRequest.getHeader("Authorization"); if (StringUtils.isEmpty(token)) { sendChallenge(response, "未提供Token"); return false; } try { getSubject(request, response).login(new JwtToken(token)); return true; } catch (AuthenticationException e) { sendChallenge(response, "Token验证失败"); return false; } } private void sendChallenge(ServletResponse response, String message) { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setStatus(401); httpResponse.setContentType("application/json"); // 输出错误信息... } } ``` ### 三、Shiro配置类(Spring Boot示例) ```java @Configuration public class ShiroConfig { @Bean public Realm jwtRealm() { JwtRealm realm = new JwtRealm(); realm.setCredentialsMatcher(new CredentialsMatcher() { @Override public boolean doCredentialsMatch( AuthenticationToken token, AuthenticationInfo info) { return true; // 已在doGetAuthenticationInfo完成验证 } }); return realm; } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(jwtRealm()); manager.setSessionManager(new DefaultSessionManager() { @Override public boolean isSessionStorageEnabled() { return false; // 禁用Session存储 } }); return manager; } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition(); chain.addPathDefinition("/login", "anon"); // 登录接口开放 chain.addPathDefinition("/**", "jwt"); // 其他路径需要JWT验证 return chain; } @Bean("jwt") public FilterRegistrationBean<JwtFilter> jwtFilter() { FilterRegistrationBean<JwtFilter> registration = new FilterRegistrationBean<>(); registration.setFilter(new JwtFilter()); registration.setOrder(Ordered.HIGHEST_PRECEDENCE); return registration; } } ``` ### 四、关键注意事项 1. **Token安全设计** - 使用`HS512`或`RS256`强加密算法 - 设置合理过期时间(如2小时) - 在payload中存储必要的最小信息(用户ID、权限标识) 2. **性能优化建议** ```java // 在Realm中增加缓存管理 @Bean public CacheManager cacheManager() { return new MemoryConstrainedCacheManager(); // 生产环境建议用Redis } public class JwtRealm extends AuthorizingRealm { @PostConstruct private void initCacheManager() { setCacheManager(cacheManager()); setAuthorizationCachingEnabled(true); setAuthorizationCacheName("jwtAuthzCache"); } } ``` 3. **处理**(需配合CORS配置) ```java @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.addAllowedOrigin("*"); config.addAllowedHeader("Authorization"); // 必须允许该头 config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } ``` ### 五、测试流程示例 1. 获取Token: ```bash curl -X POST http://localhost:8080/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}' ``` 2. 访问受保护资源: ```bash curl -X GET http://localhost:8080/api/user \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ``` ### 六、常见问题排查 1. **Token验证失败** - 检查签名密钥是否一致 - 验证Token是否过期(`exp` claim) - 确认系统时钟同步(JWT依赖时间戳) 2. **权限不生效** - 检查`doGetAuthorizationInfo`是否被调用 - 确认数据库权限数据正确加载 - 添加`@RequiresRoles("admin")`等注解测试 完整实现后,系统将实现以下工作流程: $$ \text{请求} \rightarrow \text{JwtFilter} \rightarrow \text{创建JwtToken} \rightarrow \text{Realm认证} \rightarrow \text{Realm授权} \rightarrow \text{访问控制} $$ 建议使用Postman进行全流程测试,并配合日志级别调整为DEBUG进行验证。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值